cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix/release-manifest-language
goq / portal / stream-metrics.test.mjs
stream-metrics.test.mjs 15.2K
1import test from 'node:test';2import assert from 'node:assert/strict';3import {4 BoundedCadenceWindow,5 BoundedLatencyWindow,6 BoundedValueWindow,7 CADENCE_HITCH_33_333_MS,8 LatestFramePresenter,9 MAX_STREAM_RATE_SAMPLES,10 RollingRateWindow,11 mediaAuthenticationPresentation,12} from './stream-metrics.mjs';1314test('media authentication presentation reports mode role certification and failures', () => {15 assert.deepEqual(mediaAuthenticationPresentation({16 media_authentication: {17 mode: 'ed25519-v1',18 generation_certified: true,19 delivery_role: 'direct_host',20 verification_failures: 0,21 },22 }), {23 mode: 'ed25519-v1',24 generationState: 'certified',25 deliveryRole: 'direct_host',26 verificationFailures: 0,27 trusted: true,28 });29 assert.equal(mediaAuthenticationPresentation({30 media_authentication: {31 mode: 'ed25519-v1',32 generation_certified: true,33 delivery_role: 'viewer_relay',34 verification_failures: 1,35 },36 }).trusted, false);37});3839test('media authentication presentation fails closed on unknown or malformed diagnostics', () => {40 for (const diagnostics of [41 null,42 {},43 { media_authentication: { mode: 'shared-mac-v1', generation_certified: true, delivery_role: 'direct_host', verification_failures: 0 } },44 { media_authentication: { mode: 'ed25519-v1', generation_certified: true, delivery_role: 'mesh', verification_failures: 0 } },45 { media_authentication: { mode: 'ed25519-v1', generation_certified: true, delivery_role: 'direct_host', verification_failures: -1 } },46 ]) {47 assert.deepEqual(mediaAuthenticationPresentation(diagnostics), {48 mode: 'invalid',49 generationState: 'unverified',50 deliveryRole: 'unknown',51 verificationFailures: null,52 trusted: false,53 });54 }55});5657test('cadence reports bounded interval percentiles and hitch counts', () => {58 const cadence = new BoundedCadenceWindow(5000, 8);59 let nowMs = 100;60 cadence.record(nowMs);61 for (const intervalMs of [16, 20, 26, 34, 50]) {62 nowMs += intervalMs;63 cadence.record(nowMs);64 }6566 assert.deepEqual(cadence.summary(nowMs), {67 p50: 26,68 p95: 50,69 p99: 50,70 max: 50,71 count: 5,72 over25Ms: 3,73 over33Ms: 2,74 });75});7677test('cadence uses strict hitch thresholds', () => {78 const cadence = new BoundedCadenceWindow();79 cadence.record(0);80 cadence.record(25);81 cadence.record(25 + CADENCE_HITCH_33_333_MS);82 cadence.record(25 + (2 * CADENCE_HITCH_33_333_MS) + 0.001);8384 const summary = cadence.summary(25 + (2 * CADENCE_HITCH_33_333_MS) + 0.001);85 assert.equal(summary.over25Ms, 2);86 assert.equal(summary.over33Ms, 1);87});8889test('cadence expires by time, caps interval count, and resets its anchor', () => {90 const cadence = new BoundedCadenceWindow(50, 3);91 for (const nowMs of [0, 10, 30, 60, 100]) cadence.record(nowMs);92 assert.equal(cadence.samples.length, 2);93 assert.deepEqual(cadence.samples.map((sample) => sample.value), [30, 40]);9495 assert.deepEqual(cadence.summary(151), {96 p50: null,97 p95: null,98 p99: null,99 max: null,100 count: 0,101 over25Ms: 0,102 over33Ms: 0,103 });104105 cadence.reset();106 cadence.record(5);107 assert.equal(cadence.samples.length, 0);108 cadence.record(15);109 assert.deepEqual(cadence.samples.map((sample) => sample.value), [10]);110});111112test('cadence treats the first frame after damage-driven idle as a fresh anchor', () => {113 const cadence = new BoundedCadenceWindow(1000, 8);114 cadence.record(0);115 cadence.record(16);116 cadence.record(32);117 assert.equal(cadence.summary(32).count, 2);118119 cadence.record(5032);120 assert.deepEqual(cadence.summary(5032), {121 p50: null,122 p95: null,123 p99: null,124 max: null,125 count: 0,126 over25Ms: 0,127 over33Ms: 0,128 });129130 cadence.record(5048);131 assert.deepEqual(cadence.summary(5048), {132 p50: 16,133 p95: 16,134 p99: 16,135 max: 16,136 count: 1,137 over25Ms: 0,138 over33Ms: 0,139 });140});141142test('cadence retains an interval exactly on the rolling-window boundary', () => {143 const cadence = new BoundedCadenceWindow(1000, 2);144 cadence.record(0);145 cadence.record(1000);146 assert.equal(cadence.summary(1000).count, 1);147148 cadence.record(2000.001);149 assert.equal(cadence.summary(2000.001).count, 0);150});151152test('cadence rejects invalid bounds and non-monotonic clocks', () => {153 assert.throws(() => new BoundedCadenceWindow(0), /positive/);154 assert.throws(() => new BoundedCadenceWindow(1000, 0), /positive/);155 assert.throws(() => new BoundedCadenceWindow(1000, 1.5), /positive/);156157 const cadence = new BoundedCadenceWindow();158 assert.throws(() => cadence.record(Number.NaN), /finite/);159 cadence.record(10);160 assert.throws(() => cadence.record(9), /monotonic/);161 assert.throws(() => cadence.summary(9), /monotonic/);162 assert.throws(() => cadence.summary(Number.POSITIVE_INFINITY), /finite/);163});164165test('reports a recent 60 fps cadence instead of a lifetime average', () => {166 const rate = new RollingRateWindow();167 for (let frame = 0; frame <= 60; frame++) rate.record((frame * 1000) / 60);168 assert.ok(Math.abs(rate.rate(1000) - 60) < 0.001);169170 for (let frame = 1; frame <= 30; frame++) rate.record(1000 + (frame * 1000) / 30);171 assert.ok(Math.abs(rate.rate(2000) - 30) < 0.001);172});173174test('signed value percentiles retain lead/lag direction and absolute worst case', () => {175 const skew = new BoundedValueWindow(2000, 4);176 for (const [index, value] of [-40, 10, -20, 30, 50].entries()) skew.record(value, index * 100);177 assert.equal(skew.samples.length, 4);178 assert.deepEqual(179 skew.summary(400),180 { p50: 10, p95: 50, maxAbsolute: 50, count: 4 },181 );182 assert.deepEqual(183 skew.summary(3000),184 { p50: null, p95: null, maxAbsolute: null, count: 0 },185 );186});187188test('prunes idle samples and never grows beyond its fixed bound', () => {189 const rate = new RollingRateWindow(1000, MAX_STREAM_RATE_SAMPLES);190 for (let frame = 0; frame < 1000; frame++) rate.record(frame);191 assert.ok(rate.samples.length <= MAX_STREAM_RATE_SAMPLES);192 assert.equal(rate.rate(5000), 0);193 assert.equal(rate.samples.length, 0);194});195196test('rejects invalid and non-monotonic clocks', () => {197 assert.throws(() => new RollingRateWindow(0), /positive/);198 assert.throws(() => new RollingRateWindow(1000, 1), /at least two/);199 const rate = new RollingRateWindow();200 rate.record(10);201 assert.throws(() => rate.record(9), /monotonic/);202 assert.throws(() => rate.rate(Number.NaN), /finite/);203});204205test('latency percentiles are bounded, deterministic, and expire', () => {206 const latency = new BoundedLatencyWindow(2000, 4);207 for (const [index, value] of [40, 10, 30, 20, 50].entries()) latency.record(value, index * 100);208 assert.equal(latency.samples.length, 4);209 assert.deepEqual(latency.summary(400), {210 p50: 20,211 p95: 50,212 p99: 50,213 max: 50,214 count: 4,215 });216 assert.deepEqual(latency.summary(3000), {217 p50: null,218 p95: null,219 p99: null,220 max: null,221 count: 0,222 });223});224225test('latest-frame presenter bounds jitter buffering at two and drops the oldest frame', () => {226 const callbacks = [];227 const cancelled = [];228 const timerCallbacks = [];229 const cancelledTimers = [];230 const drawn = [];231 const presented = [];232 const dropped = [];233 let nextHandle = 1;234 let nextTimerHandle = 1;235 const presenter = new LatestFramePresenter({236 requestFrame: (callback) => {237 callbacks.push(callback);238 return nextHandle++;239 },240 cancelFrame: (handle) => cancelled.push(handle),241 setTimer: (callback) => {242 timerCallbacks.push(callback);243 return nextTimerHandle++;244 },245 cancelTimer: (handle) => cancelledTimers.push(handle),246 now: () => 999,247 draw: (frame) => drawn.push(frame.id),248 onPresent: (metadata) => presented.push(metadata),249 onDrop: (metadata) => dropped.push(metadata),250 });251 const frame = (id) => ({ id, closed: 0, close() { this.closed++; } });252 const first = frame(1);253 const second = frame(2);254 const third = frame(3);255256 presenter.enqueue(first, 'first');257 presenter.enqueue(second, 'second');258 presenter.enqueue(third, 'third');259 assert.equal(callbacks.length, 1);260 assert.equal(timerCallbacks.length, 1);261 assert.equal(presenter.depth, 2);262 assert.equal(first.closed, 1);263 assert.deepEqual(dropped, ['first']);264265 callbacks.shift()(123);266 assert.deepEqual(drawn, [2]);267 assert.deepEqual(presented, ['second']);268 assert.equal(second.closed, 1);269 assert.equal(callbacks.length, 1);270 assert.deepEqual(cancelledTimers, [1]);271 assert.equal(presenter.depth, 1);272273 callbacks.shift()(140);274 assert.deepEqual(drawn, [2, 3]);275 assert.deepEqual(presented, ['second', 'third']);276 assert.equal(third.closed, 1);277 assert.equal(presenter.depth, 0);278 assert.deepEqual(cancelledTimers, [1, 2]);279280 const fourth = frame(4);281 const fifth = frame(5);282 presenter.enqueue(fourth);283 presenter.enqueue(fifth);284 presenter.clear();285 assert.deepEqual(cancelled, [3]);286 assert.deepEqual(cancelledTimers, [1, 2, 3]);287 assert.equal(fourth.closed, 1);288 assert.equal(fifth.closed, 1);289 assert.equal(presenter.depth, 0);290});291292test('two-frame presenter smooths pair-bursty 60 fps input at display cadence', () => {293 let scheduledCallback = null;294 let nextHandle = 1;295 let nextTimerHandle = 1;296 const presentedAt = [];297 const frames = [];298 let dropped = 0;299 const presenter = new LatestFramePresenter({300 requestFrame: (callback) => {301 assert.equal(scheduledCallback, null, 'only one animation frame may be scheduled');302 scheduledCallback = callback;303 return nextHandle++;304 },305 cancelFrame: () => { scheduledCallback = null; },306 setTimer: () => nextTimerHandle++,307 cancelTimer: () => {},308 now: () => 0,309 draw: () => {},310 onPresent: (_metadata, nowMs) => presentedAt.push(nowMs),311 onDrop: () => dropped++,312 });313 const frame = (id) => {314 const value = { id, closed: 0, close() { this.closed++; } };315 frames.push(value);316 return value;317 };318319 const displayIntervalMs = 1000 / 60;320 for (let displayTick = 0; displayTick < 60; displayTick++) {321 // Model decoder jitter as two frames delivered together every other322 // refresh. The average decoder-output rate is still exactly 60 fps.323 if (displayTick % 2 === 0) {324 presenter.enqueue(frame(displayTick), displayTick);325 presenter.enqueue(frame(displayTick + 1), displayTick + 1);326 assert.equal(presenter.depth, 2);327 }328 const callback = scheduledCallback;329 scheduledCallback = null;330 assert.ok(callback, `missing presentation callback at tick ${displayTick}`);331 callback(displayTick * displayIntervalMs);332 assert.ok(presenter.depth <= 2);333 }334335 assert.equal(presentedAt.length, 60);336 assert.equal(dropped, 0);337 assert.equal(presenter.depth, 0);338 assert.equal(scheduledCallback, null);339 assert.ok(presentedAt.slice(1).every((nowMs, index) => (340 Math.abs((nowMs - presentedAt[index]) - displayIntervalMs) < 0.001341 )));342 assert.ok(frames.every((value) => value.closed === 1));343});344345test('presenter watchdog drains one frame when requestAnimationFrame stalls', () => {346 let frameCallback = null;347 let timerCallback = null;348 const cancelledFrames = [];349 const drawn = [];350 const presented = [];351 const frame = { closed: 0, close() { this.closed++; } };352 const presenter = new LatestFramePresenter({353 requestFrame: (callback) => {354 frameCallback = callback;355 return 11;356 },357 cancelFrame: (handle) => cancelledFrames.push(handle),358 setTimer: (callback, delayMs) => {359 assert.equal(delayMs, 25);360 timerCallback = callback;361 return 22;362 },363 cancelTimer: () => assert.fail('winning watchdog must not cancel itself'),364 now: () => 125,365 draw: (value) => drawn.push(value),366 onPresent: (metadata, nowMs) => presented.push({ metadata, nowMs }),367 });368369 presenter.enqueue(frame, 'stalled');370 timerCallback();371372 assert.deepEqual(cancelledFrames, [11]);373 assert.deepEqual(drawn, [frame]);374 assert.deepEqual(presented, [{ metadata: 'stalled', nowMs: 125 }]);375 assert.equal(frame.closed, 1);376 assert.equal(presenter.depth, 0);377378 frameCallback(126);379 assert.equal(drawn.length, 1, 'a cancelled stale rAF must not present twice');380 assert.equal(frame.closed, 1);381});382383test('normal requestAnimationFrame presentation cancels its watchdog', () => {384 let frameCallback = null;385 let timerCallback = null;386 const cancelledTimers = [];387 const presented = [];388 const frame = { closed: 0, close() { this.closed++; } };389 const presenter = new LatestFramePresenter({390 requestFrame: (callback) => {391 frameCallback = callback;392 return 1;393 },394 cancelFrame: () => assert.fail('winning rAF must not cancel itself'),395 setTimer: (callback) => {396 timerCallback = callback;397 return 2;398 },399 cancelTimer: (handle) => cancelledTimers.push(handle),400 now: () => 999,401 draw: () => {},402 onPresent: (metadata, nowMs) => presented.push({ metadata, nowMs }),403 });404405 presenter.enqueue(frame, 'normal');406 frameCallback(16);407 assert.deepEqual(cancelledTimers, [2]);408 assert.deepEqual(presented, [{ metadata: 'normal', nowMs: 16 }]);409 assert.equal(frame.closed, 1);410411 timerCallback();412 assert.equal(presented.length, 1, 'a cancelled stale timer must not present twice');413});414415test('stale callbacks cannot consume a successor frame after a watchdog race', () => {416 const frameCallbacks = [];417 const timerCallbacks = [];418 const drawn = [];419 let nextFrameHandle = 1;420 let nextTimerHandle = 10;421 const frame = (id) => ({ id, closed: 0, close() { this.closed++; } });422 const first = frame(1);423 const second = frame(2);424 const presenter = new LatestFramePresenter({425 requestFrame: (callback) => {426 frameCallbacks.push(callback);427 return nextFrameHandle++;428 },429 cancelFrame: () => {},430 setTimer: (callback) => {431 timerCallbacks.push(callback);432 return nextTimerHandle++;433 },434 cancelTimer: () => {},435 now: () => 25,436 draw: (value) => drawn.push(value.id),437 });438439 presenter.enqueue(first);440 presenter.enqueue(second);441 timerCallbacks[0]();442 assert.deepEqual(drawn, [1]);443 assert.equal(frameCallbacks.length, 2);444445 frameCallbacks[0](26);446 timerCallbacks[0]();447 assert.deepEqual(drawn, [1]);448 assert.equal(second.closed, 0);449450 frameCallbacks[1](32);451 assert.deepEqual(drawn, [1, 2]);452 assert.equal(first.closed, 1);453 assert.equal(second.closed, 1);454});455456test('clear cancels both schedulers and makes their callbacks harmless', () => {457 let frameCallback = null;458 let timerCallback = null;459 const cancelledFrames = [];460 const cancelledTimers = [];461 const drawn = [];462 const first = { closed: 0, close() { this.closed++; } };463 const second = { closed: 0, close() { this.closed++; } };464 const presenter = new LatestFramePresenter({465 requestFrame: (callback) => {466 frameCallback = callback;467 return 7;468 },469 cancelFrame: (handle) => cancelledFrames.push(handle),470 setTimer: (callback) => {471 timerCallback = callback;472 return 8;473 },474 cancelTimer: (handle) => cancelledTimers.push(handle),475 now: () => 25,476 draw: (frame) => drawn.push(frame),477 });478479 presenter.enqueue(first);480 presenter.enqueue(second);481 presenter.clear();482483 assert.deepEqual(cancelledFrames, [7]);484 assert.deepEqual(cancelledTimers, [8]);485 assert.equal(first.closed, 1);486 assert.equal(second.closed, 1);487 assert.equal(presenter.depth, 0);488489 frameCallback(30);490 timerCallback();491 assert.deepEqual(drawn, []);492 assert.equal(first.closed, 1);493 assert.equal(second.closed, 1);494});