fix(webapp): report start latency as unknown when there is no data (#4544)

When the health report had no start-latency measurement for the window,
it printed a confident "p95 0ms" and graded it healthy. It now shows
"unknown" for that metric and skips grading it, so an absent measurement
can't read as a green signal.

A genuinely measured 0ms is still shown as 0ms: the loader keeps "no
measurement" distinct from a measured zero instead of coercing both to
0.
This commit is contained in:
Katia Bulatova
2026-08-10 13:53:23 +02:00
committed by GitHub
parent 6e00aaf92b
commit c00fb9c36c
6 changed files with 133 additions and 17 deletions
@@ -0,0 +1,6 @@
---
area: webapp
type: fix
---
The health report now says start latency is "unknown" when there is no data for it, instead of showing a healthy-looking 0ms
@@ -21,7 +21,16 @@ export type HealthInput = {
* series measured (v2) or estimated (v1).
*/
pending: { now: number; normal?: number; series: number[]; estimated: boolean };
startLatency: { p95Ms: number; normalP95Ms: number; series: number[] };
/**
* p95 wait. `availability: "unknown"` = the source had no measurement, so `p95Ms` is a
* placeholder that must not be graded — a 0 would read as a confident green.
*/
startLatency: {
p95Ms: number;
normalP95Ms?: number;
series: number[];
availability?: "measured" | "unknown";
};
throughput: { donePerMin: number; triggeredPerMin: number; normalTriggeredPerMin: number };
failures: { rate: number; normalRate: number; series: number[] };
duration: { p95Ms: number; normalP95Ms: number };
@@ -116,21 +125,30 @@ export function buildMetrics(input: HealthInput): Metric[] {
const t = HEALTH_THRESHOLDS;
const ev = input.flowEvidence;
const startLatencyUnknown = input.startLatency.availability === "unknown";
const startLatency: Metric = {
id: "start_latency_p95",
value: input.startLatency.p95Ms,
availability: startLatencyUnknown ? "unknown" : "measured",
unit: "ms",
aggregation: "p95",
normal: input.startLatency.normalP95Ms,
delta: delta(input.startLatency.p95Ms, input.startLatency.normalP95Ms),
series: { points: input.startLatency.series, kind: "measured" },
severity: multiplierSeverity(
input.startLatency.p95Ms,
input.startLatency.normalP95Ms,
t.startLatency.warnMult,
t.startLatency.critMult,
t.startLatency.floor
),
normal: startLatencyUnknown ? undefined : input.startLatency.normalP95Ms,
delta: startLatencyUnknown
? undefined
: delta(input.startLatency.p95Ms, input.startLatency.normalP95Ms),
series: startLatencyUnknown
? undefined
: { points: input.startLatency.series, kind: "measured" },
// Nothing measured -> nothing to classify (a placeholder must never grade green).
severity: startLatencyUnknown
? "ok"
: multiplierSeverity(
input.startLatency.p95Ms,
input.startLatency.normalP95Ms,
t.startLatency.warnMult,
t.startLatency.critMult,
t.startLatency.floor
),
};
const pending: Metric = {
@@ -39,6 +39,12 @@ function num(value: unknown, fallback = 0): number {
return Number.isFinite(n) ? n : fallback;
}
/** Like `num`, but keeps "no measurement" distinct from a measured 0. */
function optionalNum(value: unknown): number | undefined {
const n = num(value, NaN);
return Number.isFinite(n) ? n : undefined;
}
function mean(xs: number[]): number {
return xs.length === 0 ? 0 : xs.reduce((a, b) => a + b, 0) / xs.length;
}
@@ -313,7 +319,7 @@ async function tryQuery(
export type FlowData = {
flowSource: HealthInput["flowSource"];
pending: { now: number; normal?: number; series: number[]; estimated: boolean };
startLatency: { p95Ms: number; normalP95Ms: number; series: number[] };
startLatency: HealthInput["startLatency"];
evidence: HealthInput["flowEvidence"];
/**
* Epoch ms of the freshest telemetry the source saw (latest env_metrics bucket and/or latest
@@ -432,6 +438,8 @@ function buildQueueMetricsFlow(
// env_metrics (still a real number) rather than a misleading confident zero (#7).
const lastMeasuredQueued = num(series[series.length - 1]?.queued);
const waitP95 = optionalNum(liveScalar.wait_p95);
return {
flowSource: "queue_metrics_v1",
pending: {
@@ -441,9 +449,10 @@ function buildQueueMetricsFlow(
estimated: false, // measured
},
startLatency: {
p95Ms: num(liveScalar.wait_p95),
normalP95Ms: num(baselineScalar.wait_p95),
p95Ms: waitP95 ?? 0,
normalP95Ms: optionalNum(baselineScalar.wait_p95),
series: resampleSeries(series.map((r) => num(r.wait_p95))),
availability: waitP95 === undefined ? "unknown" : "measured",
},
evidence: {
// native resolution — cause discriminators read shares off this series.
@@ -476,6 +485,7 @@ export const SnapshotFlowSource: FlowSource = {
return backlog;
});
const series = resampleSeries(proxy);
const startLatencyP95 = optionalNum(ctx.liveScalar.start_latency_p95);
return {
flowSource: "snapshot+runs",
@@ -488,9 +498,10 @@ export const SnapshotFlowSource: FlowSource = {
estimated: true,
},
startLatency: {
p95Ms: num(ctx.liveScalar.start_latency_p95),
normalP95Ms: num(ctx.baselineScalar.start_latency_p95),
p95Ms: startLatencyP95 ?? 0,
normalP95Ms: optionalNum(ctx.baselineScalar.start_latency_p95),
series: resampleSeries(ctx.liveSeries.map((r) => num(r.start_latency_p95))),
availability: startLatencyP95 === undefined ? "unknown" : "measured",
},
// No cause-tree evidence; interpret falls back to v1 symptoms.
evidence: EMPTY_EVIDENCE,
@@ -178,6 +178,8 @@ function annotationSegment(metric: Metric, vm: ReportViewModel): string {
}
function metricValueText(metric: Metric, msg: ReportMessages): string {
// No measurement -> say so; `value` is a placeholder, not a real reading.
if (metric.availability === "unknown") return "unknown";
// concurrency etc. carry a limit -> "running/limit".
if (metric.unit === "count" && metric.breakdown?.limit !== undefined) {
return `${fmtCount(metric.value)}/${fmtCount(metric.breakdown.limit)}`;
@@ -259,7 +261,9 @@ function compactFact(metric: Metric): string | undefined {
case "pending":
return `pending ${fmtCount(metric.value)}${metric.normal !== undefined ? ` (normal ~${fmtCount(metric.normal)})` : ""}`;
case "start_latency_p95":
return `starts p95 ${fmtDuration(metric.value)}`;
return metric.availability === "unknown"
? "starts p95 unknown"
: `starts p95 ${fmtDuration(metric.value)}`;
case "failures":
return `failures ${fmtPct(metric.value)}${metric.normal !== undefined ? ` (normal ~${fmtPct(metric.normal)})` : ""}`;
case "dur_p95":
+31
View File
@@ -495,6 +495,37 @@ describe("freshness unknown is distinct from lagging", () => {
});
});
describe("start latency with no measurement", () => {
const unknownInput: HealthInput = {
...INPUT_B,
startLatency: { p95Ms: 0, normalP95Ms: undefined, series: [], availability: "unknown" },
};
it("marks the metric 'unknown' and doesn't classify it", () => {
const metric = interpret(unknownInput).metrics.find((m) => m.id === "start_latency_p95")!;
expect(metric.availability).toBe("unknown");
expect(metric.severity).toBe("ok");
expect(metric.normal).toBeUndefined();
expect(metric.series).toBeUndefined(); // no sparkline for a placeholder
});
it("renders 'unknown', never a confident 0ms", () => {
const md = renderReportMarkdown(interpret(unknownInput));
expect(md).toContain("starts p95 unknown");
expect(md).not.toContain("starts p95 0ms");
});
it("keeps a genuine 0 a measured 0ms", () => {
const measured = interpret({
...INPUT_B,
startLatency: { p95Ms: 0, normalP95Ms: 7000, series: [0, 0], availability: "measured" },
});
const metric = measured.metrics.find((m) => m.id === "start_latency_p95")!;
expect(metric.availability).toBe("measured");
expect(renderReportMarkdown(measured)).toContain("starts p95 0ms");
});
});
describe("zero baseline is not a false green (absolute floors)", () => {
it("pending spiking from a 0 baseline is not healthy", () => {
const vm = interpret({
+46
View File
@@ -239,4 +239,50 @@ describe("loadHealthInput — orchestration (query seam)", () => {
expect(input.flowSource).toBe("queue_metrics_v1");
expect(input.pending.now).toBe(900);
});
it("no wait_p95 measurement -> start latency 'unknown', not a confident 0", async () => {
const input = await loadHealthInput(
fakeEnv,
"1h",
NOW,
makeDeps({
runs: RUNS_SCALAR,
envSeries: [{ t: "a", queued: 10, running: 5, throttled: 0 }],
envScalar: [{ wait_p95: null, avg_queued: 8, env_limit: 100 }],
})
);
expect(input.flowSource).toBe("queue_metrics_v1");
expect(input.startLatency.availability).toBe("unknown");
expect(input.startLatency.normalP95Ms).toBeUndefined();
});
it("a measured wait_p95 of 0 stays measured", async () => {
const input = await loadHealthInput(
fakeEnv,
"1h",
NOW,
makeDeps({
runs: RUNS_SCALAR,
envSeries: [{ t: "a", queued: 10, running: 5, throttled: 0, wait_p95: 0 }],
envScalar: [{ wait_p95: 0, avg_queued: 8, env_limit: 100 }],
})
);
expect(input.startLatency.availability).toBe("measured");
expect(input.startLatency.p95Ms).toBe(0);
});
it("snapshot path with no start_latency_p95 -> 'unknown'", async () => {
const input = await loadHealthInput(
fakeEnv,
"1h",
NOW,
makeDeps({
runs: [{ ...RUNS_SCALAR[0], start_latency_p95: null }],
runsSeries: [{ t: "a", triggered: 10, completed: 8, failures: 0 }],
envSeries: [],
})
);
expect(input.flowSource).toBe("snapshot+runs");
expect(input.startLatency.availability).toBe("unknown");
});
});