From ae33d72b01aaa80249c0c21e18481ea6c17c73d2 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Sat, 28 Feb 2026 15:16:16 -0800 Subject: [PATCH] ui(perf): OTEL-style waterfall with time axis, tooltip on overhead --- .../PerformanceDashboardView.tsx | 141 +++++++++++++----- 1 file changed, 101 insertions(+), 40 deletions(-) diff --git a/ui/litellm-dashboard/src/components/PerformanceDashboard/PerformanceDashboardView.tsx b/ui/litellm-dashboard/src/components/PerformanceDashboard/PerformanceDashboardView.tsx index 07f7082b27b..11d9e29d85d 100644 --- a/ui/litellm-dashboard/src/components/PerformanceDashboard/PerformanceDashboardView.tsx +++ b/ui/litellm-dashboard/src/components/PerformanceDashboard/PerformanceDashboardView.tsx @@ -1,11 +1,12 @@ "use client"; import React, { useCallback, useRef, useState, useEffect } from "react"; -import { Collapse, Select, Spin, Tabs, Tag } from "antd"; +import { Collapse, Select, Spin, Tabs, Tag, Tooltip } from "antd"; import { CheckCircleOutlined, CloseCircleOutlined, DashboardOutlined, + InfoCircleOutlined, PlayCircleOutlined, } from "@ant-design/icons"; import { LineChart, Card } from "@tremor/react"; @@ -68,28 +69,50 @@ async function runTestRequest(accessToken: string, model: string, messages: { ro }; } -// Postman-style waterfall row -function WaterfallRow({ label, startMs, durationMs, totalMs, color }: { - label: string; +// OTEL-style trace span row +function WaterfallRow({ label, startMs, durationMs, totalMs, color, tooltip }: { + label: React.ReactNode; startMs: number; durationMs: number; totalMs: number; color: string; + tooltip?: string; }) { const startPct = totalMs > 0 ? (startMs / totalMs) * 100 : 0; const widthPct = totalMs > 0 ? (durationMs / totalMs) * 100 : 0; + const isWide = widthPct > 12; return ( - - {label} - -
+ + + + {label} + {tooltip && ( + + + + )} + + + +
+ {/* Timeline grid */} + {[25, 50, 75].map((pct) => ( +
+ ))} + {/* Span bar */}
+ className={`absolute top-0.5 h-5 rounded ${color} shadow-sm transition-all`} + style={{ left: `${startPct}%`, width: `${Math.max(widthPct, 0.4)}%`, minWidth: "4px" }} + > + {isWide && ( + + {durationMs}ms + + )} +
- {durationMs}ms + {durationMs}ms ); } @@ -397,38 +420,76 @@ export default function PerformanceDashboardView() { {testResult.model}
- {/* Waterfall timing */} + {/* OTEL-style trace waterfall */}
-

Response Time Breakdown

- - - 20 ? "bg-orange-300" : "bg-blue-300"} - /> - - - - - - - -
Total -
-
{testResult.wallMs}ms
+

Response Time Breakdown

+ + {/* Time axis */} +
+
+
+ {[0, 25, 50, 75, 100].map((pct) => ( + + {Math.round((pct / 100) * testResult.wallMs)}ms + + ))} +
+
+
+ + {/* Span rows */} +
+ + + 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." + /> + + {/* Total footer row */} + + + + + + +
Total +
+ {[25, 50, 75].map((pct) => ( +
+ ))} +
+
+
{testResult.wallMs}ms
+
+ {overheadPctTest != null && (

- LiteLLM overhead is {overheadPctTest}% of total response time + LiteLLM overhead:{" "} + 20 ? "text-orange-500 font-medium" : "text-gray-600 font-medium"}> + {overheadPctTest}% of total + {overheadPctTest > 20 && — higher than expected} + {testResult.overheadMs === null && ( + — overhead header not returned (check proxy version) + )}

)}