ui(perf): two-tab layout with Postman-style timing waterfall

- Move Test Request into its own tab (Overview | Test Request)
- Reduce color - muted gray palette, orange only for high overhead
- Add Postman-style waterfall: proportional bars for LiteLLM Processing vs LLM API
- Message editor with role selector and multi-message support
- Status bar showing HTTP status, wall time, model name
- x-litellm headers table shown below response
This commit is contained in:
Ishaan Jaffer 2026-02-28 12:52:15 -08:00
parent 4dc99cb6f7
commit 2ce708490d

View file

@ -1,17 +1,22 @@
"use client";
import React, { useRef, useState, useEffect } from "react";
import { Collapse, Spin, Tag } from "antd";
import { DashboardOutlined, CheckCircleOutlined } from "@ant-design/icons";
import { BarChart, LineChart, Card } from "@tremor/react";
import { MetricCard } from "@/components/GuardrailsMonitor/MetricCard";
import React, { useCallback, useRef, useState, useEffect } from "react";
import { Collapse, Select, Spin, Tabs, Tag } from "antd";
import {
CheckCircleOutlined,
CloseCircleOutlined,
DashboardOutlined,
PlayCircleOutlined,
} from "@ant-design/icons";
import { LineChart, Card } from "@tremor/react";
import {
usePerformanceSummary,
PerformanceSummaryResponse,
PerformanceIssue,
} from "@/app/(dashboard)/hooks/performanceSummary/usePerformanceSummary";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { fetchAvailableModels, ModelGroup } from "@/components/playground/llm_calls/fetch_models";
import { proxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking";
const MAX_HISTORY = 60; // 60 × 10s = 10 min
const MAX_HISTORY = 60;
interface HistoryPoint {
time: string;
@ -20,6 +25,15 @@ interface HistoryPoint {
"HTTP pool %"?: number;
}
interface TestResult {
status: number;
overheadMs: number | null;
wallMs: number;
model: string;
responseText: string;
headers: Record<string, string>;
}
function formatTime(d: Date): string {
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
}
@ -30,130 +44,141 @@ const SEVERITY_DOT: Record<string, string> = {
info: "bg-blue-400",
};
async function runTestRequest(accessToken: string, model: string, messages: { role: string; content: string }[]): Promise<TestResult> {
const url = proxyBaseUrl ? `${proxyBaseUrl}/chat/completions` : `/chat/completions`;
const authHeader = getGlobalLitellmHeaderName();
const start = Date.now();
const resp = await fetch(url, {
method: "POST",
headers: { [authHeader]: `Bearer ${accessToken}`, "Content-Type": "application/json" },
body: JSON.stringify({ model, messages, max_tokens: 100, stream: false }),
});
const wallMs = Date.now() - start;
const responseText = await resp.text();
const captured: Record<string, string> = {};
resp.headers.forEach((v, k) => { if (k.startsWith("x-litellm")) captured[k] = v; });
const overheadRaw = resp.headers.get("x-litellm-overhead-duration-ms");
return {
status: resp.status,
overheadMs: overheadRaw ? Math.round(parseFloat(overheadRaw)) : null,
wallMs,
model,
responseText,
headers: captured,
};
}
// Postman-style waterfall row
function WaterfallRow({ label, startMs, durationMs, totalMs, color }: {
label: string;
startMs: number;
durationMs: number;
totalMs: number;
color: string;
}) {
const startPct = totalMs > 0 ? (startMs / totalMs) * 100 : 0;
const widthPct = totalMs > 0 ? (durationMs / totalMs) * 100 : 0;
return (
<tr className="border-b border-gray-50">
<td className="py-2.5 pr-4 text-sm text-gray-500 w-48 whitespace-nowrap">{label}</td>
<td className="py-2.5 pr-4">
<div className="relative h-4 bg-gray-50 rounded overflow-hidden">
<div
className={`absolute top-0 h-full rounded ${color}`}
style={{ left: `${startPct}%`, width: `${Math.max(widthPct, 0.5)}%` }}
/>
</div>
</td>
<td className="py-2.5 text-sm text-gray-700 text-right w-20 tabular-nums">{durationMs}ms</td>
</tr>
);
}
export default function PerformanceDashboardView() {
const { data, isLoading, error, dataUpdatedAt } = usePerformanceSummary();
const { accessToken } = useAuthorized();
// Client-side ring buffer for time-series charts
const overheadHistoryRef = useRef<HistoryPoint[]>([]);
const [overheadHistory, setOverheadHistory] = useState<HistoryPoint[]>([]);
const inflightHistoryRef = useRef<HistoryPoint[]>([]);
const [inflightHistory, setInflightHistory] = useState<HistoryPoint[]>([]);
const httpHistoryRef = useRef<HistoryPoint[]>([]);
const [httpHistory, setHttpHistory] = useState<HistoryPoint[]>([]);
// Live "X seconds ago" counter
const [secondsAgo, setSecondsAgo] = useState(0);
useEffect(() => {
if (!dataUpdatedAt) return;
setSecondsAgo(0);
const interval = setInterval(() => {
setSecondsAgo(Math.floor((Date.now() - dataUpdatedAt) / 1000));
}, 1000);
const interval = setInterval(() => setSecondsAgo(Math.floor((Date.now() - dataUpdatedAt) / 1000)), 1000);
return () => clearInterval(interval);
}, [dataUpdatedAt]);
useEffect(() => {
if (!data) return;
const now = formatTime(new Date());
const newOH = [...overheadHistoryRef.current, {
time: now,
"Overhead avg": data.latency.overhead?.avg_ms ?? undefined,
}].slice(-MAX_HISTORY);
overheadHistoryRef.current = newOH;
setOverheadHistory([...newOH]);
const newIF = [...inflightHistoryRef.current, {
time: now,
"In-flight": data.connection_pools.in_flight_requests ?? undefined,
}].slice(-MAX_HISTORY);
inflightHistoryRef.current = newIF;
setInflightHistory([...newIF]);
const newHTTP = [...httpHistoryRef.current, {
time: now,
"HTTP pool %": data.connection_pools.http.aiohttp_pct ?? undefined,
}].slice(-MAX_HISTORY);
httpHistoryRef.current = newHTTP;
setHttpHistory([...newHTTP]);
const newOH = [...overheadHistoryRef.current, { time: now, "Overhead avg": data.latency.overhead?.avg_ms ?? undefined }].slice(-MAX_HISTORY);
overheadHistoryRef.current = newOH; setOverheadHistory([...newOH]);
const newIF = [...inflightHistoryRef.current, { time: now, "In-flight": data.connection_pools.in_flight_requests ?? undefined }].slice(-MAX_HISTORY);
inflightHistoryRef.current = newIF; setInflightHistory([...newIF]);
const newHTTP = [...httpHistoryRef.current, { time: now, "HTTP pool %": data.connection_pools.http.aiohttp_pct ?? undefined }].slice(-MAX_HISTORY);
httpHistoryRef.current = newHTTP; setHttpHistory([...newHTTP]);
}, [dataUpdatedAt]);
if (isLoading) {
return (
<div className="flex justify-center items-center h-64">
<Spin size="large" />
</div>
);
}
// Test Request state
const [models, setModels] = useState<ModelGroup[]>([]);
const [selectedModel, setSelectedModel] = useState<string | undefined>(undefined);
const [testMessages, setTestMessages] = useState([{ role: "user", content: "Say hello in one word." }]);
const [isTestLoading, setIsTestLoading] = useState(false);
const [testResult, setTestResult] = useState<TestResult | null>(null);
const [testError, setTestError] = useState<string | null>(null);
if (error || !data) {
return (
<div className="p-8">
<Alert
type="error"
message="Failed to load performance data"
description="Check that the proxy is running and your API key has admin access."
showIcon
/>
</div>
);
}
useEffect(() => {
if (!accessToken) return;
fetchAvailableModels(accessToken).then((m) => {
const chat = m.filter((x) => !x.mode || x.mode === "chat");
const list = chat.length > 0 ? chat : m;
setModels(list);
if (list.length > 0 && !selectedModel) setSelectedModel(list[0].model_group);
}).catch(() => {});
}, [accessToken]);
const { debug_flags, workers, connection_pools, latency, per_model, issues } = data;
const handleRunTest = useCallback(async () => {
if (!accessToken || !selectedModel) return;
setIsTestLoading(true); setTestError(null); setTestResult(null);
try {
setTestResult(await runTestRequest(accessToken, selectedModel, testMessages));
} catch (e: any) {
setTestError(e.message ?? "Request failed");
} finally {
setIsTestLoading(false);
}
}, [accessToken, selectedModel, testMessages]);
if (isLoading) return <div className="flex justify-center items-center h-64"><Spin size="large" /></div>;
if (error || !data) return <div className="p-8 text-sm text-red-500">Failed to load performance data.</div>;
const { debug_flags, workers, connection_pools, latency, issues } = data;
const overheadHigh = latency.overhead_pct_of_total != null && latency.overhead_pct_of_total > 20;
const workersLow = workers.num_workers < workers.cpu_count;
// Delta vs ~5 min ago (oldest point in history)
const oldestOverhead = overheadHistory.length > 1
? overheadHistory[0]["Overhead avg"]
: null;
const currentOverhead = latency.overhead?.avg_ms ?? null;
const overheadDelta = oldestOverhead != null && currentOverhead != null
? Math.round((currentOverhead - oldestOverhead) * 10) / 10
: null;
return (
<div style={{ width: "100%" }} className="p-8">
{/* Header */}
<div className="flex items-center justify-between gap-6 mb-6">
<div className="flex items-stretch gap-2 min-w-0">
<div className="flex-shrink-0 flex items-center">
<DashboardOutlined style={{ fontSize: "32px" }} />
</div>
<div className="flex-1 min-w-0 ml-1">
<h3 className="text-sm font-semibold text-gray-900 mb-0.5 leading-tight">Performance</h3>
<p className="text-xs text-gray-600 leading-tight">LiteLLM Proxy · Latency Diagnostics</p>
</div>
</div>
<Tag color={secondsAgo < 15 ? "green" : "default"} className="text-xs flex-shrink-0">
{dataUpdatedAt
? secondsAgo === 0 ? "Refreshed just now" : `Refreshed ${secondsAgo}s ago`
: "Waiting for data…"}
</Tag>
</div>
{/* ── Issues Detected ── */}
const overviewTab = (
<div>
{/* Issues Detected */}
<div className="mb-6">
<Card>
<div className="flex items-center justify-between mb-3">
<p className="text-sm font-semibold text-gray-700">Issues Detected</p>
{issues.length === 0 && latency.sample_count > 0 && (
<span className="flex items-center gap-1 text-xs text-green-600">
<CheckCircleOutlined /> All clear
</span>
<span className="flex items-center gap-1 text-xs text-green-600"><CheckCircleOutlined /> All clear</span>
)}
</div>
{issues.length === 0 ? (
<p className="text-sm text-gray-400">
{latency.sample_count === 0 ? "Send traffic to start analysis." : "No issues found — proxy looks healthy."}
</p>
<p className="text-sm text-gray-400">{latency.sample_count === 0 ? "Send traffic to start analysis." : "No issues found — proxy looks healthy."}</p>
) : (
<table className="w-full text-sm">
<thead>
<tr className="text-left text-xs text-gray-400 border-b border-gray-100">
<th className="pb-2 font-medium w-4"></th>
<th className="pb-2 w-4"></th>
<th className="pb-2 font-medium">Issue</th>
<th className="pb-2 font-medium">Suggested Fix</th>
</tr>
@ -161,20 +186,16 @@ export default function PerformanceDashboardView() {
<tbody>
{issues.map((issue, i) => (
<tr key={i} className="border-b border-gray-50 align-top">
<td className="py-2 pr-2">
<span className={`inline-block w-2 h-2 rounded-full mt-1.5 ${SEVERITY_DOT[issue.severity] ?? "bg-gray-400"}`} />
</td>
<td className="py-2 pr-2"><span className={`inline-block w-2 h-2 rounded-full mt-1.5 ${SEVERITY_DOT[issue.severity] ?? "bg-gray-400"}`} /></td>
<td className="py-2 pr-4">
<p className="font-medium text-gray-800">{issue.title}</p>
<p className="text-xs text-gray-500 mt-0.5">{issue.description}</p>
</td>
<td className="py-2 min-w-[220px]">
<td className="py-2 min-w-[200px]">
{issue.fix_snippet ? (
<Collapse ghost size="small">
<Collapse.Panel header={<span className="text-xs text-blue-600">{issue.fix}</span>} key="1">
<pre className="text-xs font-mono bg-gray-50 border border-gray-200 rounded px-3 py-2 whitespace-pre-wrap">
{issue.fix_snippet}
</pre>
<pre className="text-xs font-mono bg-gray-50 border border-gray-200 rounded px-3 py-2 whitespace-pre-wrap">{issue.fix_snippet}</pre>
</Collapse.Panel>
</Collapse>
) : (
@ -189,310 +210,288 @@ export default function PerformanceDashboardView() {
</Card>
</div>
{/* ── Top metric cards ── */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<MetricCard
label="OVERHEAD AVG"
value={latency.overhead ? `${latency.overhead.avg_ms} ms` : "—"}
subtitle={
latency.overhead
? `p50: ${latency.overhead.p50_ms}ms · p95: ${latency.overhead.p95_ms}ms${overheadDelta !== null ? ` · ${overheadDelta >= 0 ? "+" : ""}${overheadDelta}ms vs earlier` : ""}`
: "No data yet"
}
valueColor={overheadHigh ? "text-orange-500" : "text-gray-900"}
/>
<MetricCard
label="OVERHEAD % OF TOTAL"
value={latency.overhead_pct_of_total != null ? `${latency.overhead_pct_of_total}%` : "—"}
subtitle="LiteLLM share of request time"
valueColor={overheadHigh ? "text-orange-500" : "text-green-600"}
/>
<MetricCard
label="LLM API AVG"
value={latency.llm_api ? `${latency.llm_api.avg_ms} ms` : "—"}
subtitle={latency.llm_api ? `p50: ${latency.llm_api.p50_ms}ms · p95: ${latency.llm_api.p95_ms}ms` : "No data yet"}
/>
<MetricCard
label="TOTAL AVG"
value={latency.total ? `${latency.total.avg_ms} ms` : "—"}
subtitle={latency.total ? `p95: ${latency.total.p95_ms}ms · n=${latency.sample_count}` : "No data yet"}
/>
</div>
{/* ── Main 2-column layout ── */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
{/* Left: Overhead Over Time */}
<div className="lg:col-span-2">
<Card>
<p className="text-sm font-semibold text-gray-700 mb-0.5">Overhead Over Time</p>
<p className="text-xs text-gray-400 mb-3">LiteLLM-added latency · last 10 min · 10s resolution</p>
{/* Plain-English summary */}
{latency.sample_count > 0 && latency.overhead_histogram && (() => {
const hist = latency.overhead_histogram;
const total = hist.reduce((s, b) => s + b.count, 0);
const under50 = hist.filter(b => ["0-5","5-10","10-25","25-50"].includes(b.bucket)).reduce((s,b) => s+b.count, 0);
const pct = total > 0 ? Math.round(under50 / total * 100) : 0;
const worstBucket = [...hist].reverse().find(b => b.count > 0);
return (
<div className="flex items-center gap-6 mb-3 px-1">
<div className="text-center">
<p className="text-2xl font-bold text-gray-900">{total}</p>
<p className="text-xs text-gray-400">total requests</p>
</div>
<div className="text-center">
<p className={`text-2xl font-bold ${pct >= 90 ? "text-green-600" : pct >= 70 ? "text-amber-500" : "text-red-500"}`}>{pct}%</p>
<p className="text-xs text-gray-400">under 50ms overhead</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-gray-900">{latency.overhead?.p95_ms ?? "—"}ms</p>
<p className="text-xs text-gray-400">p95 overhead</p>
</div>
{worstBucket && worstBucket.bucket !== "0-5" && (
<div className="text-center">
<p className="text-2xl font-bold text-orange-500">{worstBucket.count}</p>
<p className="text-xs text-gray-400">requests &gt; {worstBucket.bucket.split("-")[0]}ms</p>
</div>
)}
</div>
);
})()}
{latency.sample_count === 0 ? (
<div className="flex items-center justify-center h-40 text-gray-400 text-sm">
No requests yet send traffic through the proxy to see data.
</div>
) : (
<LineChart
data={overheadHistory}
index="time"
categories={["Overhead avg"]}
colors={["blue"]}
valueFormatter={(v) => `${v} ms`}
yAxisWidth={52}
showLegend={false}
showAnimation={false}
className="h-40"
/>
)}
</Card>
</div>
{/* Right: Worker Provisioning + Connections */}
<div className="flex flex-col gap-4">
<Card>
<div className="flex items-center justify-between mb-3">
<p className="text-sm font-semibold text-gray-700">WORKER PROVISIONING</p>
{workersLow && <Tag color="orange" className="text-xs">Under-provisioned</Tag>}
</div>
<div className="grid grid-cols-2 gap-4 mb-3">
<div>
<p className="text-xs text-gray-500">CPU Cores</p>
<p className="text-2xl font-bold text-gray-900">{workers.cpu_count}</p>
</div>
<div>
<p className="text-xs text-gray-500">Workers</p>
<p className={`text-2xl font-bold ${workersLow ? "text-orange-500" : "text-gray-900"}`}>
{workers.num_workers}
</p>
</div>
</div>
{workers.cpu_percent != null && (
<div className="mb-3">
<p className="text-xs text-gray-500">CPU Usage</p>
<p className={`text-lg font-semibold ${workers.cpu_percent > 80 ? "text-red-500" : workers.cpu_percent > 60 ? "text-orange-500" : "text-green-600"}`}>
{workers.cpu_percent}%
</p>
</div>
)}
<div className="text-xs text-gray-500 mb-1">
Workers / CPU ratio
<span className="float-right">{workers.num_workers}/{workers.cpu_count}</span>
</div>
<div className="w-full bg-gray-100 rounded-full h-1.5 mb-2">
<div
className={`h-1.5 rounded-full ${workersLow ? "bg-orange-400" : "bg-green-500"}`}
style={{ width: `${Math.min((workers.num_workers / workers.cpu_count) * 100, 100)}%` }}
/>
</div>
{workersLow && (
<p className="text-xs text-orange-600">Recommended: {2 * workers.cpu_count + 1} workers (2× CPU + 1)</p>
)}
</Card>
<Card className="flex-1">
<p className="text-sm font-semibold text-gray-700 mb-3">CONNECTIONS</p>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-gray-500 mb-0.5">Database</p>
<span className={`text-xs font-medium ${connection_pools.db.connected ? "text-green-600" : "text-red-500"}`}>
{connection_pools.db.connected ? "● Connected" : "● Disconnected"}
</span>
</div>
<div className="text-right">
<p className="text-xs text-gray-500 mb-0.5">Pool limit</p>
<p className="text-lg font-bold text-gray-900">{connection_pools.db.pool_limit}</p>
</div>
</div>
<div className="text-xs text-gray-400 -mt-1">
Timeout: {connection_pools.db.pool_timeout_seconds}s
</div>
<div className="border-t border-gray-100 pt-3">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-gray-500 mb-0.5">Redis</p>
<span className={`text-xs font-medium ${connection_pools.redis.enabled ? "text-green-600" : "text-gray-400"}`}>
{connection_pools.redis.enabled ? "● Connected" : "● Not configured"}
</span>
</div>
{connection_pools.redis.enabled && (
<div className="text-right">
<p className="text-xs text-gray-500 mb-0.5">Max connections</p>
<p className="text-lg font-bold text-gray-900">{connection_pools.redis.max_connections ?? "∞"}</p>
</div>
)}
</div>
</div>
</div>
</Card>
</div>
</div>
{/* ── Bottom row: In-Flight + HTTP pool ── */}
{/* Current Overhead | Worker Provisioning */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<Card>
<div className="flex items-center justify-between mb-0.5">
<p className="text-sm font-semibold text-gray-700">In-Flight Requests</p>
<span className="text-sm font-bold text-gray-700">
{connection_pools.in_flight_requests ?? "—"}
<span className="text-xs font-normal text-gray-400"> asyncio tasks</span>
</span>
</div>
<p className="text-xs text-gray-400 mb-4">
Concurrent asyncio tasks (proxy-wide) · last 10 min
</p>
<LineChart
data={inflightHistory}
index="time"
categories={["In-flight"]}
colors={["blue"]}
yAxisWidth={40}
showLegend={false}
showAnimation={false}
className="h-40"
/>
<p className="text-sm font-semibold text-gray-700 mb-4">Current Overhead</p>
{latency.sample_count === 0 ? (
<p className="text-sm text-gray-400">No data send traffic through the proxy.</p>
) : (
<>
<div className="grid grid-cols-2 gap-y-4 mb-4">
<div><p className="text-xs text-gray-500">Avg</p><p className={`text-2xl font-bold ${overheadHigh ? "text-orange-500" : "text-gray-900"}`}>{latency.overhead ? `${latency.overhead.avg_ms}ms` : "—"}</p></div>
<div><p className="text-xs text-gray-500">p50</p><p className="text-2xl font-bold text-gray-900">{latency.overhead ? `${latency.overhead.p50_ms}ms` : "—"}</p></div>
<div><p className="text-xs text-gray-500">p95</p><p className="text-2xl font-bold text-gray-900">{latency.overhead ? `${latency.overhead.p95_ms}ms` : "—"}</p></div>
<div><p className="text-xs text-gray-500">% of total</p><p className={`text-2xl font-bold ${overheadHigh ? "text-orange-500" : "text-green-600"}`}>{latency.overhead_pct_of_total != null ? `${latency.overhead_pct_of_total}%` : "—"}</p></div>
</div>
<p className="text-xs text-gray-400 mb-2">Last {latency.sample_count} requests · LLM API avg: {latency.llm_api ? `${latency.llm_api.avg_ms}ms` : "—"} · Total avg: {latency.total ? `${latency.total.avg_ms}ms` : "—"}</p>
<LineChart data={overheadHistory} index="time" categories={["Overhead avg"]} colors={["blue"]} valueFormatter={(v) => `${v}ms`} yAxisWidth={48} showLegend={false} showAnimation={false} className="h-28" />
</>
)}
</Card>
<Card>
<div className="flex items-center justify-between mb-0.5">
<p className="text-sm font-semibold text-gray-700">HTTP Client Pool Utilization</p>
<span className="text-sm font-bold text-gray-700">
{connection_pools.http.aiohttp_active ?? "—"}
<span className="text-xs font-normal text-gray-400">
{" "}/ {connection_pools.http.aiohttp_limit} limit
{connection_pools.http.aiohttp_pct != null && ` · ${connection_pools.http.aiohttp_pct}%`}
</span>
</span>
<div className="flex items-center justify-between mb-4">
<p className="text-sm font-semibold text-gray-700">Worker Provisioning</p>
{workersLow && <Tag color="orange" className="text-xs">Under-provisioned</Tag>}
</div>
<p className="text-xs text-gray-400 mb-4">
aiohttp active connections ÷ pool limit · amber line = 80% · last 10 min
</p>
<LineChart
data={httpHistory}
index="time"
categories={["HTTP pool %"]}
colors={["blue"]}
valueFormatter={(v) => `${v}%`}
yAxisWidth={44}
showLegend={false}
showAnimation={false}
className="h-40"
referenceLine={{ value: 80, label: "80%", color: "amber" }}
/>
<div className="grid grid-cols-3 gap-4 mb-4">
<div><p className="text-xs text-gray-500">CPU Cores</p><p className="text-2xl font-bold text-gray-900">{workers.cpu_count}</p></div>
<div><p className="text-xs text-gray-500">Workers</p><p className={`text-2xl font-bold ${workersLow ? "text-orange-500" : "text-gray-900"}`}>{workers.num_workers}</p></div>
<div>
<p className="text-xs text-gray-500">CPU Usage</p>
<p className={`text-2xl font-bold ${workers.cpu_percent == null ? "text-gray-400" : workers.cpu_percent > 80 ? "text-red-500" : workers.cpu_percent > 60 ? "text-orange-500" : "text-green-600"}`}>
{workers.cpu_percent != null ? `${workers.cpu_percent}%` : "—"}
</p>
</div>
</div>
<div className="text-xs text-gray-500 mb-1">Workers / CPU <span className="float-right">{workers.num_workers}/{workers.cpu_count}</span></div>
<div className="w-full bg-gray-100 rounded-full h-1.5">
<div className={`h-1.5 rounded-full ${workersLow ? "bg-orange-400" : "bg-green-500"}`} style={{ width: `${Math.min((workers.num_workers / workers.cpu_count) * 100, 100)}%` }} />
</div>
{workersLow && <p className="text-xs text-orange-600 mt-2">Recommended: {2 * workers.cpu_count + 1} workers (2× CPU + 1)</p>}
</Card>
</div>
{/* ── Per-model breakdown ── */}
{per_model.length > 0 && (
<div className="mb-6">
<Card>
<p className="text-sm font-semibold text-gray-700 mb-0.5">Per-Model Overhead</p>
<p className="text-xs text-gray-400 mb-4">Sorted by overhead avg · last {latency.sample_count} requests</p>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="text-left text-xs text-gray-400 border-b border-gray-100">
<th className="pb-2 font-medium">Model</th>
<th className="pb-2 font-medium text-right">Overhead avg</th>
<th className="pb-2 font-medium text-right">Overhead p95</th>
<th className="pb-2 font-medium text-right">LLM API avg</th>
<th className="pb-2 font-medium text-right">Total avg</th>
<th className="pb-2 font-medium text-right">Requests</th>
</tr>
</thead>
<tbody>
{per_model.map((row) => {
const overheadPct = row.overhead && row.total
? Math.round((row.overhead.avg_ms / row.total.avg_ms) * 100)
: null;
return (
<tr key={row.model} className="border-b border-gray-50 hover:bg-gray-50">
<td className="py-2 font-mono text-xs text-gray-700 max-w-[200px] truncate pr-4">
{row.model}
</td>
<td className="py-2 text-right">
<span className={overheadPct != null && overheadPct > 20 ? "text-orange-500 font-semibold" : "text-gray-700"}>
{row.overhead ? `${row.overhead.avg_ms}ms` : "—"}
</span>
{overheadPct != null && (
<span className="text-xs text-gray-400 ml-1">({overheadPct}%)</span>
)}
</td>
<td className="py-2 text-right text-gray-600">
{row.overhead ? `${row.overhead.p95_ms}ms` : "—"}
</td>
<td className="py-2 text-right text-gray-600">
{row.llm_api ? `${row.llm_api.avg_ms}ms` : "—"}
</td>
<td className="py-2 text-right text-gray-600">
{row.total ? `${row.total.avg_ms}ms` : "—"}
</td>
<td className="py-2 text-right text-gray-400 text-xs">{row.sample_count}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</Card>
</div>
)}
{/* Proxy Settings */}
<div className="mb-6">
<Card>
<p className="text-sm font-semibold text-gray-700 mb-3">Proxy Settings</p>
<table className="w-full text-sm">
<thead>
<tr className="text-left text-xs text-gray-400 border-b border-gray-100">
<th className="pb-2 font-medium">Setting</th>
<th className="pb-2 font-medium">Current Value</th>
<th className="pb-2 font-medium">Optimized for Performance</th>
</tr>
</thead>
<tbody>
<tr className="border-b border-gray-50">
<td className="py-3 text-gray-700">Debug Mode</td>
<td className="py-3"><Tag color={debug_flags.is_detailed_debug ? "red" : "default"} className="text-xs">{debug_flags.log_level}</Tag></td>
<td className="py-3">{!debug_flags.is_detailed_debug ? <span className="flex items-center gap-1.5 text-green-600 text-xs font-medium"><CheckCircleOutlined /> Yes</span> : <span className="flex items-center gap-1.5 text-red-500 text-xs font-medium"><CloseCircleOutlined /> No disable DEBUG logging in production</span>}</td>
</tr>
<tr className="border-b border-gray-50">
<td className="py-3 text-gray-700">Detailed Timing Headers</td>
<td className="py-3"><Tag color={debug_flags.detailed_timing_enabled ? "blue" : "default"} className="text-xs">{debug_flags.detailed_timing_enabled ? "Enabled" : "Disabled"}</Tag></td>
<td className="py-3">{!debug_flags.detailed_timing_enabled ? <span className="flex items-center gap-1.5 text-green-600 text-xs font-medium"><CheckCircleOutlined /> Yes</span> : <span className="flex items-center gap-1.5 text-gray-500 text-xs font-medium"><CheckCircleOutlined className="text-gray-400" /> Minor overhead safe to keep for debugging</span>}</td>
</tr>
<tr>
<td className="py-3 text-gray-700">Worker Count</td>
<td className="py-3"><span className="text-gray-700 font-medium">{workers.num_workers}</span><span className="text-gray-400 text-xs ml-1">/ {workers.cpu_count} CPU cores</span></td>
<td className="py-3">{!workersLow ? <span className="flex items-center gap-1.5 text-green-600 text-xs font-medium"><CheckCircleOutlined /> Yes</span> : <span className="flex items-center gap-1.5 text-orange-500 text-xs font-medium"><CloseCircleOutlined /> No recommend {2 * workers.cpu_count + 1} workers (2× CPU + 1)</span>}</td>
</tr>
</tbody>
</table>
</Card>
</div>
{/* ── Config summary ── */}
<div className="border border-gray-200 rounded-lg p-4 bg-gray-50">
<p className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3">Configuration Summary</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<p className="text-xs text-gray-400 mb-1">Log Level</p>
<Tag color={debug_flags.is_detailed_debug ? "red" : "green"}>{debug_flags.log_level}</Tag>
{/* Active Async Tasks | HTTP Pool */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<div className="flex items-center justify-between mb-0.5">
<p className="text-sm font-semibold text-gray-700">Active Async Tasks</p>
<span className="text-sm font-bold text-gray-700">{connection_pools.in_flight_requests ?? "—"}<span className="text-xs font-normal text-gray-400"> tasks</span></span>
</div>
<div>
<p className="text-xs text-gray-400 mb-1">Detailed Timing</p>
<Tag color={debug_flags.detailed_timing_enabled ? "green" : "default"}>
{debug_flags.detailed_timing_enabled ? "Enabled" : "Disabled"}
</Tag>
<p className="text-xs text-gray-400 mb-4">Total asyncio tasks running on this worker · last 10 min</p>
<LineChart data={inflightHistory} index="time" categories={["In-flight"]} colors={["blue"]} yAxisWidth={40} showLegend={false} showAnimation={false} className="h-36" />
</Card>
<Card>
<div className="flex items-center justify-between mb-0.5">
<p className="text-sm font-semibold text-gray-700">HTTP Client Pool</p>
<span className="text-sm font-bold text-gray-700">{connection_pools.http.aiohttp_active ?? "—"}<span className="text-xs font-normal text-gray-400"> / {connection_pools.http.aiohttp_limit}{connection_pools.http.aiohttp_pct != null && ` · ${connection_pools.http.aiohttp_pct}%`}</span></span>
</div>
<div>
<p className="text-xs text-gray-400 mb-1">Workers / CPU</p>
<span className="font-semibold text-gray-700">{workers.num_workers} / {workers.cpu_count}</span>
</div>
<div>
<p className="text-xs text-gray-400 mb-1">Sample Count</p>
<span className="font-semibold text-gray-700">{latency.sample_count}</span>
</div>
</div>
<p className="text-xs text-gray-400 mb-4">aiohttp active / pool limit · last 10 min</p>
<LineChart data={httpHistory} index="time" categories={["HTTP pool %"]} colors={["blue"]} valueFormatter={(v) => `${v}%`} yAxisWidth={44} showLegend={false} showAnimation={false} className="h-36" referenceLine={{ value: 80, label: "80%", color: "amber" }} />
</Card>
</div>
</div>
);
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)
: null;
let parsedContent = "";
if (testResult) {
try {
const parsed = JSON.parse(testResult.responseText);
parsedContent = parsed?.choices?.[0]?.message?.content ?? testResult.responseText;
} catch {
parsedContent = testResult.responseText;
}
}
const testTab = (
<div>
<Card>
{/* Controls */}
<div className="flex items-end gap-3 mb-6">
<div>
<p className="text-xs text-gray-500 mb-1">Model</p>
<Select
value={selectedModel}
placeholder="Select model"
onChange={setSelectedModel}
options={models.map((m) => ({ value: m.model_group, label: m.model_group }))}
style={{ width: 260 }}
showSearch
/>
</div>
<button
onClick={handleRunTest}
disabled={isTestLoading || !selectedModel}
className={`flex items-center gap-2 px-4 py-1.5 rounded text-sm font-medium transition-colors ${isTestLoading || !selectedModel ? "bg-gray-100 text-gray-400 cursor-not-allowed" : "bg-blue-600 text-white hover:bg-blue-700 cursor-pointer"}`}
>
{isTestLoading ? <Spin size="small" /> : <PlayCircleOutlined />}
{isTestLoading ? "Running…" : "Run Test"}
</button>
</div>
{/* Message editor */}
<div className="mb-6">
<p className="text-xs text-gray-500 mb-2">Messages</p>
{testMessages.map((msg, i) => (
<div key={i} className="flex gap-2 mb-2 items-start">
<select
value={msg.role}
onChange={(e) => setTestMessages((prev) => prev.map((m, j) => j === i ? { ...m, role: e.target.value } : m))}
className="text-xs border border-gray-200 rounded px-2 py-1.5 text-gray-600 bg-white w-24 flex-shrink-0"
>
<option value="user">user</option>
<option value="system">system</option>
<option value="assistant">assistant</option>
</select>
<textarea
value={msg.content}
onChange={(e) => setTestMessages((prev) => prev.map((m, j) => j === i ? { ...m, content: e.target.value } : m))}
rows={2}
className="flex-1 text-sm border border-gray-200 rounded px-3 py-1.5 text-gray-700 resize-y font-mono"
/>
{testMessages.length > 1 && (
<button onClick={() => setTestMessages((prev) => prev.filter((_, j) => j !== i))} className="text-gray-300 hover:text-gray-500 text-xs pt-1.5"></button>
)}
</div>
))}
<button
onClick={() => setTestMessages((prev) => [...prev, { role: "user", content: "" }])}
className="text-xs text-blue-500 hover:text-blue-700 mt-1"
>
+ Add message
</button>
</div>
{testError && <p className="text-xs text-red-500 mb-4">{testError}</p>}
{testResult && (
<>
{/* Status bar */}
<div className="flex items-center gap-4 py-2.5 px-3 bg-gray-50 rounded border border-gray-100 mb-6 text-sm">
<span className={`font-medium ${testResult.status === 200 ? "text-green-600" : "text-red-500"}`}>
{testResult.status === 200 ? "200 OK" : `${testResult.status} Error`}
</span>
<span className="text-gray-400">·</span>
<span className="text-gray-600">{testResult.wallMs}ms</span>
<span className="text-gray-400">·</span>
<span className="text-gray-500 text-xs">{testResult.model}</span>
</div>
{/* Waterfall timing */}
<div className="mb-6">
<p className="text-xs font-medium text-gray-500 uppercase tracking-wide mb-3">Response Time Breakdown</p>
<table className="w-full">
<tbody>
<WaterfallRow
label="LiteLLM Processing"
startMs={0}
durationMs={testResult.overheadMs ?? 0}
totalMs={testResult.wallMs}
color={overheadPctTest != null && overheadPctTest > 20 ? "bg-orange-300" : "bg-blue-300"}
/>
<WaterfallRow
label="LLM API (waiting)"
startMs={testResult.overheadMs ?? 0}
durationMs={llmApiMs}
totalMs={testResult.wallMs}
color="bg-gray-300"
/>
<tr>
<td className="py-2.5 pr-4 text-sm font-medium text-gray-700 w-48">Total</td>
<td className="py-2.5 pr-4">
<div className="h-4 bg-gray-100 rounded" />
</td>
<td className="py-2.5 text-sm font-semibold text-gray-900 text-right w-20 tabular-nums">{testResult.wallMs}ms</td>
</tr>
</tbody>
</table>
{overheadPctTest != null && (
<p className="text-xs text-gray-400 mt-2">
LiteLLM overhead is {overheadPctTest}% of total response time
{overheadPctTest > 20 && <span className="text-orange-500"> higher than expected</span>}
</p>
)}
</div>
{/* Response content */}
{parsedContent && (
<div>
<p className="text-xs font-medium text-gray-500 uppercase tracking-wide mb-2">Response</p>
<div className="bg-gray-50 border border-gray-100 rounded px-3 py-2.5 text-sm text-gray-700 font-mono whitespace-pre-wrap max-h-48 overflow-y-auto">
{parsedContent}
</div>
</div>
)}
{/* x-litellm headers */}
{Object.keys(testResult.headers).length > 0 && (
<div className="mt-4">
<p className="text-xs font-medium text-gray-500 uppercase tracking-wide mb-2">LiteLLM Headers</p>
<table className="w-full text-xs">
<tbody>
{Object.entries(testResult.headers).map(([k, v]) => (
<tr key={k} className="border-b border-gray-50">
<td className="py-1.5 pr-4 text-gray-400 font-mono">{k}</td>
<td className="py-1.5 text-gray-600 font-mono">{v}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</>
)}
{!testResult && !testError && !isTestLoading && (
<p className="text-sm text-gray-400">Select a model and run a test to see latency breakdown.</p>
)}
</Card>
</div>
);
return (
<div style={{ width: "100%" }} className="p-8">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<DashboardOutlined style={{ fontSize: "24px" }} />
<div>
<h3 className="text-sm font-semibold text-gray-900 leading-tight">Performance</h3>
<p className="text-xs text-gray-500 leading-tight">LiteLLM Proxy · Latency Diagnostics</p>
</div>
</div>
<Tag color={secondsAgo < 15 ? "green" : "default"} className="text-xs">
{dataUpdatedAt ? (secondsAgo === 0 ? "just now" : `${secondsAgo}s ago`) : "waiting…"}
</Tag>
</div>
<Tabs
defaultActiveKey="overview"
items={[
{ key: "overview", label: "Overview", children: overviewTab },
{ key: "test", label: "Test Request", children: testTab },
]}
/>
</div>
);
}