Performance
node bench/run.ts # about a minutenode bench/run.ts --sockets # also 2,000 real sockets (--sockets=200 for a small run)run.ts prints one table with the measured value, the target and pass or FAIL; a FAIL is a result, not an error, and the exit code is 1 only when a script crashes. Not part of the test gate. Results as JSON go to the temp folder.
| Script | Measures | Target |
|---|---|---|
hubbench.mjs |
data down per player (16 players, 32 moving monsters) | 11 KB/s or less |
fxbench.mjs |
frames per player a second with one-off effects | 25 or less |
interp-real.mjs |
others’ movement through the real room.js (interp.mjs: the older synthetic model) |
under 1% jump frames, speed within 10% |
savetest.mjs |
world writes after a failed load; fuzzed saves (--store <module> also times a host app’s own store) |
0 writes, 0 escaped errors |
clientcopy.mjs |
the page’s cost per frame at 60 KB of shared state | under 0.1 ms |
wsclient.mjs + wsserver.mjs (--sockets) |
event loop delay of a real server, 2,000 local sockets; --url ws://... drives a deployed demo server |
p99 under 10 ms |
Live load test (a host app on a 6-core, 12 GB server, the load client on the same machine, 16 players and 32 moving monsters per room):
| Players | Joined | Dropped | Data per player | Total out | Server event loop p99 | Delta age p99 |
|---|---|---|---|---|---|---|
| 208 (13 rooms) | 208 | 0 | 12.2 KB/s | 21 Mbit/s | 12 ms | 1 ms |
| 1,008 (63 rooms) | 960 (48 refused: over the 50 rooms per namespace cap) | 0 | 5.5 KB/s (half rate: busy mode on) | peak 88 Mbit/s | 32 ms (max 106) | 3 ms |
At about 1,000 players the loop passed the 30 ms line, so the server switched to busy mode (rooms over 4 players at half rate) and 3 rooms over their own 250 KB/s budget were slowed; everyone stayed connected. Room for more needs a second process or a bigger server; the data cap (maxOutMbit 100) is the next limit.
Meters
Section titled “Meters”hub.stats().live: frames, bytes and messages per second, tick time p50/p99/max (the last 1,200 sub-ticks of 10 ms, about 12 s), slow readers, matches in progress, saves ok/failed, failed loads, world writes a minute, the 5 busiest rooms (players, bytes out a second, shared size; no names or ids) loop (event-loop delay, set by the host process: hub.loop = {p50, p99, max}), pressure (the server is over its data cap or its loop runs late) and slowedRooms (rooms over their own budget). hub.stats() also has refused: turned-away and dropped counts by reason (full, joins, address, accept_rate, busy_rooms, silent, room_error, room_budget, pressure, too_fast, …). A host app can show the whole stats() on an admin page and a few totals in its health check.