mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-10-17 13:19:18 +02:00
Phase 1 of the live benchmark-run experience. Replaces the opaque (and in sync mode, simulated) progress bar with a real-time run view driven by actual host telemetry. - Async run path: the UI now dispatches to the queue worker and keys off SSE instead of faking stage progress with client-side timers. - New BenchmarkTelemetrySampler broadcasts per-core CPU load, CPU temp (best-effort, hidden when unavailable), and disk MB/s at 1 Hz over a new benchmark-telemetry SSE channel. Runs in the orchestration process, never the sysbench container, so it cannot affect scores. - BenchmarkProgress carries the ordered stage plan + index so the frontend renders a live stage rail. - AI benchmark streams /api/generate for live tokens/sec and true TTFT; the scored numbers still come from Ollama's authoritative final eval fields. - Frontend: useBenchmarkRun hook owns both subscriptions; self-contained SVG components (StageRail, CoreGrid, Sparkline, LiveReadout) + BenchmarkRunView, styled in the desert palette. No chart library added. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
60 lines
1.7 KiB
TypeScript
60 lines
1.7 KiB
TypeScript
import classNames from '~/lib/classNames'
|
|
|
|
interface CoreGridProps {
|
|
/** Per-thread load, 0-100. */
|
|
loads: number[]
|
|
}
|
|
|
|
const MAX_CELLS = 64
|
|
|
|
// Colour ramp by load: idle olive -> warm orange -> hot red. Cells transition
|
|
// smoothly as load climbs, so a single-thread pass lights one cell and an
|
|
// all-threads pass lights the whole grid.
|
|
function cellClass(load: number): string {
|
|
if (load >= 80) return 'bg-desert-red'
|
|
if (load >= 55) return 'bg-desert-orange'
|
|
if (load >= 25) return 'bg-desert-olive'
|
|
if (load >= 8) return 'bg-desert-green'
|
|
return 'bg-desert-green/15'
|
|
}
|
|
|
|
export default function CoreGrid({ loads }: CoreGridProps) {
|
|
// Fold very high core counts down to MAX_CELLS by averaging groups.
|
|
let cells = loads
|
|
let grouped = 1
|
|
if (loads.length > MAX_CELLS) {
|
|
grouped = Math.ceil(loads.length / MAX_CELLS)
|
|
cells = []
|
|
for (let i = 0; i < loads.length; i += grouped) {
|
|
const slice = loads.slice(i, i + grouped)
|
|
cells.push(slice.reduce((a, b) => a + b, 0) / slice.length)
|
|
}
|
|
}
|
|
|
|
const cols = Math.min(cells.length, 16) || 1
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
<div
|
|
className="grid gap-1.5"
|
|
style={{ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }}
|
|
>
|
|
{cells.map((load, i) => (
|
|
<div
|
|
key={i}
|
|
title={`Thread ${i + 1}: ${Math.round(load)}%`}
|
|
className={classNames(
|
|
'aspect-square rounded-sm transition-colors duration-300',
|
|
cellClass(load)
|
|
)}
|
|
/>
|
|
))}
|
|
</div>
|
|
<div className="text-xs text-desert-stone-dark font-mono">
|
|
{loads.length} thread{loads.length === 1 ? '' : 's'}
|
|
{grouped > 1 ? ` (${grouped}/cell)` : ''}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|