import { useEffect, useState } from "react"
import { Activity, Gauge, HardDrive, Timer } from "lucide-react"
import { getProfile, type ProfileTurn } from "@/lib/api"
/* Wall-time phases stacked per turn. The order is the palette's CVD-safe slot
* order (validated as a set on this surface) — identity never leans on colour
* alone: segments keep 2px gaps, the legend is always shown and the table
* carries the exact numbers. Disk *service* time is reported separately: it
* runs on I/O threads overlapped with compute, so only the stall the compute
* thread actually felt (I/O wait) belongs inside the wall-time stack. */
const PHASES = [
{ key: "expert_wait_s", name: "I/O wait", color: "#3987e5" },
{ key: "expert_matmul_s", name: "Expert matmul", color: "#199e70" },
{ key: "attention_s", name: "Attention", color: "#c98500" },
{ key: "lm_head_s", name: "LM head", color: "#008300" },
{ key: "other_s", name: "Other", color: "#9085e9" },
] as const
interface Turn extends ProfileTurn { other_s: number; toks: number }
const derive = (turn: ProfileTurn): Turn => ({
...turn,
other_s: Math.max(0, turn.wall_s - turn.expert_wait_s - turn.expert_matmul_s - turn.attention_s - turn.lm_head_s),
toks: turn.wall_s > 0 ? turn.completion_tokens / turn.wall_s : 0,
})
const seconds = (value: number) => (value >= 10 ? value.toFixed(1) : value.toFixed(2)) + "s"
function ShareBar({ label, turns }: { label: string; turns: Turn[] }) {
const total = turns.reduce((sum, turn) => sum + turn.wall_s, 0)
const parts = PHASES.map((phase) => ({ ...phase, value: turns.reduce((sum, turn) => sum + turn[phase.key], 0) }))
return (
{label} {seconds(total)}
`${part.name} ${seconds(part.value)}`).join(", ")}>
{parts.map((part) => {
const share = total > 0 ? part.value / total : 0
return share > 0.001 ? (
{share >= 0.09 ? `${Math.round(100 * share)}%` : ""}
) : null
})}
)
}
/* Column chart over the recent turns; oldest on the left. Stacked mode draws the
* wall-time composition, plain mode a single series (no legend — the title names it). */
function TurnColumns({ turns, stacked, height, format }: { turns: Turn[]; stacked: boolean; height: number; format: (turn: Turn) => string }) {
const [hover, setHover] = useState(null)
const peak = Math.max(...turns.map((turn) => (stacked ? turn.wall_s : turn.toks)), 1e-9)
const gap = 2
const width = Math.max(1, (100 - gap * (turns.length - 1)) / turns.length)
return (
setHover(null)}>
{[0.25, 0.5, 0.75].map((line) => )}
{turns.map((turn, index) => {
const x = index * (width + gap)
if (!stacked) {
const h = (height * turn.toks) / peak
return
}
let y = height
return PHASES.map((phase) => {
const h = (height * turn[phase.key]) / peak
y -= h
return h > 0.1 ? : null
})
})}
{/* hit targets bigger than the marks */}
{turns.map((_, index) => setHover(index)} />)}
{turns.length > 1 ? `${turns.length} turns · oldest → newest` : "1 turn"}
{hover !== null && turns[hover] ? format(turns[hover]) : `peak ${stacked ? seconds(peak) : peak.toFixed(1) + " tok/s"}`}
)
}
export function Profiling({ baseUrl, apiKey, connected }: { baseUrl: string; apiKey: string; connected: boolean }) {
const [turns, setTurns] = useState([])
useEffect(() => {
if (!connected) return
let disposed = false
const poll = async () => {
if (document.visibilityState === "hidden") return
try {
const result = await getProfile(baseUrl, apiKey)
if (!disposed) setTurns(result.turns.map(derive))
} catch { /* engine busy or restarting — keep the last snapshot */ }
}
void poll()
const timer = window.setInterval(() => void poll(), 2000)
return () => { disposed = true; window.clearInterval(timer) }
}, [baseUrl, apiKey, connected])
const latest = turns[turns.length - 1]
const recent = turns.slice(-40)
const diskService = turns.reduce((sum, turn) => sum + turn.expert_disk_s, 0)
return (
Profiling — where the engine spends each turn
{PHASES.map((phase) => {phase.name} )}
{!latest ? (
{connected ? "No profiled turns yet — send a chat message and the breakdown appears here." : "Connect to the engine to collect per-turn timings."}
) : (
<>
Last turn{latest.toks.toFixed(1)} tok/s
Wall time{seconds(latest.wall_s)} {latest.prompt_tokens} → {latest.completion_tokens} tokens
Batching{latest.forwards > 0 ? (latest.completion_tokens / latest.forwards).toFixed(2) : "—"} tokens / forward
Disk service{seconds(latest.expert_disk_s)} overlapped with compute
{turns.length > 1 ? : null}
Throughput per turn (tok/s)
`${turn.toks.toFixed(1)} tok/s · ${turn.completion_tokens} tokens`} />
Turn wall time by phase (s)
`${seconds(turn.wall_s)} · ${PHASES.map((phase) => `${phase.name} ${seconds(turn[phase.key])}`).join(" · ")}`} />
Turn Tokens tok/s Wall {PHASES.map((phase) => {phase.name} )}Disk service
{recent.slice().reverse().map((turn, index) => (
{turns.length - index}
{turn.prompt_tokens} → {turn.completion_tokens}
{turn.toks.toFixed(1)}
{seconds(turn.wall_s)}
{PHASES.map((phase) => {seconds(turn[phase.key])} )}
{seconds(turn.expert_disk_s)}
))}
{diskService > 0 ?
Disk service is time spent reading experts on I/O threads; it overlaps with compute, so only the I/O wait the compute thread felt counts inside the wall-time stack. With multiple KV sessions the shares describe the whole engine over the turn's window.
: null}
>
)}
)
}