igneum/tools/proving-v1/miner-rate.mjs
igneum-labs 7dade79bcb Aggregation cost on the 5090 (5 October, night): the chained aggregation is 2.1 s alone and 9.7 s beside the miner, the batch-log2 curve (2^16 buys 1.6x for a fifth of the hash rate), batch and tree folds estimated, two streams and SP1 knobs closed; the host times the stdin build and names the knobs, --save-shards; the PC 2 job scripts and the readers
The statement and the pinned guests are unchanged; every fixture proof verifies as before. The defaults stay (batch-log2 22, SP1 defaults): the one knob that moves a mining card's prover costs a fifth of the hash rate; the plan carries the trade for the project lead and the batch fold for the next pin. Measured: docs/bench-log.md "aggregation cost on the RTX 5090"; the plan line: docs/plans/proving-v1.md "Aggregation cost (5 October, night)". Also: make-package's gate skips the exporter's .node-plan.json side files and takes the run lock for its execute step; the state-reply class (/api/state answering {} once paid_wei passes u64::MAX) found on the way and fixed on the app branch at 42f36b3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit ea38ece9eaa5949dd657cbfea5308c4948177ff8)
2026-10-06 07:04:50 +00:00

35 lines
2.5 KiB
JavaScript

#!/usr/bin/env node
// The hash rate of one miner over a UTC window, from its STATUS lines in the log intake (the app uploads the miner's
// log every minute; each STATUS line starts with a unix timestamp and carries `now=<MH/s> MH/s wall`, the last
// interval's rate). For the aggregation-cost phases that keep the app's own miner running (docs/bench-log.md,
// 5 October 2026 night), where the job cannot read the app's state from PowerShell 5.1.
// node tools/proving-v1/miner-rate.mjs <label> <start ISO> <end ISO> e.g. miner-nvidia-1ccfe586-1 2026-10-05T21:00:00Z 2026-10-05T21:05:00Z
import { readFileSync } from 'node:fs';
import { homedir } from 'node:os';
const [label, start, end] = process.argv.slice(2);
if (!label || !start || !end) { console.error('usage: miner-rate.mjs <label> <start ISO> <end ISO>'); process.exit(2); }
const m = /^DATABASE_URL=(.*)$/m.exec(readFileSync(`${homedir()}/.config/igneum/env`, 'utf8'));
const url = m[1].trim().replace(/^['"]|['"]$/g, '');
const host = new URL(url).hostname.replace('-pooler', '');
const sql = async (query, params = []) => {
const r = await fetch(`https://${host}/sql`, { method: 'POST', headers: { 'Neon-Connection-String': url, 'Content-Type': 'application/json' }, body: JSON.stringify({ query, params }) });
const j = await r.json();
if (!r.ok) throw new Error(j.message || JSON.stringify(j));
return j.rows;
};
const t0 = Date.parse(start) / 1000, t1 = Date.parse(end) / 1000;
// every upload received up to 15 min after the window (the log is re-uploaded whole, so the newest covering upload holds every line)
const rows = await sql(`SELECT received_at, lines FROM miner_logs WHERE label = $1 AND received_at > to_timestamp($2) AND received_at < to_timestamp($3) ORDER BY received_at DESC LIMIT 20`, [label, t0, t1 + 900]);
const seen = new Map();
for (const r of rows) {
for (const l of r.lines.split('\n')) {
const mm = /^(\d+(?:\.\d+)?) STATUS .* now=([\d.]+) MH\/s wall/.exec(l);
if (!mm) continue;
const t = Number(mm[1]);
if (t >= t0 && t <= t1) seen.set(mm[1], Number(mm[2]));
}
}
const h = [...seen.values()].sort((a, b) => a - b);
if (!h.length) { console.log(`${label} ${start}..${end}: no STATUS lines in the window (${rows.length} uploads read)`); process.exit(1); }
const mean = h.reduce((a, b) => a + b, 0) / h.length;
console.log(`${label} ${start}..${end}: n=${h.length} mean=${mean.toFixed(2)} p50=${h[Math.floor((h.length - 1) / 2)].toFixed(2)} min=${h[0].toFixed(2)} max=${h[h.length - 1].toFixed(2)} MH/s wall (now= of the STATUS lines, ${rows.length} uploads read)`);