// CIL[BEGIN chunk_id="PerformancePage_module_header_1785000031" type="module_header" identity="module_header" parent="none"] // CIL[DOC-MACRO purpose="Render the Workbench Performance observability page"] // CIL[DOC-RESPONSIBILITY responsibility="Load handler duration, request count, and error rate series plus trace stats, then render charts and a slowest-traces table"] // CIL[DOC-CONTEXT module="workbench.pages.observe.PerformancePage" layer="frontend-page" related_files="[\"custom/apps/workbench/functions/custom_workbench-observability.ts\",\"custom/apps/workbench/components/MetricChart.tsx\"]"] // CIL[DOC-CONTRACT inputs="none" outputs="JSX Performance page" errors="fetch errors rendered inline" side_effects="calls /api/custom_workbench-observability"] // CIL[DOC-MICRO logic_summary="Provides a time range selector, fetches three metric series and trace stats, computes duration percentiles, and displays charts and a slowest traces table" notable_decisions=["Reuses MetricChart component","Computes P95 from trace duration_ms array"]] // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"] // CIL[DOC-STABILITY stability="experimental"] // CIL[DOC-AUDIENCE audience="developer"] import { useEffect, useMemo, useState, Fragment } from 'react' import { apiFetch } from '@core/lib/api' import { formatDateTime, formatNumber, cn } from '@core/lib/utils' import { Card, CardContent, CardHeader, CardTitle } from '@core/components/ui/card' import { NativeSelect } from '@core/components/ui/native-select' import { LoadingSpinner } from '@core/components/ui/LoadingSpinner' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@core/components/ui/table' import { MetricChart } from '../../components/MetricChart' import { Pagination } from '../../components/Pagination' import { TraceTree } from '../../components/TraceTree' import { TrendingUp, Activity, Clock, AlertTriangle, Gauge, ChevronDown, ChevronRight } from 'lucide-react' // CIL[END chunk_id="PerformancePage_module_header_1785000031"] // CIL[BEGIN chunk_id="PerformancePage_performancePage_1785000031" type="function" identity="PerformancePage" parent="PerformancePage_module_header_1785000031"] // CIL[DOC-MACRO purpose="Performance observability page implementation"] // CIL[DOC-RESPONSIBILITY responsibility="Fetch and display performance metrics and slowest traces"] // CIL[DOC-CONTEXT module="workbench.pages.observe.PerformancePage" layer="frontend-page" related_files="[\"custom/apps/workbench/pages/observe/UptimePage.tsx\"]"] // CIL[DOC-CONTRACT inputs="none" outputs="JSX" errors="fetch errors rendered inline" side_effects="network requests on mount and range change"] // CIL[DOC-MICRO logic_summary="Fetches metric series and trace stats for the selected range, renders metric charts, summary cards, and a slowest traces table" notable_decisions=["Uses 1 hour granularity for ranges up to 7 days, 1 day for 30 days"]] // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"] // CIL[DOC-STABILITY stability="experimental"] // CIL[DOC-AUDIENCE audience="developer"] const RANGE_OPTIONS = [ { label: 'Last hour', value: '1h', ms: 60 * 60 * 1000 }, { label: 'Last 24 hours', value: '24h', ms: 24 * 60 * 60 * 1000 }, { label: 'Last 7 days', value: '7d', ms: 7 * 24 * 60 * 60 * 1000 }, { label: 'Last 30 days', value: '30d', ms: 30 * 24 * 60 * 60 * 1000 }, ] function rangeDates(value: string) { const now = new Date() const option = RANGE_OPTIONS.find((o) => o.value === value) || RANGE_OPTIONS[1] return { from: new Date(now.getTime() - option.ms).toISOString(), to: now.toISOString(), } } function granularityFor(range: string) { return range === '30d' ? '1 day' : '1 hour' } function percentile(values: number[], p: number) { if (values.length === 0) return 0 const sorted = [...values].sort((a, b) => a - b) const idx = Math.max(0, Math.ceil((p / 100) * sorted.length) - 1) return sorted[idx] } interface TraceRow { trace_id: string root_span_name: string started_at: string duration_ms: number span_count: number error_count: number outcome: string } export default function PerformancePage() { const [range, setRange] = useState('24h') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [avgDuration, setAvgDuration] = useState([]) const [requests, setRequests] = useState([]) const [errorRate, setErrorRate] = useState([]) const [traces, setTraces] = useState([]) const [tracePage, setTracePage] = useState(1) const [tracePageSize, setTracePageSize] = useState(10) const [traceExpanded, setTraceExpanded] = useState(null) const load = async () => { const { from, to } = rangeDates(range) const granularity = granularityFor(range) setLoading(true) setError(null) try { const base = `/api/custom_workbench-observability?date_from=${encodeURIComponent(from)}&date_to=${encodeURIComponent(to)}` const [avgRes, reqRes, errRes, traceRes] = await Promise.all([ apiFetch(`${base}&action=metric&metric_name=avg_handler_duration&granularity=${encodeURIComponent(granularity)}`), apiFetch(`${base}&action=metric&metric_name=request_count&granularity=${encodeURIComponent(granularity)}`), apiFetch(`${base}&action=metric&metric_name=error_rate&granularity=${encodeURIComponent(granularity)}`), apiFetch(`${base}&action=traces`), ]) const [avgJson, reqJson, errJson, traceJson] = await Promise.all([ avgRes.json(), reqRes.json(), errRes.json(), traceRes.json(), ]) if (avgJson.error) throw new Error(avgJson.error) if (reqJson.error) throw new Error(reqJson.error) if (errJson.error) throw new Error(errJson.error) if (traceJson.error) throw new Error(traceJson.error) setAvgDuration((avgJson.data || []).map((r: any) => ({ bucket: r.bucket, value: Number(r.value) }))) setRequests((reqJson.data || []).map((r: any) => ({ bucket: r.bucket, value: Number(r.value) }))) setErrorRate((errJson.data || []).map((r: any) => ({ bucket: r.bucket, value: Number(r.value) }))) const tracePayload = traceJson.data || { data: [], total: 0 } setTraces((tracePayload.data || []).map((r: any) => ({ ...r, duration_ms: Number(r.duration_ms || 0), span_count: Number(r.span_count || 0), error_count: Number(r.error_count || 0), }))) } catch (e: any) { setError(e.message || 'Failed to load performance data') } finally { setLoading(false) } } useEffect(() => { load() setTracePage(1) }, [range]) const durations = useMemo(() => traces.map((t) => t.duration_ms).filter((v) => v > 0), [traces]) const avgDur = useMemo(() => (durations.length ? durations.reduce((a, b) => a + b, 0) / durations.length : 0), [durations]) const p95 = useMemo(() => percentile(durations, 95), [durations]) const maxDur = useMemo(() => (durations.length ? Math.max(...durations) : 0), [durations]) const totalRequests = useMemo(() => requests.reduce((sum, r) => sum + r.value, 0), [requests]) const totalErrors = useMemo(() => traces.reduce((sum, t) => sum + t.error_count, 0), [traces]) const sortedTraces = useMemo(() => [...traces].sort((a, b) => b.duration_ms - a.duration_ms), [traces]) const slowestTraces = useMemo(() => { const start = (tracePage - 1) * tracePageSize return sortedTraces.slice(start, start + tracePageSize) }, [sortedTraces, tracePage, tracePageSize]) return (

