import { cn } from "../lib/cn";
import { formatBytes } from "../lib/format";
import type { MetricSample } from "../types/machines";
import { useOpenGeniText } from "../messages/runtime";
export type MachineMetricsProps = {
/** The latest metric sample, or null when the machine hasn't reported yet. */
metrics: MetricSample | null;
/** Compact (dashboard row) vs full (the dock detail). */
density?: "compact" | "full" | undefined;
className?: string | undefined;
};
/** Clamp a 0..100-ish percent into the bar range. */
function pct(value: number): number {
if (!Number.isFinite(value)) return 0;
return Math.max(0, Math.min(100, value));
}
/** A label/value pair with a token-tinted utilisation bar. */
function Meter({
label,
value,
fillPct,
tone,
}: {
label: string;
value: string;
fillPct: number;
tone: "ok" | "warn" | "hot";
}) {
// Traffic-light ramp, aligned with the fused health verdict: calm resources
// read green (idle), pressure ambers (running), near-the-wall reds (failed).
const fillClass =
tone === "hot"
? "bg-og-status-failed"
: tone === "warn"
? "bg-og-status-running"
: "bg-og-status-idle";
return (
);
}
/** Map a percent to a calm/warn/hot tone (no color invented — token classes). */
function toneFor(p: number): "ok" | "warn" | "hot" {
if (p >= 90) return "hot";
if (p >= 70) return "warn";
return "ok";
}
/**
* Load average as three labeled mini-stats (1m / 5m / 15m) — an honest triple of
* numbers, NOT a fake gauge. Load is not a 0..100 ratio and we don't know the
* core count, so a raw load of 2 or 8 is not inherently "bad" — we render it
* NEUTRAL rather than falsely alarming. The real saturation signal is the run
* queue, which rides alongside as a quiet chip when there's pending work.
*/
function StatTriple({
load1,
load5,
load15,
runQueue,
}: {
load1: number;
load5: number;
load15: number;
runQueue: number;
}) {
const t = useOpenGeniText();
const stats: Array<{ label: string; value: number }> = [
{ label: "1m", value: load1 },
{ label: "5m", value: load5 },
{ label: "15m", value: load15 },
];
return (
{t("machineMetrics.load")}
{stats.map((s) => (
{s.label}
{s.value.toFixed(2)}
))}
{runQueue > 0 ? (
{t("machineMetrics.queue")}
{runQueue}
) : null}
);
}
/**
* Per-machine resource meters: CPU%, load average, memory, disk, and GPU when
* present. Renders the M2 `machine_metrics_latest` sample. When `metrics` is
* null the panel shows a quiet "no samples yet" placeholder (offline / just
* enrolled). GPU rows only render when `gpuUtilPct` is non-null.
*/
export function MachineMetrics({ metrics, density = "compact", className }: MachineMetricsProps) {
const t = useOpenGeniText();
if (!metrics) {
return (
{t("machineMetrics.noMetricsYet")}
);
}
const memPct =
metrics.memTotalBytes > 0 ? (metrics.memUsedBytes / metrics.memTotalBytes) * 100 : 0;
const diskPct =
metrics.diskTotalBytes > 0 ? (metrics.diskUsedBytes / metrics.diskTotalBytes) * 100 : 0;
const hasGpu = metrics.gpuUtilPct !== null;
const gpuMemLabel = metrics.gpuMemBytes !== null ? formatBytes(metrics.gpuMemBytes) : null;
// Memory & disk are honest used/total ratios → full-width meters; stack them in
// the dock detail, two-up in the dashboard row.
const ratioGridClass = density === "full" ? "grid-cols-1" : "grid-cols-1 sm:grid-cols-2";
return (
{/* Resources — used/total ratios with real 0..100% bars. */}
{/* Utilization — CPU% (and GPU% when present) as honest percent gauges. */}
{hasGpu ? (
) : null}
{/* Activity — load average as labeled mini-stats (no fake bar) + run queue. */}
);
}