cat / goq
clone

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

fix/architecture-review-findings
goq / portal / stream-metrics.test.mjs
stream-metrics.test.mjs 13.7K
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} from './stream-metrics.mjs';1213test('cadence reports bounded interval percentiles and hitch counts', () => {14 const cadence = new BoundedCadenceWindow(5000, 8);15 let nowMs = 100;16 cadence.record(nowMs);17 for (const intervalMs of [16, 20, 26, 34, 50]) {18 nowMs += intervalMs;19 cadence.record(nowMs);20 }2122 assert.deepEqual(cadence.summary(nowMs), {23 p50: 26,24 p95: 50,25 p99: 50,26 max: 50,27 count: 5,28 over25Ms: 3,29 over33Ms: 2,30 });31});3233test('cadence uses strict hitch thresholds', () => {34 const cadence = new BoundedCadenceWindow();35 cadence.record(0);36 cadence.record(25);37 cadence.record(25 + CADENCE_HITCH_33_333_MS);38 cadence.record(25 + (2 * CADENCE_HITCH_33_333_MS) + 0.001);3940 const summary = cadence.summary(25 + (2 * CADENCE_HITCH_33_333_MS) + 0.001);41 assert.equal(summary.over25Ms, 2);42 assert.equal(summary.over33Ms, 1);43});4445test('cadence expires by time, caps interval count, and resets its anchor', () => {46 const cadence = new BoundedCadenceWindow(50, 3);47 for (const nowMs of [0, 10, 30, 60, 100]) cadence.record(nowMs);48 assert.equal(cadence.samples.length, 2);49 assert.deepEqual(cadence.samples.map((sample) => sample.value), [30, 40]);5051 assert.deepEqual(cadence.summary(151), {52 p50: null,53 p95: null,54 p99: null,55 max: null,56 count: 0,57 over25Ms: 0,58 over33Ms: 0,59 });6061 cadence.reset();62 cadence.record(5);63 assert.equal(cadence.samples.length, 0);64 cadence.record(15);65 assert.deepEqual(cadence.samples.map((sample) => sample.value), [10]);66});6768test('cadence treats the first frame after damage-driven idle as a fresh anchor', () => {69 const cadence = new BoundedCadenceWindow(1000, 8);70 cadence.record(0);71 cadence.record(16);72 cadence.record(32);73 assert.equal(cadence.summary(32).count, 2);7475 cadence.record(5032);76 assert.deepEqual(cadence.summary(5032), {77 p50: null,78 p95: null,79 p99: null,80 max: null,81 count: 0,82 over25Ms: 0,83 over33Ms: 0,84 });8586 cadence.record(5048);87 assert.deepEqual(cadence.summary(5048), {88 p50: 16,89 p95: 16,90 p99: 16,91 max: 16,92 count: 1,93 over25Ms: 0,94 over33Ms: 0,95 });96});9798test('cadence retains an interval exactly on the rolling-window boundary', () => {99 const cadence = new BoundedCadenceWindow(1000, 2);100 cadence.record(0);101 cadence.record(1000);102 assert.equal(cadence.summary(1000).count, 1);103104 cadence.record(2000.001);105 assert.equal(cadence.summary(2000.001).count, 0);106});107108test('cadence rejects invalid bounds and non-monotonic clocks', () => {109 assert.throws(() => new BoundedCadenceWindow(0), /positive/);110 assert.throws(() => new BoundedCadenceWindow(1000, 0), /positive/);111 assert.throws(() => new BoundedCadenceWindow(1000, 1.5), /positive/);112113 const cadence = new BoundedCadenceWindow();114 assert.throws(() => cadence.record(Number.NaN), /finite/);115 cadence.record(10);116 assert.throws(() => cadence.record(9), /monotonic/);117 assert.throws(() => cadence.summary(9), /monotonic/);118 assert.throws(() => cadence.summary(Number.POSITIVE_INFINITY), /finite/);119});120121test('reports a recent 60 fps cadence instead of a lifetime average', () => {122 const rate = new RollingRateWindow();123 for (let frame = 0; frame <= 60; frame++) rate.record((frame * 1000) / 60);124 assert.ok(Math.abs(rate.rate(1000) - 60) < 0.001);125126 for (let frame = 1; frame <= 30; frame++) rate.record(1000 + (frame * 1000) / 30);127 assert.ok(Math.abs(rate.rate(2000) - 30) < 0.001);128});129130test('signed value percentiles retain lead/lag direction and absolute worst case', () => {131 const skew = new BoundedValueWindow(2000, 4);132 for (const [index, value] of [-40, 10, -20, 30, 50].entries()) skew.record(value, index * 100);133 assert.equal(skew.samples.length, 4);134 assert.deepEqual(135 skew.summary(400),136 { p50: 10, p95: 50, maxAbsolute: 50, count: 4 },137 );138 assert.deepEqual(139 skew.summary(3000),140 { p50: null, p95: null, maxAbsolute: null, count: 0 },141 );142});143144test('prunes idle samples and never grows beyond its fixed bound', () => {145 const rate = new RollingRateWindow(1000, MAX_STREAM_RATE_SAMPLES);146 for (let frame = 0; frame < 1000; frame++) rate.record(frame);147 assert.ok(rate.samples.length <= MAX_STREAM_RATE_SAMPLES);148 assert.equal(rate.rate(5000), 0);149 assert.equal(rate.samples.length, 0);150});151152test('rejects invalid and non-monotonic clocks', () => {153 assert.throws(() => new RollingRateWindow(0), /positive/);154 assert.throws(() => new RollingRateWindow(1000, 1), /at least two/);155 const rate = new RollingRateWindow();156 rate.record(10);157 assert.throws(() => rate.record(9), /monotonic/);158 assert.throws(() => rate.rate(Number.NaN), /finite/);159});160161test('latency percentiles are bounded, deterministic, and expire', () => {162 const latency = new BoundedLatencyWindow(2000, 4);163 for (const [index, value] of [40, 10, 30, 20, 50].entries()) latency.record(value, index * 100);164 assert.equal(latency.samples.length, 4);165 assert.deepEqual(latency.summary(400), {166 p50: 20,167 p95: 50,168 p99: 50,169 max: 50,170 count: 4,171 });172 assert.deepEqual(latency.summary(3000), {173 p50: null,174 p95: null,175 p99: null,176 max: null,177 count: 0,178 });179});180181test('latest-frame presenter bounds jitter buffering at two and drops the oldest frame', () => {182 const callbacks = [];183 const cancelled = [];184 const timerCallbacks = [];185 const cancelledTimers = [];186 const drawn = [];187 const presented = [];188 const dropped = [];189 let nextHandle = 1;190 let nextTimerHandle = 1;191 const presenter = new LatestFramePresenter({192 requestFrame: (callback) => {193 callbacks.push(callback);194 return nextHandle++;195 },196 cancelFrame: (handle) => cancelled.push(handle),197 setTimer: (callback) => {198 timerCallbacks.push(callback);199 return nextTimerHandle++;200 },201 cancelTimer: (handle) => cancelledTimers.push(handle),202 now: () => 999,203 draw: (frame) => drawn.push(frame.id),204 onPresent: (metadata) => presented.push(metadata),205 onDrop: (metadata) => dropped.push(metadata),206 });207 const frame = (id) => ({ id, closed: 0, close() { this.closed++; } });208 const first = frame(1);209 const second = frame(2);210 const third = frame(3);211212 presenter.enqueue(first, 'first');213 presenter.enqueue(second, 'second');214 presenter.enqueue(third, 'third');215 assert.equal(callbacks.length, 1);216 assert.equal(timerCallbacks.length, 1);217 assert.equal(presenter.depth, 2);218 assert.equal(first.closed, 1);219 assert.deepEqual(dropped, ['first']);220221 callbacks.shift()(123);222 assert.deepEqual(drawn, [2]);223 assert.deepEqual(presented, ['second']);224 assert.equal(second.closed, 1);225 assert.equal(callbacks.length, 1);226 assert.deepEqual(cancelledTimers, [1]);227 assert.equal(presenter.depth, 1);228229 callbacks.shift()(140);230 assert.deepEqual(drawn, [2, 3]);231 assert.deepEqual(presented, ['second', 'third']);232 assert.equal(third.closed, 1);233 assert.equal(presenter.depth, 0);234 assert.deepEqual(cancelledTimers, [1, 2]);235236 const fourth = frame(4);237 const fifth = frame(5);238 presenter.enqueue(fourth);239 presenter.enqueue(fifth);240 presenter.clear();241 assert.deepEqual(cancelled, [3]);242 assert.deepEqual(cancelledTimers, [1, 2, 3]);243 assert.equal(fourth.closed, 1);244 assert.equal(fifth.closed, 1);245 assert.equal(presenter.depth, 0);246});247248test('two-frame presenter smooths pair-bursty 60 fps input at display cadence', () => {249 let scheduledCallback = null;250 let nextHandle = 1;251 let nextTimerHandle = 1;252 const presentedAt = [];253 const frames = [];254 let dropped = 0;255 const presenter = new LatestFramePresenter({256 requestFrame: (callback) => {257 assert.equal(scheduledCallback, null, 'only one animation frame may be scheduled');258 scheduledCallback = callback;259 return nextHandle++;260 },261 cancelFrame: () => { scheduledCallback = null; },262 setTimer: () => nextTimerHandle++,263 cancelTimer: () => {},264 now: () => 0,265 draw: () => {},266 onPresent: (_metadata, nowMs) => presentedAt.push(nowMs),267 onDrop: () => dropped++,268 });269 const frame = (id) => {270 const value = { id, closed: 0, close() { this.closed++; } };271 frames.push(value);272 return value;273 };274275 const displayIntervalMs = 1000 / 60;276 for (let displayTick = 0; displayTick < 60; displayTick++) {277 // Model decoder jitter as two frames delivered together every other278 // refresh. The average decoder-output rate is still exactly 60 fps.279 if (displayTick % 2 === 0) {280 presenter.enqueue(frame(displayTick), displayTick);281 presenter.enqueue(frame(displayTick + 1), displayTick + 1);282 assert.equal(presenter.depth, 2);283 }284 const callback = scheduledCallback;285 scheduledCallback = null;286 assert.ok(callback, `missing presentation callback at tick ${displayTick}`);287 callback(displayTick * displayIntervalMs);288 assert.ok(presenter.depth <= 2);289 }290291 assert.equal(presentedAt.length, 60);292 assert.equal(dropped, 0);293 assert.equal(presenter.depth, 0);294 assert.equal(scheduledCallback, null);295 assert.ok(presentedAt.slice(1).every((nowMs, index) => (296 Math.abs((nowMs - presentedAt[index]) - displayIntervalMs) < 0.001297 )));298 assert.ok(frames.every((value) => value.closed === 1));299});300301test('presenter watchdog drains one frame when requestAnimationFrame stalls', () => {302 let frameCallback = null;303 let timerCallback = null;304 const cancelledFrames = [];305 const drawn = [];306 const presented = [];307 const frame = { closed: 0, close() { this.closed++; } };308 const presenter = new LatestFramePresenter({309 requestFrame: (callback) => {310 frameCallback = callback;311 return 11;312 },313 cancelFrame: (handle) => cancelledFrames.push(handle),314 setTimer: (callback, delayMs) => {315 assert.equal(delayMs, 25);316 timerCallback = callback;317 return 22;318 },319 cancelTimer: () => assert.fail('winning watchdog must not cancel itself'),320 now: () => 125,321 draw: (value) => drawn.push(value),322 onPresent: (metadata, nowMs) => presented.push({ metadata, nowMs }),323 });324325 presenter.enqueue(frame, 'stalled');326 timerCallback();327328 assert.deepEqual(cancelledFrames, [11]);329 assert.deepEqual(drawn, [frame]);330 assert.deepEqual(presented, [{ metadata: 'stalled', nowMs: 125 }]);331 assert.equal(frame.closed, 1);332 assert.equal(presenter.depth, 0);333334 frameCallback(126);335 assert.equal(drawn.length, 1, 'a cancelled stale rAF must not present twice');336 assert.equal(frame.closed, 1);337});338339test('normal requestAnimationFrame presentation cancels its watchdog', () => {340 let frameCallback = null;341 let timerCallback = null;342 const cancelledTimers = [];343 const presented = [];344 const frame = { closed: 0, close() { this.closed++; } };345 const presenter = new LatestFramePresenter({346 requestFrame: (callback) => {347 frameCallback = callback;348 return 1;349 },350 cancelFrame: () => assert.fail('winning rAF must not cancel itself'),351 setTimer: (callback) => {352 timerCallback = callback;353 return 2;354 },355 cancelTimer: (handle) => cancelledTimers.push(handle),356 now: () => 999,357 draw: () => {},358 onPresent: (metadata, nowMs) => presented.push({ metadata, nowMs }),359 });360361 presenter.enqueue(frame, 'normal');362 frameCallback(16);363 assert.deepEqual(cancelledTimers, [2]);364 assert.deepEqual(presented, [{ metadata: 'normal', nowMs: 16 }]);365 assert.equal(frame.closed, 1);366367 timerCallback();368 assert.equal(presented.length, 1, 'a cancelled stale timer must not present twice');369});370371test('stale callbacks cannot consume a successor frame after a watchdog race', () => {372 const frameCallbacks = [];373 const timerCallbacks = [];374 const drawn = [];375 let nextFrameHandle = 1;376 let nextTimerHandle = 10;377 const frame = (id) => ({ id, closed: 0, close() { this.closed++; } });378 const first = frame(1);379 const second = frame(2);380 const presenter = new LatestFramePresenter({381 requestFrame: (callback) => {382 frameCallbacks.push(callback);383 return nextFrameHandle++;384 },385 cancelFrame: () => {},386 setTimer: (callback) => {387 timerCallbacks.push(callback);388 return nextTimerHandle++;389 },390 cancelTimer: () => {},391 now: () => 25,392 draw: (value) => drawn.push(value.id),393 });394395 presenter.enqueue(first);396 presenter.enqueue(second);397 timerCallbacks[0]();398 assert.deepEqual(drawn, [1]);399 assert.equal(frameCallbacks.length, 2);400401 frameCallbacks[0](26);402 timerCallbacks[0]();403 assert.deepEqual(drawn, [1]);404 assert.equal(second.closed, 0);405406 frameCallbacks[1](32);407 assert.deepEqual(drawn, [1, 2]);408 assert.equal(first.closed, 1);409 assert.equal(second.closed, 1);410});411412test('clear cancels both schedulers and makes their callbacks harmless', () => {413 let frameCallback = null;414 let timerCallback = null;415 const cancelledFrames = [];416 const cancelledTimers = [];417 const drawn = [];418 const first = { closed: 0, close() { this.closed++; } };419 const second = { closed: 0, close() { this.closed++; } };420 const presenter = new LatestFramePresenter({421 requestFrame: (callback) => {422 frameCallback = callback;423 return 7;424 },425 cancelFrame: (handle) => cancelledFrames.push(handle),426 setTimer: (callback) => {427 timerCallback = callback;428 return 8;429 },430 cancelTimer: (handle) => cancelledTimers.push(handle),431 now: () => 25,432 draw: (frame) => drawn.push(frame),433 });434435 presenter.enqueue(first);436 presenter.enqueue(second);437 presenter.clear();438439 assert.deepEqual(cancelledFrames, [7]);440 assert.deepEqual(cancelledTimers, [8]);441 assert.equal(first.closed, 1);442 assert.equal(second.closed, 1);443 assert.equal(presenter.depth, 0);444445 frameCallback(30);446 timerCallback();447 assert.deepEqual(drawn, []);448 assert.equal(first.closed, 1);449 assert.equal(second.closed, 1);450});