import { describe, expect, test } from "vitest"; import { buildPlan, CUMULATIVE_COMPONENT_SETS, estimateDurationMs, formatSeries, ISOLATED_COMPONENT_SETS, type BenchmarkStep } from "./benchmarkPlan"; import type { BenchmarkResult, BenchmarkRun, SceneApplied } from "./benchmarkRunner"; import { FrameSampler, JANK_MS, seriesStats } from "./frameSampler"; import { absoluteFitTrustworthy, formatTable, gpuTimerTrustworthy, hasFootprints, isDrifted, marginalCosts, memoryFits, memoryFitTrustworthy, propagationCosts, repeatChecks, reportRows, type RepeatCheck, scalingFits, thinRows, toCsv, } from "./report"; // A result carrying only what the report reads. `cpuMs` is the figure every // derived table differences, so it is the one the fixtures set deliberately. function result(options: { sats: number; components: string[]; clock?: number; cpuMs: number; gpuMs?: number; wallMs?: number; tickMs?: number; repeat?: boolean; buildMs?: number; visible?: number; frames?: number; heapMb?: readonly number[]; footprintMb?: number; }): BenchmarkResult { const clock = options.clock ?? 1; const components = options.components; const step: BenchmarkStep = { index: 0, satelliteCount: options.sats, components, clockMultiplier: clock, repeat: options.repeat ?? false, series: formatSeries(components, clock), label: "", }; const applied: SceneApplied = { satellitesRequested: options.sats, satellitesVisible: options.visible ?? options.sats, componentsRequested: components, componentsDrawn: components, componentInstances: {}, clockMultiplier: clock, entities: 0, primitives: 0, clearMs: 0, buildMs: options.buildMs ?? 0, }; const frames = options.frames ?? 100; return { step, applied, frames: { frames, elapsedMs: frames * 10, fps: 100, wall: seriesStats([options.wallMs ?? 10]), cpu: seriesStats([options.cpuMs]), tick: seriesStats([options.tickMs ?? 0]), gpu: options.gpuMs === undefined ? undefined : seriesStats([options.gpuMs]), heap: options.heapMb === undefined ? undefined : seriesStats(options.heapMb), jankFrames: 0, jankRatio: 0, }, footprint: options.footprintMb === undefined ? undefined : { totalMb: options.footprintMb * 1.4, jsMb: options.footprintMb, workerMb: 0, elapsedMs: 17_000 }, }; } const run = (results: BenchmarkResult[]): BenchmarkRun => ({ startedAtIso: "2026-01-01T00:00:00.000Z", spec: { satelliteCounts: [], componentSets: [] }, environment: {}, options: { warmupMs: 0, sampleMs: 0 }, catalogSize: 0, results, cancelled: false, }); describe("seriesStats", () => { test("no values means no stats, rather than a row of zeroes", () => { expect(seriesStats([])).toBeUndefined(); }); test("percentiles come off the sorted values, whatever order they arrived in", () => { const stats = seriesStats([50, 10, 30, 20, 40]); expect(stats).toMatchObject({ count: 5, min: 10, max: 50, mean: 30, p50: 30 }); }); test("the input array is left in its arrival order", () => { const values = [3, 1, 2]; seriesStats(values); expect(values).toEqual([3, 1, 2]); }); }); describe("FrameSampler", () => { test("the first push only sets the origin, so one timestamp is no frame", () => { const sampler = new FrameSampler(); sampler.push(1000); expect(sampler.frames).toBe(0); expect(sampler.snapshot().wall).toBeUndefined(); }); test("deltas between timestamps become the wall series", () => { const sampler = new FrameSampler(); for (const [index, time] of [0, 10, 20, 30].entries()) { sampler.push(1000 + time, index); } const snapshot = sampler.snapshot(); expect(snapshot.frames).toBe(3); expect(snapshot.elapsedMs).toBe(30); expect(snapshot.fps).toBeCloseTo(100); expect(snapshot.wall?.mean).toBe(10); }); test("reset bumps the epoch, so a reading in flight can tell it is stale", () => { // The GPU clock's only defence against attributing a warmup frame to the // sample, or the tail of one step to the next: a query records the epoch it // started in and the delivery is dropped when the sampler has moved on. const sampler = new FrameSampler(); const issued = sampler.epoch; sampler.pushGpu(5); expect(sampler.snapshot().gpu?.mean).toBe(5); sampler.reset(); expect(sampler.epoch).not.toBe(issued); // What the collector does with a stale reading: nothing. if (sampler.epoch === issued) { sampler.pushGpu(250); } expect(sampler.snapshot().gpu).toBeUndefined(); // A reading issued after the reset still lands. sampler.pushGpu(7); expect(sampler.snapshot().gpu?.mean).toBe(7); }); test("frames slower than 30 fps count as jank", () => { const sampler = new FrameSampler(); sampler.push(0); sampler.push(10); sampler.push(10 + JANK_MS + 1); const snapshot = sampler.snapshot(); expect(snapshot.jankFrames).toBe(1); expect(snapshot.jankRatio).toBeCloseTo(0.5); }); test("reset drops the samples but keeps the origin, so no frame is lost at the seam", () => { const sampler = new FrameSampler(); sampler.push(0); sampler.push(100); sampler.reset(); sampler.push(110); expect(sampler.frames).toBe(1); expect(sampler.snapshot().wall?.mean).toBe(10); }); test("a limit makes the window roll rather than grow", () => { const sampler = new FrameSampler(2); for (let i = 0; i <= 10; i += 1) { sampler.push(i * 10); } expect(sampler.frames).toBe(2); }); }); describe("buildPlan", () => { test("counts are deduplicated, sorted and cleared of nonsense", () => { const steps = buildPlan({ satelliteCounts: [100, 0, 100, -5, 10.5], componentSets: [["Point"]] }); expect(steps.filter((step) => !step.repeat).map((step) => step.satelliteCount)).toEqual([0, 100]); }); test("component sets are outermost so a cancelled sweep leaves whole series", () => { const steps = buildPlan({ satelliteCounts: [1, 2], componentSets: [["Point"], ["Label"]], repeatFirstStep: false }); expect(steps.map((step) => `${step.components.join()}@${step.satelliteCount}`)).toEqual(["Point@1", "Point@2", "Label@1", "Label@2"]); }); test("the clock is a real axis, and one value unless asked for", () => { const plain = buildPlan({ satelliteCounts: [1], componentSets: [["Point"]], repeatFirstStep: false }); expect(plain.map((step) => step.clockMultiplier)).toEqual([1]); const swept = buildPlan({ satelliteCounts: [1, 2], componentSets: [["Point"]], clockMultipliers: [1, 100], repeatFirstStep: false }); expect(swept.map((step) => `${step.clockMultiplier}@${step.satelliteCount}`)).toEqual(["1@1", "1@2", "100@1", "100@2"]); }); test("the series names the clock only when it is not real time", () => { expect(formatSeries(["Point"], 1)).toBe("Point"); expect(formatSeries(["Point"], 100)).toBe("Point @ ×100"); }); test("the first step is re-run last, and marked", () => { const steps = buildPlan({ satelliteCounts: [10, 20], componentSets: [["Point"]] }); const last = steps[steps.length - 1] as BenchmarkStep; expect(steps).toHaveLength(3); expect(last.repeat).toBe(true); expect(last.satelliteCount).toBe(10); expect(steps.filter((step) => step.repeat)).toHaveLength(1); }); test("a lone step is not repeated — there would be nothing to compare it against", () => { expect(buildPlan({ satelliteCounts: [10], componentSets: [["Point"]] })).toHaveLength(1); }); test("the cumulative sets grow by one component and start from nothing", () => { expect(CUMULATIVE_COMPONENT_SETS[0]).toEqual([]); expect(CUMULATIVE_COMPONENT_SETS[1]).toEqual(["Point"]); for (const [index, set] of CUMULATIVE_COMPONENT_SETS.entries()) { expect(set).toHaveLength(index); } }); test("the isolated sets are Point plus exactly one other", () => { expect(ISOLATED_COMPONENT_SETS[0]).toEqual(["Point"]); for (const set of ISOLATED_COMPONENT_SETS.slice(1)) { expect(set[0]).toBe("Point"); expect(set).toHaveLength(2); } }); }); describe("scalingFits", () => { test("a linear cost is reported as its slope per 1,000 satellites", () => { const fits = scalingFits( run([ result({ sats: 0, components: ["Point"], cpuMs: 5 }), result({ sats: 500, components: ["Point"], cpuMs: 10 }), result({ sats: 1000, components: ["Point"], cpuMs: 15 }), ]), ); expect(fits).toHaveLength(1); expect(fits[0]).toMatchObject({ series: "Point", mainMsPer1000: 10, baseMainMs: 5, r2: 1 }); }); test("clock rates are separate series, never one averaged fit", () => { const fits = scalingFits( run([ result({ sats: 0, components: ["Point"], cpuMs: 5 }), result({ sats: 1000, components: ["Point"], cpuMs: 10 }), result({ sats: 0, components: ["Point"], clock: 100, cpuMs: 20 }), result({ sats: 1000, components: ["Point"], clock: 100, cpuMs: 60 }), ]), ); expect(fits.map((fit) => fit.series)).toEqual(["Point", "Point @ ×100"]); expect(fits[1]?.mainMsPer1000).toBe(40); }); test("the repeat step is left out, so it cannot weight one point twice", () => { const fits = scalingFits( run([ result({ sats: 0, components: ["Point"], cpuMs: 5 }), result({ sats: 1000, components: ["Point"], cpuMs: 15 }), result({ sats: 0, components: ["Point"], cpuMs: 500, repeat: true }), ]), ); expect(fits[0]).toMatchObject({ points: 2, mainMsPer1000: 10, baseMainMs: 5 }); }); // The defect: cpuMs excludes the clock tick, where most per-satellite work // happens, so fitting it alone had the Point series holding 60 fps into the // millions. The fit is over cpuMs + tickMs. test("the fit counts the clock tick, not just the render", () => { const fits = scalingFits(run([result({ sats: 0, components: ["Point"], cpuMs: 1, tickMs: 0 }), result({ sats: 1000, components: ["Point"], cpuMs: 1, tickMs: 10 })])); // cpuMs is flat across these two rows; all the growth is in the tick. expect(fits[0]?.mainMsPer1000).toBe(10); expect(fits[0]?.baseMainMs).toBe(1); }); test("satsAt60fps is blank when the floor alone has eaten the budget", () => { const fits = scalingFits(run([result({ sats: 0, components: ["Point"], cpuMs: 1, wallMs: 40 }), result({ sats: 1000, components: ["Point"], cpuMs: 2, wallMs: 42 })])); // Main-thread work is trivial and its slope would extrapolate to a huge // count, but every frame took 40 ms regardless — GPU or vsync bound. No // satellite count is the reason 60 fps is unavailable. expect(fits[0]?.floorMs).toBe(40); expect(fits[0]?.satsAt60fps).toBe(""); }); test("satsAt60fps is blank when the fixed cost already blows the budget", () => { const fits = scalingFits(run([result({ sats: 0, components: ["Point"], cpuMs: 30 }), result({ sats: 1000, components: ["Point"], cpuMs: 40 })])); expect(fits[0]?.satsAt60fps).toBe(""); }); }); describe("marginalCosts", () => { test("a set is differenced against the largest subset measured beside it", () => { const costs = marginalCosts( run([ result({ sats: 100, components: ["Point"], cpuMs: 10 }), result({ sats: 100, components: ["Point", "Label"], cpuMs: 14 }), result({ sats: 100, components: ["Point", "Label", "Orbit"], cpuMs: 20 }), ]), ); expect(costs).toEqual([ { sats: 100, clock: 1, added: "Label", over: "Point", deltaMainMs: 4, usPerSatellite: 40 }, { sats: 100, clock: 1, added: "Orbit", over: "Point + Label", deltaMainMs: 6, usPerSatellite: 60 }, ]); }); test("rows under different clocks are never differenced against each other", () => { const costs = marginalCosts(run([result({ sats: 100, components: ["Point"], cpuMs: 10 }), result({ sats: 100, components: ["Point", "Label"], clock: 100, cpuMs: 90 })])); expect(costs).toEqual([]); }); test("a set with no subset beside it yields no row rather than a bogus baseline", () => { const costs = marginalCosts(run([result({ sats: 100, components: ["Label"], cpuMs: 10 }), result({ sats: 100, components: ["Orbit"], cpuMs: 20 })])); expect(costs).toEqual([]); }); }); describe("propagationCosts", () => { test("each rate is differenced against x1 for the same scene", () => { const costs = propagationCosts( run([ result({ sats: 200, components: ["Point"], cpuMs: 1, tickMs: 10 }), result({ sats: 200, components: ["Point"], clock: 100, cpuMs: 1, tickMs: 30 }), result({ sats: 200, components: ["Point"], clock: 1000, cpuMs: 1, tickMs: 110 }), ]), ); expect(costs.map((cost) => [cost.clock, cost.deltaTickMs, cost.usPerSatellite])).toEqual([ [100, 20, 100], [1000, 100, 500], ]); }); // The defect this table was rewritten for: propagation happens in clock.onTick, // which runs before preUpdate and so is outside cpuMs entirely. Differencing // cpuMs reported nothing for a step that had ground to a halt propagating. test("sees a cost that cpuMs cannot", () => { const costs = propagationCosts( run([result({ sats: 5000, components: ["Point"], cpuMs: 1.28, tickMs: 0.2 }), result({ sats: 5000, components: ["Point"], clock: 10000, cpuMs: 1.2, tickMs: 460 })]), ); // cpuMs went *down* between these two rows; the tick went up by 460 ms. expect(costs).toHaveLength(1); expect(costs[0]?.deltaTickMs).toBeCloseTo(459.8, 1); expect(costs[0]?.usPerSatellite).toBeGreaterThan(90); expect(costs[0]?.cpuMs).toBeLessThan(costs[0]?.tickMs ?? 0); }); test("without a x1 row there is nothing to difference against", () => { const costs = propagationCosts( run([result({ sats: 200, components: ["Point"], clock: 100, cpuMs: 1, tickMs: 30 }), result({ sats: 200, components: ["Point"], clock: 1000, cpuMs: 1, tickMs: 110 })]), ); expect(costs).toEqual([]); }); test("a run that never swept the clock reports nothing at all", () => { expect(propagationCosts(run([result({ sats: 200, components: ["Point"], cpuMs: 10 })]))).toEqual([]); }); }); describe("repeatChecks", () => { test("the repeat is matched to its original and reported as drift", () => { const checks = repeatChecks( run([ result({ sats: 100, components: ["Point"], cpuMs: 10, buildMs: 800 }), result({ sats: 500, components: ["Point"], cpuMs: 20, buildMs: 100 }), result({ sats: 100, components: ["Point"], cpuMs: 11, buildMs: 200, repeat: true }), ]), ); expect(checks).toHaveLength(1); expect(checks[0]).toMatchObject({ sats: 100, firstMainMs: 10, repeatMainMs: 11, mainDriftPct: 10, firstBuildMs: 800, repeatBuildMs: 200, buildDriftPct: -75 }); }); test("drift is only untrustworthy when it is both proportional and material", () => { const check = (firstMainMs: number, repeatMainMs: number): RepeatCheck => ({ sats: 0, components: "Point", clock: 1, firstMainMs, repeatMainMs, mainDriftPct: ((repeatMainMs - firstMainMs) / firstMainMs) * 100, firstBuildMs: 0, repeatBuildMs: 0, buildDriftPct: 0, }); // A fifth of a millisecond on a two-millisecond control step. This is what a // clean run looks like at the bottom of the range, and it used to warn. expect(isDrifted(check(2.2, 1.79))).toBe(false); // The same proportion where it is worth milliseconds. expect(isDrifted(check(11, 18))).toBe(true); // Material but proportionally small: a big scene that moved a little. expect(isDrifted(check(200, 202))).toBe(false); }); test("a run without a repeat step reports nothing", () => { expect(repeatChecks(run([result({ sats: 100, components: ["Point"], cpuMs: 10 })]))).toEqual([]); }); }); describe("gpu timing", () => { test("a plausible GPU time is reported", () => { const rows = reportRows(run([result({ sats: 0, components: ["Point"], cpuMs: 1, gpuMs: 9, wallMs: 14 })])); expect(rows[0]?.gpuMs).toBe(9); }); test("a GPU-bound scene, where gpu approaches the frame interval, is still believed", () => { const rows = reportRows(run([result({ sats: 0, components: ["Point"], cpuMs: 1, gpuMs: 13.5, wallMs: 14 })])); expect(rows[0]?.gpuMs).toBe(13.5); }); test("a timer claiming more GPU than the frame it presented in is withheld", () => { // The ANGLE/Metal case: 49 ms of "GPU" against frames arriving every 14 ms. // A frame that presented cannot have cost that, so the column goes blank // rather than print it. const bad = run([result({ sats: 0, components: ["Point"], cpuMs: 1, gpuMs: 49, wallMs: 14 })]); expect(gpuTimerTrustworthy(bad)).toBe(false); expect(reportRows(bad)[0]?.gpuMs).toBe(""); }); test("one odd step does not discredit a run, a majority does", () => { const mostlyFine = run([ result({ sats: 0, components: ["Point"], cpuMs: 1, gpuMs: 9, wallMs: 14 }), result({ sats: 1, components: ["Point"], cpuMs: 1, gpuMs: 9, wallMs: 14 }), result({ sats: 2, components: ["Point"], cpuMs: 1, gpuMs: 60, wallMs: 14 }), ]); expect(gpuTimerTrustworthy(mostlyFine)).toBe(true); const mostlyBad = run([ result({ sats: 0, components: ["Point"], cpuMs: 1, gpuMs: 60, wallMs: 14 }), result({ sats: 1, components: ["Point"], cpuMs: 1, gpuMs: 60, wallMs: 14 }), result({ sats: 2, components: ["Point"], cpuMs: 1, gpuMs: 9, wallMs: 14 }), ]); expect(gpuTimerTrustworthy(mostlyBad)).toBe(false); }); test("no GPU samples at all is blank, not zero", () => { const none = run([result({ sats: 0, components: ["Point"], cpuMs: 1 })]); expect(gpuTimerTrustworthy(none)).toBe(false); expect(reportRows(none)[0]?.gpuMs).toBe(""); }); test("the sampler keeps GPU timings as their own population", () => { const sampler = new FrameSampler(); sampler.push(0); sampler.push(10, 1); sampler.push(20, 1); // Two frames, one GPU result — they arrive out of step and are not paired. sampler.pushGpu(8); const snap = sampler.snapshot(); expect(snap.frames).toBe(2); expect(snap.gpu?.count).toBe(1); expect(snap.gpu?.mean).toBe(8); }); test("reset clears GPU timings with everything else", () => { const sampler = new FrameSampler(); sampler.pushGpu(8); sampler.reset(); expect(sampler.snapshot().gpu).toBeUndefined(); }); }); describe("thinRows", () => { test("rows sampled over too few frames are the ones flagged", () => { const thin = thinRows(run([result({ sats: 0, components: ["Point"], cpuMs: 1, frames: 3 }), result({ sats: 1, components: ["Point"], cpuMs: 1, frames: 300 })])); expect(thin.map((row) => row.frames)).toEqual([3]); }); }); describe("toCsv", () => { test("the header names every column of the row beneath it", () => { const csv = toCsv(run([result({ sats: 1, components: ["Point", "Label"], cpuMs: 1 })])); const [header, row] = csv.split("\n"); expect(header?.split(",")).toContain("components"); expect(row?.split(",")).toHaveLength(header?.split(",").length ?? 0); }); test("a value carrying a comma is quoted rather than left to split the row", () => { // Component names must not contain commas (see CONTEXT.md), so this is the // quoting being defensive rather than a case the app can reach — and the row // staying one field is the whole point of it. const csv = toCsv(run([result({ sats: 1, components: ["Point", "A,B"], cpuMs: 1 })])); const [header, row] = csv.split("\n"); expect(row).toContain('"Point + A,B"'); expect(row?.split(",")).toHaveLength((header?.split(",").length ?? 0) + 1); }); test("an empty run is an empty string rather than a lone header", () => { expect(toCsv(run([]))).toBe(""); }); }); describe("memoryFits", () => { // The whole reason this is a slope and not a column: the garbage offset is // common to the rows of one series, so it lands in the intercept. These two // series carry wildly different offsets (+400 MB apart) over the same // per-satellite cost, and must produce the same slope. test("the garbage offset lands in the intercept, not the slope", () => { const clean = run([ result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [40] }), result({ sats: 1000, components: ["Point"], cpuMs: 1, visible: 1000, heapMb: [93] }), result({ sats: 5000, components: ["Point"], cpuMs: 1, visible: 5000, heapMb: [310] }), ]); const dirty = run([ result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [440] }), result({ sats: 1000, components: ["Point"], cpuMs: 1, visible: 1000, heapMb: [493] }), result({ sats: 5000, components: ["Point"], cpuMs: 1, visible: 5000, heapMb: [710] }), ]); expect(memoryFits(clean)[0]?.mbPer1000Sats).toBe(memoryFits(dirty)[0]?.mbPer1000Sats); expect(memoryFits(clean)[0]?.baseMb).not.toBe(memoryFits(dirty)[0]?.baseMb); }); test("reports the slope per 1,000 and per satellite", () => { const fits = memoryFits( run([ result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [40] }), result({ sats: 5000, components: ["Point"], cpuMs: 1, visible: 5000, heapMb: [40 + 250] }), ]), ); expect(fits[0]?.mbPer1000Sats).toBe(50); // 50 MB per 1,000 is 51.2 KB each, not 50 — the unit change is binary. expect(fits[0]?.kbPerSatellite).toBe(51.2); expect(fits[0]?.baseMb).toBe(40); }); test("fits against satellites drawn, not the count asked for", () => { const fits = memoryFits( run([ result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [10] }), // Only half applied, so the same heap growth is twice the cost each. result({ sats: 1000, components: ["Point"], cpuMs: 1, visible: 500, heapMb: [60] }), ]), ); expect(fits[0]?.mbPer1000Sats).toBe(100); }); test("clock rates are separate series, as everywhere else", () => { const fits = memoryFits( run([ result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [10], clock: 1 }), result({ sats: 1000, components: ["Point"], cpuMs: 1, visible: 1000, heapMb: [60], clock: 1 }), result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [10], clock: 100 }), result({ sats: 1000, components: ["Point"], cpuMs: 1, visible: 1000, heapMb: [110], clock: 100 }), ]), ); expect(fits.map((fit) => fit.mbPer1000Sats)).toEqual([50, 100]); }); // The failure this fit actually has, caught in the wild: a major collection fell // between the zero row and the next, so the offset stopped being common and the // slope came out negative — memory apparently freed by drawing satellites. r² is // the only thing standing between that and being read as a finding. test("a collection landing mid-series shows up as scatter, not a plausible slope", () => { const fits = memoryFits( run([ result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [419.2] }), result({ sats: 1000, components: ["Point"], cpuMs: 1, visible: 1001, heapMb: [100.8] }), result({ sats: 5000, components: ["Point"], cpuMs: 1, visible: 5001, heapMb: [299.9] }), ]), ); expect(fits[0]?.mbPer1000Sats).toBeLessThan(0); expect(memoryFitTrustworthy(fits[0]!)).toBe(false); }); test("a clean series clears the r² guard comfortably", () => { const fits = memoryFits( run([ result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [50.7] }), result({ sats: 1000, components: ["Point"], cpuMs: 1, visible: 1001, heapMb: [95.4] }), result({ sats: 5000, components: ["Point"], cpuMs: 1, visible: 5001, heapMb: [310.5] }), ]), ); // The real measurement this is taken from: 53.7 KB per satellite against a // forced-collection truth of 52.5. expect(fits[0]?.kbPerSatellite).toBeCloseTo(53.7, 0); expect(memoryFitTrustworthy(fits[0]!)).toBe(true); }); // Two points always lie on their own line, so r² alone would wave this through // at 1.000 — which is precisely where the offset assumption is least tested. test("a two-count sweep is refused however well it fits", () => { const fits = memoryFits( run([result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [53] }), result({ sats: 100, components: ["Point"], cpuMs: 1, visible: 100, heapMb: [73.4] })]), ); expect(fits[0]?.r2).toBe(1); expect(memoryFitTrustworthy(fits[0]!)).toBe(false); }); test("no heap reading means no rows, rather than a fit through zeroes", () => { // A fit through absent data would report every satellite as free, which is // worse than an empty table saying the browser cannot answer. const fits = memoryFits(run([result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0 }), result({ sats: 5000, components: ["Point"], cpuMs: 1, visible: 5000 })])); expect(fits).toEqual([]); }); test("the repeat step is excluded, as it is from every other fit", () => { const fits = memoryFits( run([ result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [10] }), result({ sats: 1000, components: ["Point"], cpuMs: 1, visible: 1000, heapMb: [60] }), result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [500], repeat: true }), ]), ); expect(fits[0]?.points).toBe(2); expect(fits[0]?.mbPer1000Sats).toBe(50); }); }); describe("the footprint capture", () => { const withFootprints = (points: readonly { visible: number; jsMb: number; floorMb: number }[]) => run(points.map((point) => result({ sats: point.visible, visible: point.visible, components: ["Point"], cpuMs: 1, heapMb: [point.floorMb], footprintMb: point.jsMb }))); // The two slopes are independent derivations of one quantity: one differenced // out of garbage-contaminated floors, one measured after a collection. That is // the whole reason both are reported. test("the measured slope sits beside the derived one", () => { const fits = memoryFits( withFootprints([ { visible: 0, jsMb: 30.2, floorMb: 50.7 }, { visible: 1001, jsMb: 82.8, floorMb: 95.4 }, { visible: 5001, jsMb: 287.7, floorMb: 310.5 }, ]), ); // Both land near the 52.5 KB per satellite a forced collection reported. expect(fits[0]?.kbPerSatellite).toBeCloseTo(53.7, 0); expect(fits[0]?.absoluteKbPerSatellite).toBeCloseTo(52.7, 0); // Three captures for three counts, so the absolute slope stands on its own. expect(absoluteFitTrustworthy(fits[0]!)).toBe(true); }); test("blank where the run never captured one, rather than zero", () => { const fits = memoryFits( run([result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [10] }), result({ sats: 1000, components: ["Point"], cpuMs: 1, visible: 1000, heapMb: [60] })]), ); expect(fits[0]?.absoluteKbPerSatellite).toBeUndefined(); expect(reportRows(run([result({ sats: 0, components: ["Point"], cpuMs: 1 })]))[0]?.footprintMb).toBe(""); }); // The review finding: `measureFootprint` can be refused for one step, which // leaves the absolute fit two points wide while the floor fit beside it still has // three. Borrowing the floor fit's r² would print that under a green guard. test("a refused capture is judged on its own points, not the floor fit's", () => { const fits = memoryFits( run([ result({ sats: 0, components: ["Point"], cpuMs: 1, visible: 0, heapMb: [50.7], footprintMb: 30.2 }), // This step's capture was refused: heap floor present, footprint absent. result({ sats: 1000, components: ["Point"], cpuMs: 1, visible: 1001, heapMb: [95.4] }), result({ sats: 5000, components: ["Point"], cpuMs: 1, visible: 5001, heapMb: [310.5], footprintMb: 287.7 }), ]), ); // The floor fit is fine — three points, tight. expect(memoryFitTrustworthy(fits[0]!)).toBe(true); // The absolute one is not, and says so rather than borrowing that verdict. expect(fits[0]?.absolutePoints).toBe(2); expect(fits[0]?.absoluteKbPerSatellite).not.toBeUndefined(); expect(absoluteFitTrustworthy(fits[0]!)).toBe(false); }); test("hasFootprints is the one predicate both readers use", () => { expect(hasFootprints(run([result({ sats: 0, components: ["Point"], cpuMs: 1, heapMb: [40] })]))).toBe(false); expect(hasFootprints(run([result({ sats: 0, components: ["Point"], cpuMs: 1, heapMb: [40], footprintMb: 30 })]))).toBe(true); }); test("the absolute figures reach the rows, total and js apart", () => { const rows = reportRows(withFootprints([{ visible: 5001, jsMb: 297.4, floorMb: 310.5 }])); expect(rows[0]?.footprintMb).toBe(297.4); // The total counts DOM and worker memory too, so it is the larger number. expect(Number(rows[0]?.footprintTotalMb)).toBeGreaterThan(297.4); }); // Same rule the gpu column follows: a column of dashes says nothing an absent // column does not. test("the pasted table grows a memory column only when one was captured", () => { expect(formatTable(withFootprints([{ visible: 0, jsMb: 30, floorMb: 50 }])).split("\n")[0]).toContain("footprint"); expect(formatTable(run([result({ sats: 0, components: ["Point"], cpuMs: 1 })])).split("\n")[0]).not.toContain("footprint"); }); }); describe("estimateDurationMs with a footprint capture", () => { test("the capture is added per step, not amortised", () => { const steps = buildPlan({ satelliteCounts: [0, 100, 500], componentSets: [["Point"]], repeatFirstStep: false }); const plain = estimateDurationMs(steps, 6000); const withCapture = estimateDurationMs(steps, 6000, 17_000); expect(withCapture - plain).toBe(steps.length * 17_000); }); test("no capture leaves the estimate exactly as it was", () => { const steps = buildPlan({ satelliteCounts: [0, 100], componentSets: [["Point"]], repeatFirstStep: false }); expect(estimateDurationMs(steps, 6000, 0)).toBe(estimateDurationMs(steps, 6000)); }); }); describe("the raw heap columns", () => { test("stay out of the pasted table but remain in the csv", () => { const one = run([result({ sats: 0, components: ["Point"], cpuMs: 1, heapMb: [40, 50] })]); expect(formatTable(one).split("\n")[0]).not.toContain("heap"); expect(toCsv(one).split("\n")[0]?.split(",")).toEqual(expect.arrayContaining(["heapMb", "heapPeakMb"])); }); test("carry the window's floor and peak, not an average", () => { const rows = reportRows(run([result({ sats: 5000, components: ["Point"], cpuMs: 1, heapMb: [413, 462.2, 86.3, 300] })])); expect(rows[0]?.heapMb).toBe(86.3); expect(rows[0]?.heapPeakMb).toBe(462.2); }); test("blank on a browser that offers no reading, rather than zero", () => { const rows = reportRows(run([result({ sats: 0, components: ["Point"], cpuMs: 1 })])); expect(rows[0]?.heapMb).toBe(""); expect(rows[0]?.heapPeakMb).toBe(""); }); }); describe("FrameSampler heap series", () => { test("a frame without a reading is still a frame", () => { const sampler = new FrameSampler(); sampler.push(0); sampler.push(10); sampler.push(20); expect(sampler.snapshot().frames).toBe(2); expect(sampler.snapshot().heap).toBeUndefined(); }); test("the heap is its own population, min and max over the window", () => { const sampler = new FrameSampler(); sampler.push(0); sampler.push(10); sampler.pushHeap(413); sampler.pushHeap(86.3); sampler.pushHeap(462.2); const heap = sampler.snapshot().heap; expect(heap?.min).toBe(86.3); expect(heap?.max).toBe(462.2); expect(heap?.count).toBe(3); }); test("reset drops the heap samples with the rest of the warmup", () => { const sampler = new FrameSampler(); sampler.pushHeap(999); sampler.reset(); sampler.pushHeap(40); expect(sampler.snapshot().heap?.max).toBe(40); }); test("a limit rolls the heap window too, so the live readout stays recent", () => { const sampler = new FrameSampler(2); sampler.pushHeap(900); sampler.pushHeap(41); sampler.pushHeap(42); expect(sampler.snapshot().heap?.max).toBe(42); }); });