Performance

Handler durations, throughput, and slow traces

setRange(e.target.value)} className="w-40"> {RANGE_OPTIONS.map((o) => ( ))}
{error && (
{error}
)} {loading && !requests.length && (
)}
Avg Duration
{avgDur.toFixed(0)} ms
P95 Duration
{p95.toFixed(0)} ms
Max Duration
{maxDur.toFixed(0)} ms
Total Requests
{formatNumber(totalRequests)}
Error Traces
{formatNumber(totalErrors)}
`${Number(v).toFixed(0)} ms`} yTickFormatter={(v) => `${Number(v).toFixed(0)} ms`} /> formatNumber(Number(v))} /> `${(Number(v) * 100).toFixed(1)}%`} yTickFormatter={(v) => `${(Number(v) * 100).toFixed(0)}%`} />
Slowest Traces {slowestTraces.length === 0 ? (

No traces in the selected range.

) : ( <>
Trace Root Span Started Duration Spans Errors Outcome {slowestTraces.map((row) => ( setTraceExpanded(traceExpanded === row.trace_id ? null : row.trace_id)} > {traceExpanded === row.trace_id ? ( ) : ( )} {row.trace_id?.slice(0, 8)}… {row.root_span_name || '—'} {formatDateTime(row.started_at)} {row.duration_ms.toFixed(0)} ms {formatNumber(row.span_count)} {formatNumber(row.error_count)} 0 || row.outcome === 'failure' ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700' )} > {row.error_count > 0 || row.outcome === 'failure' ? 'Error' : 'OK'} {traceExpanded === row.trace_id && ( )} ))}
)}
) } // CIL[END chunk_id="PerformancePage_performancePage_1785000031"]