Files
synapse/scripts-dev/createroom_bench.py
T
Erik JohnstonandClaude Opus 5.5 4dd87ff58a Add /createRoom benchmarks and fix the template DB under the Rust driver
`tests/rest/client/bench_createroom.py` times room creation per room in
the trial harness, with optional cProfile dumps and per-room counts of
database interactions and SQL statements. It skips unless
`SYNAPSE_BENCH_CREATEROOM` is set.

`scripts-dev/createroom_bench.py` times it against a real server, reusing
`persist_bench.Server`. That server now lifts the room-creation rate limit
and can run on psycopg2 instead of the native driver.

The test template database's maintenance connection now always uses
psycopg2, as `setup_test_homeserver` does. The Rust driver's `postgres`
module has no `connect`, so it failed under `SYNAPSE_TEST_RUST_DRIVER=1`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVLsNh6akt4ifEizY4D1ta
2026-10-18 16:09:03 +00:00

140 lines
4.5 KiB
Python
Executable File

#!/usr/bin/env python
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#
# Copyright (C) 2026 Element Creations Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# See the GNU Affero General Public License for more details:
# <https://www.gnu.org/licenses/agpl-3.0.html>.
#
"""Time `/createRoom` against a real Synapse on Postgres.
Starts the server the way `persist_bench.py` does, once per
`rust_event_persistence` level. For each scenario it creates five warm-up
rooms, then `--rooms` rooms one after another, and reports the mean and median
request time and the CPU of the reactor thread and of the process per room.
poetry run python scripts-dev/createroom_bench.py --levels off,full
`--psycopg2` runs the server on psycopg2 instead of the native driver.
"""
import argparse
import json
import os
import statistics
import sys
import tempfile
import time
from typing import Any
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import persist_bench as pb # noqa: E402
SCENARIOS: dict[str, dict[str, Any]] = {
"plain": {},
"private_invite": {"preset": "private_chat", "invite": ["@invitee:bench"]},
"public_named": {"preset": "public_chat", "name": "Bench room", "topic": "A topic"},
"initial_state": {
"initial_state": [
{
"type": "m.room.encryption",
"state_key": "",
"content": {"algorithm": "m.megolm.v1.aes-sha2"},
},
{
"type": "m.room.guest_access",
"state_key": "",
"content": {"guest_access": "can_join"},
},
{
"type": "m.space.child",
"state_key": "!a:bench",
"content": {"via": ["bench"]},
},
{
"type": "m.space.child",
"state_key": "!b:bench",
"content": {"via": ["bench"]},
},
]
},
}
CREATE_ROOM = "/_matrix/client/v3/createRoom"
def bench_level(level: str, rooms: int, rust_driver: bool) -> list[dict[str, Any]]:
results = []
workdir = tempfile.mkdtemp(prefix=f"createroom-bench-{level}-")
server = pb.Server(level, workdir, rust_driver=rust_driver)
try:
server.start()
token = pb.register("creator")
pb.register("invitee")
for name, body in SCENARIOS.items():
for _ in range(5):
pb.ok("POST", CREATE_ROOM, body, token)
before = server.sample()
times = []
for _ in range(rooms):
t0 = time.perf_counter()
pb.ok("POST", CREATE_ROOM, body, token)
times.append(time.perf_counter() - t0)
after = server.sample()
row = {
"level": level,
"scenario": name,
"mean_ms": 1000 * statistics.mean(times),
"p50_ms": 1000 * statistics.median(times),
"reactor_cpu_ms": 1000
* (after.reactor_cpu - before.reactor_cpu)
/ rooms,
"process_cpu_ms": 1000
* (after.process_cpu - before.process_cpu)
/ rooms,
}
results.append(row)
print(row, flush=True)
finally:
server.stop()
return results
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--levels", default="off,full")
parser.add_argument("--rooms", type=int, default=40)
parser.add_argument("--psycopg2", action="store_true")
parser.add_argument("--json", help="also write the results to this file")
args = parser.parse_args()
results = []
for level in args.levels.split(","):
results += bench_level(level, args.rooms, rust_driver=not args.psycopg2)
print(
f"\n{'scenario':<16} {'level':<12} {'mean ms':>8} {'p50 ms':>8}"
f" {'reactor ms':>11} {'process ms':>11}"
)
for r in results:
print(
f"{r['scenario']:<16} {r['level']:<12} {r['mean_ms']:>8.1f}"
f" {r['p50_ms']:>8.1f} {r['reactor_cpu_ms']:>11.1f}"
f" {r['process_cpu_ms']:>11.1f}"
)
if args.json:
with open(args.json, "w") as f:
json.dump(results, f, indent=2)
if __name__ == "__main__":
main()