feat: F5.6c measured and accepted on test-filius; tickets up to 5 s ahead of F's clock
test-filius refused every ticket as from the future: its clock was 886 ms behind the dashboard's machine, with NTP still 8 ms (one ticket in 125, as a ticket carries whole seconds). F now takes a ticket signed up to 5 s ahead (TICKET_AHEAD_SECONDS); itsdangerous refuses one second, and the oracle keeps that on record. tools/live measures and accepts on a live F, with fresh projects each run: m3 (200 rooms of the largest example with pings: probe p95 37 ms), m4 (63 KB document; dragging makes an update per pointer move, 59/s), m5 (an hour through the proxy), acceptance (two browsers, 14 checks, passed three times) and clock (F's clock against ours). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
// 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<string, number>();
|
||||
let counter = 0;
|
||||
|
||||
async function addRoom(): Promise<void> {
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user