A resources timer samples host CPU, RAM, swap and per-session tokens into samples.jsonl; the hub serves them and a Resources view charts them. Committed below the bar on the operator's word 2026-11-12 (no further per-bundle tests, one gate after the merge): the W4 e2e never ran in a real browser. 🎯 Fix: 92 % 💥 Break: 6 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 86 🚦 Gate: ⛔ break 6 % > 5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
145 lines
5.4 KiB
JavaScript
145 lines
5.4 KiB
JavaScript
const Resources = (() => {
|
|
"use strict";
|
|
const IDLE = 30;
|
|
const SATURATED = 90;
|
|
const CALM = 50;
|
|
const GAP = 1800;
|
|
const STEP = 60;
|
|
const COUNTS = ["input", "output", "cache_read", "cache_write"];
|
|
const ZONES = [{kind: "idle", from: 0, to: IDLE}, {kind: "saturated", from: SATURATED, to: 100}];
|
|
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
const share = (part, whole) => (finite(part) && finite(whole) && whole > 0 ? Math.round((1000 * part) / whole) / 10 : null);
|
|
const listOf = (row, key) => (Array.isArray(row[key]) ? row[key].filter((item) => item && typeof item === "object") : []);
|
|
const timed = (samples) => samples.filter((row) => row && finite(row.t));
|
|
|
|
function series(samples, pick) {
|
|
const found = new Map();
|
|
for (const row of timed(samples)) {
|
|
for (const [name, kind, value] of pick(row)) {
|
|
const key = `${kind}\u0000${name}`;
|
|
if (!found.has(key)) {
|
|
found.set(key, {name: String(name), kind, points: []});
|
|
}
|
|
found.get(key).points.push([row.t, finite(value) ? value : null]);
|
|
}
|
|
}
|
|
return [...found.values()];
|
|
}
|
|
|
|
function lines(samples) {
|
|
return {
|
|
disks: series(samples, (row) => listOf(row, "disks").map((disk) => [disk.mount, "disk", share(disk.used, disk.total)])),
|
|
host: series(samples, (row) => [["CPU", "host", row.cpu], ["RAM", "host", row.ram], ["Swap", "host", row.swap]]),
|
|
gpu: series(samples, (row) => listOf(row, "gpus").flatMap((gpu) => [[gpu.name, "util", gpu.asleep ? null : gpu.util], [gpu.name, "vram", gpu.asleep ? null : share(gpu.vram_used, gpu.vram_total)]])),
|
|
};
|
|
}
|
|
|
|
function segments(points) {
|
|
const found = [];
|
|
let current = [];
|
|
let last = null;
|
|
for (const [t, value] of points) {
|
|
if (current.length && (value === null || t - last > GAP)) {
|
|
found.push(current);
|
|
current = [];
|
|
}
|
|
if (value !== null) {
|
|
current.push([t, value]);
|
|
}
|
|
last = t;
|
|
}
|
|
if (current.length) {
|
|
found.push(current);
|
|
}
|
|
return found;
|
|
}
|
|
|
|
function spans(samples, test, value) {
|
|
const rows = timed(samples);
|
|
const found = [];
|
|
let open = null;
|
|
rows.forEach((row, index) => {
|
|
if (open && index && row.t - rows[index - 1].t > GAP) {
|
|
found.push(open);
|
|
open = null;
|
|
}
|
|
if (!test(row)) {
|
|
if (open) {
|
|
found.push(open);
|
|
}
|
|
open = null;
|
|
return;
|
|
}
|
|
const next = rows[index + 1];
|
|
const end = row.t + (next && next.t - row.t <= GAP ? next.t - row.t : STEP);
|
|
const peak = value(row);
|
|
open = open ? {from: open.from, to: end, peak: Math.max(open.peak, peak)} : {from: row.t, to: end, peak};
|
|
});
|
|
if (open) {
|
|
found.push(open);
|
|
}
|
|
return found;
|
|
}
|
|
|
|
function waits(samples) {
|
|
const waiting = (row) => (row.queue && finite(row.queue.waiting) ? row.queue.waiting : 0);
|
|
return spans(samples, (row) => waiting(row) > 0 && finite(row.cpu) && finite(row.ram) && row.cpu < CALM && row.ram < CALM, waiting);
|
|
}
|
|
|
|
function gpuSpans(samples, test) {
|
|
const names = [...new Set(timed(samples).flatMap((row) => listOf(row, "gpus").filter(test).map((gpu) => String(gpu.name))))];
|
|
return names.map((name) => ({name, spans: spans(samples, (row) => listOf(row, "gpus").some((gpu) => String(gpu.name) === name && test(gpu)), () => 1)}));
|
|
}
|
|
|
|
const asleep = (samples) => gpuSpans(samples, (gpu) => gpu.asleep === true);
|
|
const unknown = (samples) => gpuSpans(samples, (gpu) => gpu.asleep !== true && !finite(gpu.util) && !finite(gpu.vram_used));
|
|
|
|
function stack(tokens) {
|
|
const totals = new Map();
|
|
const hours = new Map();
|
|
for (const row of tokens) {
|
|
if (!row || !finite(row.t) || typeof row.agent !== "string") {
|
|
continue;
|
|
}
|
|
const counts = COUNTS.map((name) => (finite(row[name]) && row[name] > 0 ? row[name] : 0));
|
|
const sum = counts.reduce((total, value) => total + value, 0);
|
|
if (!totals.has(row.agent)) {
|
|
totals.set(row.agent, 0);
|
|
}
|
|
totals.set(row.agent, totals.get(row.agent) + sum);
|
|
if (!hours.has(row.t)) {
|
|
hours.set(row.t, new Map());
|
|
}
|
|
const hour = hours.get(row.t);
|
|
if (!hour.has(row.agent)) {
|
|
hour.set(row.agent, {agent: row.agent, total: 0, models: [], ...Object.fromEntries(COUNTS.map((name) => [name, 0]))});
|
|
}
|
|
const entry = hour.get(row.agent);
|
|
COUNTS.forEach((name, index) => {
|
|
entry[name] += counts[index];
|
|
});
|
|
entry.total += sum;
|
|
if (typeof row.model === "string" && !entry.models.includes(row.model)) {
|
|
entry.models.push(row.model);
|
|
}
|
|
}
|
|
const agents = [...totals].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([agent]) => agent);
|
|
const bars = [...hours.keys()].sort((a, b) => a - b).map((t) => {
|
|
let base = 0;
|
|
const parts = agents.filter((agent) => hours.get(t).has(agent)).map((agent) => {
|
|
const entry = hours.get(t).get(agent);
|
|
const part = {...entry, from: base, to: base + entry.total};
|
|
base += entry.total;
|
|
return part;
|
|
});
|
|
return {t, total: base, parts};
|
|
});
|
|
return {agents, totals: Object.fromEntries(totals), bars, top: Math.max(0, ...bars.map((bar) => bar.total))};
|
|
}
|
|
|
|
return {IDLE, SATURATED, CALM, GAP, COUNTS, ZONES, lines, segments, spans, waits, asleep, unknown, stack};
|
|
})();
|
|
if (typeof module === "object" && module.exports) {
|
|
module.exports = Resources;
|
|
}
|