Files
matrix-rust-sdk/benchmarks/benches/room_bench.rs
2026-04-30 17:04:39 +02:00

209 lines
7.4 KiB
Rust

use std::time::Duration;
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::{
cross_process_lock::CrossProcessLockConfig, store::RoomLoadSettings,
test_utils::mocks::MatrixMockServer,
};
use matrix_sdk_base::{
BaseClient, DmRoomDefinition, RoomInfo, RoomState, SessionMeta, StateChanges, StateStore,
ThreadingSupport, store::StoreConfig,
};
use matrix_sdk_sqlite::SqliteStateStore;
use matrix_sdk_test::{JoinedRoomBuilder, base64_sha256_hash, event_factory::EventFactory};
use matrix_sdk_ui::timeline::{TimelineBuilder, TimelineFocus};
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId,
api::client::membership::get_member_events,
events::room::member::{MembershipState, RoomMemberEvent},
mxc_uri, owned_device_id, owned_room_id, owned_user_id,
serde::Raw,
};
use tokio::runtime::Builder;
use wiremock::{Request, ResponseTemplate};
pub fn receive_all_members_benchmark(c: &mut Criterion) {
const MEMBERS_IN_ROOM: usize = 100000;
let runtime = Builder::new_multi_thread().build().expect("Can't create runtime");
let room_id = owned_room_id!("!homohominilupusest:example.com");
let f = EventFactory::new().room(&room_id);
let mut member_events: Vec<Raw<RoomMemberEvent>> = Vec::with_capacity(MEMBERS_IN_ROOM);
for i in 0..MEMBERS_IN_ROOM {
let user_id = OwnedUserId::try_from(format!("@user_{i}:matrix.org")).unwrap();
let event = f
.member(&user_id)
.membership(MembershipState::Join)
.avatar_url(mxc_uri!("mxc://example.org/SEsfnsuifSDFSSEF"))
.display_name("Alice Margatroid")
.reason("Looking for support")
.into_raw();
member_events.push(event);
}
// Create a fake list of changes, and a session to recover from.
let mut changes = StateChanges::default();
changes.add_room(RoomInfo::new(&room_id, RoomState::Joined));
for member_event in member_events.iter() {
let event = member_event.clone().cast();
changes.add_state_event(&room_id, event.deserialize().unwrap(), event);
}
// Sqlite
let sqlite_dir = tempfile::tempdir().unwrap();
let sqlite_store = runtime.block_on(SqliteStateStore::open(sqlite_dir.path(), None)).unwrap();
runtime
.block_on(sqlite_store.save_changes(&changes))
.expect("initial filling of sqlite failed");
let base_client = BaseClient::new(
StoreConfig::new(CrossProcessLockConfig::multi_process(
"cross-process-store-locks-holder-name",
))
.state_store(sqlite_store),
ThreadingSupport::Disabled,
DmRoomDefinition::default(),
);
runtime
.block_on(base_client.activate(
SessionMeta {
user_id: owned_user_id!("@somebody:example.com"),
device_id: owned_device_id!("DEVICE_ID"),
},
RoomLoadSettings::default(),
None,
))
.expect("Could not set session meta");
base_client.get_or_create_room(&room_id, RoomState::Joined);
let request = get_member_events::v3::Request::new(room_id.clone());
let response = get_member_events::v3::Response::new(member_events);
let count = MEMBERS_IN_ROOM;
let name = format!("{count} members");
let mut group = c.benchmark_group("Test");
group.throughput(Throughput::Elements(count as u64));
group.sample_size(50);
group.bench_function(BenchmarkId::new("Handle /members request [SQLite]", name), |b| {
b.to_async(&runtime).iter(|| async {
base_client.receive_all_members(&room_id, &request, &response).await.unwrap();
});
});
{
let _guard = runtime.enter();
drop(base_client);
}
group.finish();
}
pub fn load_pinned_events_benchmark(c: &mut Criterion) {
const PINNED_EVENTS_COUNT: usize = 100;
let runtime = Builder::new_multi_thread().enable_all().build().expect("Can't create runtime");
let room_id = owned_room_id!("!homohominilupusest:example.com");
let sender_id = owned_user_id!("@sender:example.com");
let f = EventFactory::new().room(&room_id).sender(&sender_id);
let mut joined_room_builder =
JoinedRoomBuilder::new(&room_id).add_state_event(f.room_encryption());
let pinned_event_ids: Vec<OwnedEventId> = (0..PINNED_EVENTS_COUNT)
.map(|i| {
EventId::new_v2_or_v3(&base64_sha256_hash(format!("${i}").as_bytes()))
.expect("Invalid event id")
})
.collect();
joined_room_builder =
joined_room_builder.add_state_bulk(vec![f.room_pinned_events(pinned_event_ids).into()]);
let (server, client, room) = runtime.block_on(async move {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
let room = server.sync_room(&client, joined_room_builder).await;
server
.mock_room_event()
.respond_with(move |r: &Request| {
let segments: Vec<&str> = r.url.path_segments().expect("Invalid path").collect();
let event_id_str = segments[6];
let event_id = EventId::parse(event_id_str).expect("Invalid event id in response");
let event = f
.text_msg(format!("Message {event_id_str}"))
.event_id(&event_id)
.server_ts(MilliSecondsSinceUnixEpoch::now())
.into_raw_sync();
ResponseTemplate::new(200)
.set_delay(Duration::from_millis(50))
.set_body_json(event.json())
})
.mount()
.await;
client.event_cache().subscribe().unwrap();
(server, client, room)
});
let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
assert!(!pinned_event_ids.is_empty());
assert_eq!(pinned_event_ids.len(), PINNED_EVENTS_COUNT);
let count = PINNED_EVENTS_COUNT;
let name = format!("{count} pinned events");
let mut group = c.benchmark_group("Load pinned events");
group.throughput(Throughput::Elements(count as u64));
group.sample_size(10);
group.bench_function(BenchmarkId::new("Load pinned events [memory]", name), |b| {
b.to_async(&runtime).iter(|| async {
let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
assert!(!pinned_event_ids.is_empty());
assert_eq!(pinned_event_ids.len(), PINNED_EVENTS_COUNT);
// Reset cache so it always loads the events from the mocked endpoint
client
.event_cache_store()
.lock()
.await
.unwrap()
.as_clean()
.unwrap()
.clear_all_linked_chunks()
.await
.unwrap();
let timeline = TimelineBuilder::new(&room)
.with_focus(TimelineFocus::PinnedEvents)
.build()
.await
.expect("Could not create timeline");
let (items, _) = timeline.subscribe().await;
assert_eq!(items.len(), PINNED_EVENTS_COUNT + 1);
timeline.clear().await;
});
});
{
let _guard = runtime.enter();
drop(server);
}
group.finish();
}
criterion_group! {
name = room;
config = Criterion::default();
targets = receive_all_members_benchmark, load_pinned_events_benchmark,
}
criterion_main!(room);