| 1 | use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; |
| 2 | use matrix_sdk::test_utils::mocks::MatrixMockServer; |
| 3 | use matrix_sdk_test::{JoinedRoomBuilder, event_factory::EventFactory}; |
| 4 | use matrix_sdk_ui::timeline::{TimelineBuilder, TimelineReadReceiptTracking}; |
| 5 | use ruma::{ |
| 6 | OwnedEventId, events::room::message::RoomMessageEventContentWithoutRelation, owned_room_id, |
| 7 | owned_user_id, |
| 8 | }; |
| 9 | use tokio::runtime::Builder; |
| 10 | |
| 11 | /// Benchmark the time it takes to create a timeline (with read receipt |
| 12 | /// support), when there are many initial events at rest in the event cache. |
| 13 | /// |
| 14 | /// `NUM_EVENTS` is the number of events that will be stored initially in the |
| 15 | /// event cache. It will be a mix of messages, reactions, edits and redactions, |
| 16 | /// so there are some aggregations to take into account by the timeline as well. |
| 17 | pub fn create_timeline_with_initial_events(c: &mut Criterion) { |
| 18 | const NUM_EVENTS: usize = 10000; |
| 19 | |
| 20 | let runtime = Builder::new_multi_thread().enable_all().build().expect("Can't create runtime"); |
| 21 | let room_id = owned_room_id!("!fortesfortunajuvat:example.com"); |
| 22 | |
| 23 | let sender_id = owned_user_id!("@sender:example.com"); |
| 24 | let other_sender_id = owned_user_id!("@other_sender:example.com"); |
| 25 | let another_sender_id = owned_user_id!("@another_sender:example.com"); |
| 26 | |
| 27 | let f = EventFactory::new().room(&room_id); |
| 28 | |
| 29 | let mut events = Vec::new(); |
| 30 | for i in 0..NUM_EVENTS { |
| 31 | let sender = match i % 3 { |
| 32 | 0 => &sender_id, |
| 33 | 1 => &other_sender_id, |
| 34 | 2 => &another_sender_id, |
| 35 | _ => unreachable!("math genius over here"), |
| 36 | }; |
| 37 | |
| 38 | let j = i % 10; |
| 39 | if j < 6 { |
| 40 | // Messages. |
| 41 | events.push(f.text_msg(format!("Message {i}")).sender(sender).into_raw_sync()); |
| 42 | } else if j < 8 { |
| 43 | // Reactions. |
| 44 | let prev_event_id = events[i - 2] |
| 45 | .get_field::<OwnedEventId>("event_id") |
| 46 | .expect("invalid event ID") |
| 47 | .expect("missing event ID"); |
| 48 | events.push(f.reaction(&prev_event_id, "👍").sender(sender).into_raw_sync()); |
| 49 | } else if j == 8 { |
| 50 | // Edit. |
| 51 | // Note: (i-3)%3 is the same as i%3 -> same sender! |
| 52 | let prev_event_id = events[i - 3] |
| 53 | .get_field::<OwnedEventId>("event_id") |
| 54 | .expect("invalid event ID") |
| 55 | .expect("missing event ID"); |
| 56 | events.push( |
| 57 | f.text_msg(format!("* Message {}v2", i - 3)) |
| 58 | .edit( |
| 59 | &prev_event_id, |
| 60 | RoomMessageEventContentWithoutRelation::text_plain(format!( |
| 61 | "Message {}v2", |
| 62 | i - 3 |
| 63 | )), |
| 64 | ) |
| 65 | .sender(sender) |
| 66 | .into_raw_sync(), |
| 67 | ); |
| 68 | } else if j == 9 { |
| 69 | // Redaction. |
| 70 | // Note: (i-6)%3 is the same as i%6 -> same sender! |
| 71 | let prev_event_id = events[i - 6] |
| 72 | .get_field::<OwnedEventId>("event_id") |
| 73 | .expect("invalid event ID") |
| 74 | .expect("missing event ID"); |
| 75 | events.push(f.redaction(&prev_event_id).sender(sender).into_raw_sync()); |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | let builder = JoinedRoomBuilder::new(&room_id) |
| 80 | .add_state_event(f.room_encryption().sender(&sender_id)) |
| 81 | .add_timeline_bulk(events); |
| 82 | |
| 83 | let room = runtime.block_on(async move { |
| 84 | let server = MatrixMockServer::new().await; |
| 85 | let client = server.client_builder().build().await; |
| 86 | |
| 87 | client.event_cache().subscribe().unwrap(); |
| 88 | |
| 89 | let room = server.sync_room(&client, builder).await; |
| 90 | drop(server); |
| 91 | |
| 92 | room |
| 93 | }); |
| 94 | |
| 95 | let mut group = c.benchmark_group("Create a timeline"); |
| 96 | group.throughput(Throughput::Elements(NUM_EVENTS as _)); |
| 97 | group.sample_size(10); |
| 98 | |
| 99 | group.bench_function( |
| 100 | BenchmarkId::new("Create a timeline with initial events", format!("{NUM_EVENTS} events")), |
| 101 | |b| { |
| 102 | b.to_async(&runtime).iter(|| async { |
| 103 | let timeline = TimelineBuilder::new(&room) |
| 104 | .track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents) |
| 105 | .build() |
| 106 | .await |
| 107 | .expect("Could not create timeline"); |
| 108 | |
| 109 | let (items, _) = timeline.subscribe().await; |
| 110 | assert_eq!(items.len(), 20); |
| 111 | }); |
| 112 | }, |
| 113 | ); |
| 114 | |
| 115 | group.finish(); |
| 116 | } |
| 117 | |
| 118 | criterion_group! { |
| 119 | name = room; |
| 120 | config = Criterion::default(); |
| 121 | targets = create_timeline_with_initial_events |
| 122 | } |
| 123 | criterion_main!(room); |