From 6a3814757f117da0e6820fc1b8fc718dbb7fe7d5 Mon Sep 17 00:00:00 2001 From: Matthew Valancy Date: Fri, 19 Jun 2026 13:46:06 -0700 Subject: [PATCH] fix(dashboard): drop stale -1 not-measured sentinels from served metric trends Ingestion (metrics.mjs) already filters -1 sentinels, but the append-only metrics.jsonl retains points written before that fix landed, so the served state still carried -1 for graph.driftPx/avgTickMs. A -1 on a lower-is-better metric reads as a perfect score and distorts the trend chart. Add a liveMetrics() guard at the serve layer dropping non-finite/negative values (every GraphDone metric is non-negative). Dedup-on-append still reads raw. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/lib/dashboard/dashboard.test.mjs | 15 ++++++++++++++- tests/lib/dashboard/history.mjs | 12 ++++++++++++ tests/lib/dashboard/server.mjs | 4 ++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/tests/lib/dashboard/dashboard.test.mjs b/tests/lib/dashboard/dashboard.test.mjs index 93a607ec..9b573baa 100644 --- a/tests/lib/dashboard/dashboard.test.mjs +++ b/tests/lib/dashboard/dashboard.test.mjs @@ -8,7 +8,7 @@ import { rollupStatus, fmtDuration, fmtBytes, pct, decodeMungedName, esc, STATUS import { niceMax, bounds, lineChart, statusBar, sparkline } from './charts.mjs'; import { isUnifiedReport, runIdFor, summarize, mediaCount, safeId } from './ingest.mjs'; import { reportMetrics, scaleSweepMetrics, largeGraphMetrics, physicsMetrics, vlmMetrics, pointKey } from './metrics.mjs'; -import { mergeRuns, mergeMetrics, readJsonl, appendJsonl, snapshotRun, pruneSnapshots, pickLatest } from './history.mjs'; +import { mergeRuns, mergeMetrics, liveMetrics, readJsonl, appendJsonl, snapshotRun, pruneSnapshots, pickLatest } from './history.mjs'; // ── format ──────────────────────────────────────────────────────────────── test('rollupStatus: worst-of precedence', () => { @@ -213,6 +213,19 @@ test('mergeMetrics dedupes by key', () => { assert.equal(merged.length, 2); assert.deepEqual(added.map((m) => m.key), ['b']); }); +test('liveMetrics drops stale -1 sentinels and non-finite values from stored history', () => { + const m = liveMetrics([ + { metric: 'graph.driftPx', value: -1 }, + { metric: 'graph.avgTickMs', value: -1 }, + { metric: 'graph.driftPx', value: 85.77 }, + { metric: 'graph.idleFps', value: 0 }, + { metric: 'graph.loadMs', value: NaN }, + { metric: 'suite.passRate', value: 100 }, + null, + ]); + assert.equal(m.length, 3); + assert.deepEqual(m.map((x) => x.value), [85.77, 0, 100]); +}); // ── history fs round-trip ─────────────────────────────────────────────────── test('jsonl append/read round-trip tolerates blank lines', () => { diff --git a/tests/lib/dashboard/history.mjs b/tests/lib/dashboard/history.mjs index 707f256c..11efbef2 100644 --- a/tests/lib/dashboard/history.mjs +++ b/tests/lib/dashboard/history.mjs @@ -36,6 +36,18 @@ export function pickLatest(runs) { return (available || runs[0]).runId; } +/** + * Drop not-measured sentinels from a stored metric series before it reaches the + * charts. Ingestion (metrics.mjs) already filters -1 sentinels, but the + * append-only metrics.jsonl retains points written before that fix landed; a -1 + * on a lower-is-better metric (driftPx, avgTickMs) otherwise reads as a perfect + * score and distorts the trend. Every GraphDone metric is non-negative, so a + * non-finite or negative value is always a sentinel. + */ +export function liveMetrics(metrics) { + return metrics.filter((m) => m && typeof m.value === 'number' && isFinite(m.value) && m.value >= 0); +} + export function mergeMetrics(existing, incoming) { const seen = new Set(existing.map((m) => m.key)); const added = []; diff --git a/tests/lib/dashboard/server.mjs b/tests/lib/dashboard/server.mjs index ee970c82..11435c2a 100644 --- a/tests/lib/dashboard/server.mjs +++ b/tests/lib/dashboard/server.mjs @@ -16,7 +16,7 @@ import { resolve, join, sep, extname } from 'node:path'; import { spawn } from 'node:child_process'; import { discover, signature, safeId, summarize } from './ingest.mjs'; import { reportMetrics, scanPerfArtifacts } from './metrics.mjs'; -import { mergeRuns, mergeMetrics, readJsonl, appendJsonl, snapshotRun, pruneSnapshots, pickLatest } from './history.mjs'; +import { mergeRuns, mergeMetrics, liveMetrics, readJsonl, appendJsonl, snapshotRun, pruneSnapshots, pickLatest } from './history.mjs'; import { renderShell } from './page.mjs'; const args = process.argv.slice(2); @@ -101,7 +101,7 @@ function reindex() { if (existsSync(join(STORE, 'runs'))) pruneSnapshots(STORE, KEEP); - const allMetrics = readJsonl(METRICS_JSONL); + const allMetrics = liveMetrics(readJsonl(METRICS_JSONL)); state = { generatedAt: Date.now(), latest: pickLatest(merged),