From 21fa8d0a3c4b90a0ca2b69a9c2c41d8c51f58429 Mon Sep 17 00:00:00 2001 From: "Jonathan D. Rhyne" Date: Mon, 26 Jan 2026 20:28:21 -0500 Subject: [PATCH] feat(report): add latency percentiles Include p50/p95/min/max latency metrics in benchmark reports and clarify notes. --- src/commands/report.ts | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/commands/report.ts b/src/commands/report.ts index f206877..6b2a942 100644 --- a/src/commands/report.ts +++ b/src/commands/report.ts @@ -17,6 +17,7 @@ type BenchmarkResults = { }>; summary?: { avgLatencyMs?: number; + errorCount?: number; }; warnings?: string[]; }; @@ -71,6 +72,20 @@ const ensureOutputPath = async (filePath: string): Promise => { const isFiniteNumber = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value); +const percentile = (values: number[], p: number): number | undefined => { + if (values.length === 0) { + return undefined; + } + const sorted = [...values].sort((a, b) => a - b); + const index = (sorted.length - 1) * p; + const lower = Math.floor(index); + const upper = Math.ceil(index); + if (lower === upper) { + return sorted[lower]; + } + return sorted[lower] + (sorted[upper] - sorted[lower]) * (index - lower); +}; + export const registerReportCommand = (program: Command) => { program .command("report") @@ -111,8 +126,25 @@ export const registerReportCommand = (program: Command) => { : isFiniteNumber(parsed.summary?.avgLatencyMs) ? parsed.summary?.avgLatencyMs : undefined; + const p50Latency = percentile(latencies, 0.5); + const p95Latency = percentile(latencies, 0.95); + const minLatency = latencies.length > 0 ? Math.min(...latencies) : undefined; + const maxLatency = latencies.length > 0 ? Math.max(...latencies) : undefined; const avgLatencyText = typeof avgLatency === "number" ? avgLatency.toFixed(2) : "N/A"; + const p50LatencyText = + typeof p50Latency === "number" ? p50Latency.toFixed(2) : "N/A"; + const p95LatencyText = + typeof p95Latency === "number" ? p95Latency.toFixed(2) : "N/A"; + const minLatencyText = + typeof minLatency === "number" ? minLatency.toFixed(2) : "N/A"; + const maxLatencyText = + typeof maxLatency === "number" ? maxLatency.toFixed(2) : "N/A"; + const errorCount = isFiniteNumber(parsed.summary?.errorCount) + ? parsed.summary?.errorCount + : undefined; + const errorCountText = + typeof errorCount === "number" ? String(errorCount) : "N/A"; const status = parsed.status ? parsed.status : "unknown"; const warnings = Array.isArray(parsed.warnings) ? parsed.warnings : []; @@ -124,13 +156,18 @@ export const registerReportCommand = (program: Command) => { `- Suite: ${suite}`, `- Runs: ${runCount}`, `- Avg latency (ms): ${avgLatencyText}`, + `- p50 latency (ms): ${p50LatencyText}`, + `- p95 latency (ms): ${p95LatencyText}`, + `- Min latency (ms): ${minLatencyText}`, + `- Max latency (ms): ${maxLatencyText}`, + `- Errors: ${errorCountText}`, "", warnings.length > 0 ? "## Warnings" : "", ...warnings.map((warning) => `- ${warning}`), warnings.length > 0 ? "" : "", "## Notes", - "- If this report is marked as stub, replace placeholder metrics with real benchmark output.", - "- Re-run `dws benchmark --suite ` after integrating the DWS API.", + "- Generated from a single results file; combine baseline/deterministic reports externally if needed.", + "- Re-run `dws benchmark --suite ` after environment changes to keep results current.", "", ].filter((line) => line !== "");