From 62ba3f69158d708535115d49addac6c6ea0fa782 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Sat, 28 Feb 2026 15:24:46 -0800 Subject: [PATCH] fix(perf): read overhead from captured headers map, show decimal ms + % in status bar --- .../PerformanceDashboardView.tsx | 71 ++++++++++++------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/ui/litellm-dashboard/src/components/PerformanceDashboard/PerformanceDashboardView.tsx b/ui/litellm-dashboard/src/components/PerformanceDashboard/PerformanceDashboardView.tsx index 9823661f195..f6a63a54e3a 100644 --- a/ui/litellm-dashboard/src/components/PerformanceDashboard/PerformanceDashboardView.tsx +++ b/ui/litellm-dashboard/src/components/PerformanceDashboard/PerformanceDashboardView.tsx @@ -28,8 +28,9 @@ interface HistoryPoint { interface TestResult { status: number; - overheadMs: number | null; - wallMs: number; + overheadMs: number | null; // raw float from x-litellm-overhead-duration-ms + wallMs: number; // client-side wall clock ms + proxyTotalMs: number; // proxy-measured total (x-litellm-response-duration-ms) or fallback to wallMs model: string; responseText: string; headers: Record; @@ -56,14 +57,21 @@ async function runTestRequest(accessToken: string, model: string, messages: { ro }); const wallMs = Date.now() - start; const responseText = await resp.text(); + // Capture headers first via forEach (most reliable cross-browser method for custom headers) const captured: Record = {}; resp.headers.forEach((v, k) => { if (k.startsWith("x-litellm")) captured[k] = v; }); - const overheadRaw = resp.headers.get("x-litellm-overhead-duration-ms"); + // Read overhead from captured map — forEach and get() can behave differently for exposed headers + const overheadRaw = captured["x-litellm-overhead-duration-ms"] ?? null; const overheadParsed = overheadRaw && overheadRaw !== "None" ? parseFloat(overheadRaw) : NaN; + const responseDurationRaw = captured["x-litellm-response-duration-ms"] ?? null; + const responseDurationParsed = responseDurationRaw && responseDurationRaw !== "None" ? parseFloat(responseDurationRaw) : NaN; + // Prefer the proxy's measured total over wall clock (avoids network jitter inflating LLM API time) + const proxyTotalMs = isNaN(responseDurationParsed) ? wallMs : Math.round(responseDurationParsed); return { status: resp.status, - overheadMs: isNaN(overheadParsed) ? null : Math.round(overheadParsed), + overheadMs: isNaN(overheadParsed) ? null : parseFloat(overheadRaw!), wallMs, + proxyTotalMs, model, responseText, headers: captured, @@ -332,9 +340,17 @@ export default function PerformanceDashboardView() { ); - const llmApiMs = testResult ? Math.max(0, testResult.wallMs - (testResult.overheadMs ?? 0)) : 0; - const overheadPctTest = testResult && testResult.overheadMs != null - ? Math.round((testResult.overheadMs / testResult.wallMs) * 100) + // Use proxy-measured total for waterfall so LLM API bar = proxyTotal - overhead (no network noise) + const waterfallTotal = testResult?.proxyTotalMs ?? 0; + const llmApiMs = testResult ? Math.max(0, waterfallTotal - (testResult.overheadMs ?? 0)) : 0; + const overheadPctTest = testResult && testResult.overheadMs != null && waterfallTotal > 0 + ? parseFloat(((testResult.overheadMs / waterfallTotal) * 100).toFixed(1)) + : null; + // Display string: show 1 decimal for sub-10ms, otherwise round + const overheadDisplay = testResult?.overheadMs != null + ? testResult.overheadMs < 10 + ? `${testResult.overheadMs.toFixed(2)}ms` + : `${Math.round(testResult.overheadMs)}ms` : null; let parsedContent = ""; @@ -419,13 +435,13 @@ export default function PerformanceDashboardView() {
Total - {testResult.wallMs}ms + {testResult.proxyTotalMs}ms
20 ? "bg-orange-50" : "bg-white"}`}> LiteLLM Overhead - {testResult.overheadMs !== null ? ( - 20 ? "text-orange-500" : "text-gray-800"}`}> - {testResult.overheadMs}ms + {overheadDisplay !== null ? ( + 20 ? "text-orange-500" : "text-blue-600"}`}> + {overheadDisplay} ) : ( @@ -436,7 +452,7 @@ export default function PerformanceDashboardView() { {overheadPctTest !== null && testResult.overheadMs !== null && (
20 ? "bg-orange-50" : "bg-white"}`}> Overhead % - 20 ? "text-orange-500" : "text-green-600"}`}> + 20 ? "text-orange-500" : "text-blue-600"}`}> {overheadPctTest}%
@@ -463,7 +479,7 @@ export default function PerformanceDashboardView() { transform: pct === 0 ? "none" : pct === 100 ? "translateX(-100%)" : "translateX(-50%)", }} > - {Math.round((pct / 100) * testResult.wallMs)}ms + {Math.round((pct / 100) * waterfallTotal)}ms
))}
@@ -478,7 +494,7 @@ export default function PerformanceDashboardView() { label="LiteLLM Processing" startMs={0} durationMs={testResult.overheadMs ?? 0} - totalMs={testResult.wallMs} + totalMs={waterfallTotal} color={overheadPctTest != null && overheadPctTest > 20 ? "bg-orange-400" : "bg-blue-500"} tooltip="Time LiteLLM spends on auth, routing, request transformation, and logging — excludes time waiting for the LLM API to respond." /> @@ -486,7 +502,7 @@ export default function PerformanceDashboardView() { label="LLM API (waiting)" startMs={testResult.overheadMs ?? 0} durationMs={llmApiMs} - totalMs={testResult.wallMs} + totalMs={waterfallTotal} color="bg-teal-400" /> {/* Total footer row */} @@ -500,24 +516,25 @@ export default function PerformanceDashboardView() {
- {testResult.wallMs}ms + {testResult.proxyTotalMs}ms - {overheadPctTest != null && ( -

- LiteLLM overhead:{" "} - 20 ? "text-orange-500 font-medium" : "text-gray-600 font-medium"}> - {overheadPctTest}% of total +

+ {overheadPctTest != null ? ( + + LiteLLM overhead:{" "} + 20 ? "text-orange-500" : "text-blue-600"}`}> + {overheadDisplay} ({overheadPctTest}% of {testResult.proxyTotalMs}ms) + + {overheadPctTest > 20 && — higher than expected} - {overheadPctTest > 20 && — higher than expected} - {testResult.overheadMs === null && ( - — overhead header not returned (check proxy version) - )} -

- )} + ) : testResult.overheadMs === null ? ( + Overhead header not returned — check proxy version + ) : null} +
{/* Response content */}