igneum/relay/lib/parse.mjs

63 lines
4.1 KiB
JavaScript

// Pure parsers of the console (no dependencies, so `node --test relay/test` covers them): the log-intake labels,
// a miner log's tail, and the stale mark on a card whose last STATUS line is old.
export const kv = (text, k) => { const m = new RegExp(`(?:^|[\\s(])${k}=([^\\s,)]+)`).exec(text); return m ? m[1] : null; };
export const kvNum = (text, k) => { const v = kv(text, k); return v === null ? null : Number(v); };
export const lastMatch = (lines, re) => { for (let i = lines.length - 1; i >= 0; i--) { const m = re.exec(lines[i]); if (m) return m; } return null; };
export const FAULT = /WORKER MISMATCH|worker error|worker exited|panicked|CUDA error|submit error/;
// A card's hash is "now" only while its worker's STATUS line (every 30 s) is this fresh; older than this the card is
// marked stale and its hash leaves the machine total (4 October 2026: PC 2 showed 117 MH/s "now" from a STATUS line
// four minutes old while the shard prover held the GPU).
export const STALE_S = 120;
export function markStale(cards, now = Date.now()) {
for (const c of cards) {
const age = c.status_at ? Math.round((now - Date.parse(c.status_at)) / 1000) : null;
c.status_age_s = age;
c.stale = age === null || age > STALE_S;
}
return cards;
}
// Labels the app uploads: nodelog-<os>-<id8>, miner-<vendor>-<id8>-<n> (vendor as the app names the worker: nvidia, amd,
// mac/metal, opencl, and other/intel for the OpenCL fallback on an iGPU; 4 October 2026: PC 37ba0461's other-* worker
// was dropped and the card showed 0 MH/s), <os>-<id8> for the app log; the launchers before the app are "legacy".
export function parseLabel(label, machine) {
let m;
if ((m = /^nodelog-(win|mac)-([0-9a-f]{8})$/.exec(label))) return { id: m[2], platform: m[1], stream: 'node' };
if ((m = /^miner-(nvidia|amd|mac|metal|opencl|other|intel)-([0-9a-f]{8})-(\d+)$/.exec(label))) return { id: m[2], platform: m[1] === 'mac' || m[1] === 'metal' ? 'mac' : 'win', stream: 'miner', vendor: m[1], card: Number(m[3]) };
if ((m = /^(win|mac)-([0-9a-f]{8})$/.exec(label))) return { id: m[2], platform: m[1], stream: 'app' };
// the launchers before the app: nodelog-<host>, nvidia-<host>[-n], amd-<host>[-n], igneum-<host>, launcher-<host>
if ((m = /^nodelog-(.+)$/.exec(label))) return { id: 'legacy:' + m[1], platform: 'legacy', stream: 'node' };
if ((m = /^(nvidia|amd)-(.+?)(?:-(\d+))?$/.exec(label))) return { id: 'legacy:' + m[2], platform: 'legacy', stream: 'miner', vendor: m[1], card: Number(m[3] || 1) };
if ((m = /^(igneum|launcher)-(.+)$/.exec(label))) return { id: 'legacy:' + m[2], platform: 'legacy', stream: 'app' };
return null;
}
export function parseMinerTail(tail) {
const lines = tail.split('\n');
const c = { hash_now: 0, hash_avg: 0, accepted: 0, rejected: 0, mismatched: 0, restarts: 0, identities: null, synced: null, uptime_s: null, status_at: null, fault: null, faults: 0, template_age_s: null };
const st = lastMatch(lines, /^(\d+(?:\.\d+)?) STATUS '([^']+)'.*$/);
if (st) {
const t = st[0];
c.status_at = new Date(Number(st[1]) * 1000).toISOString();
c.hash_avg = kvNum(t, 'hash') ?? 0;
c.hash_now = kvNum(t, 'now') ?? c.hash_avg;
c.accepted = kvNum(t, 'accepted') ?? 0;
c.rejected = kvNum(t, 'rejected') ?? 0;
c.mismatched = kvNum(t, 'mismatched') ?? 0;
c.restarts = kvNum(t, 'restarts') ?? 0;
c.identities = kvNum(t, 'identities');
c.synced = kv(t, 'synced') === 'true';
c.template_age_s = (() => { const v = kv(t, 'template_age'); return v ? Number(v.replace('s', '')) : null; })();
const up = /STATUS '[^']+' \[[^\]]*\]: (\d+)s /.exec(t); if (up) c.uptime_s = Number(up[1]);
} else {
// no STATUS line in the tail: count the ACCEPTED lines at least
c.accepted = lines.filter(l => l.includes(' ACCEPTED block')).length;
}
const faults = lines.filter(l => FAULT.test(l));
c.faults = faults.length;
if (faults.length) c.fault = faults[faults.length - 1].replace(/^\d+(\.\d+)? /, '').slice(0, 200);
const acc = lastMatch(lines, /^(\d+(?:\.\d+)?) ACCEPTED block/);
if (acc) c.last_accepted_at = new Date(Number(acc[1]) * 1000).toISOString();
return c;
}