// M3 on a live server F: how many simulation rooms it carries. Each room is a // fresh project with the largest example, one person, the simulation at // 100 %, the cables' activity watched (as an editor does) and a ping across // the network every 6 s. The probe: once a second per room, an action for a // device that does not exist — the room answers it at once, between two of // its thread's ticks, so the time to the answer is how far behind the // room's thread is (plus the way there and back). Rooms are added in steps; // each step is held, then measured. Every project made is deleted at the end. // // npx vite-node tools/live/m3.ts [steps, e.g. 1,10,25,50] [seconds per step] import { cleanUp, openRoom, seededProject, signIn, svc, type Room } from './lib.js'; const EXAMPLE = 'java-examples/Internet_Komplett_mit_eMail_Webserver_Intranet_Portforwarding_Firewall_DHCP_DE.fls'; const steps = (process.argv[2] ?? '1,10,25,50,100,150,200').split(',').map(Number); const seconds = Number(process.argv[3] ?? 45); const SETTLE_SECONDS = 10; interface Running { readonly room: Room; readonly timers: NodeJS.Timeout[]; } const rooms: Running[] = []; let samples: number[] = []; let lost = 0; let errors = 0; const pending = new Map(); let counter = 0; async function addRoom(): Promise { const { project, example } = await seededProject(EXAMPLE); const room = await openRoom(project, await signIn(project, { name: `Raum ${rooms.length + 1}` })); const ipad = example.graph.nodes.find((node) => node.displayName === 'iPad 1')!; room.onMessage = (message) => { if (message.kind === 'error' && typeof message.reqId === 'string' && pending.has(message.reqId)) { samples.push(performance.now() - pending.get(message.reqId)!); pending.delete(message.reqId); } else if (message.kind === 'error' && message.code !== 'unknownNode') errors += 1; }; room.send({ kind: 'speed', value: 100 }); room.send({ kind: 'start' }); await room.waitFor((message) => message.kind === 'state' && message.mode === 'simulation', 20_000); room.send({ kind: 'sub', topics: ['activity', `app:${ipad.id}/terminal`] }); const probe = () => { const reqId = `p${counter++}`; pending.set(reqId, performance.now()); room.send({ kind: 'input', node: 'probe', app: 'terminal', action: 'enter', args: { line: '' }, reqId }); }; const ping = () => room.send({ kind: 'input', node: ipad.id, app: 'terminal', action: 'enter', args: { line: 'ping 120.130.140.80' }, reqId: `ping${counter++}` }); const timers: NodeJS.Timeout[] = [setInterval(ping, 6000)]; // Spread over the second, so that the probes do not all arrive at one tick. timers.push(setTimeout(() => timers.push(setInterval(probe, 1000)), Math.random() * 1000)); rooms.push({ room, timers }); ping(); } const quantile = (sorted: number[], q: number) => sorted[Math.min(sorted.length - 1, Math.floor(q * sorted.length))] ?? NaN; try { console.log('rooms probes p50 ms p95 ms p99 ms max ms lost errors F rooms seeding s'); for (const target of steps) { const seeding = performance.now(); while (rooms.length < target) { await addRoom(); await new Promise((resolve) => setTimeout(resolve, Math.random() * 50)); } const seeded = (performance.now() - seeding) / 1000; await new Promise((resolve) => setTimeout(resolve, SETTLE_SECONDS * 1000)); samples = []; lost = 0; errors = 0; for (const sent of pending.values()) if (performance.now() - sent > 10_000) lost += 1; pending.clear(); await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); lost += [...pending.values()].filter((sent) => performance.now() - sent > 5_000).length; const sorted = [...samples].sort((a, b) => a - b); const health = await (await fetch('https://test-filius.hub.mdg-hamburg.de/healthz')).json(); console.log( [rooms.length, sorted.length, quantile(sorted, 0.5), quantile(sorted, 0.95), quantile(sorted, 0.99), sorted.at(-1) ?? NaN] .map((value, index) => (index < 2 ? String(value) : value.toFixed(0)).padStart(index === 0 ? 5 : 7)) .join(' ') + ` ${String(lost).padStart(4)} ${String(errors).padStart(6)} ${String(health.rooms).padStart(7)} ${seeded.toFixed(0).padStart(9)}`, ); } } finally { for (const { room, timers } of rooms) { timers.forEach((timer) => clearInterval(timer)); room.close(); } await cleanUp(); const health = await (await fetch('https://test-filius.hub.mdg-hamburg.de/healthz')).json(); console.log('after clean-up:', JSON.stringify(health), (await svc('GET', '/svc/sessions')).status); }