// Runs the in-process simulator (vendor worktree crate igneum/harness-sim) and reads its JSON report. import { spawnSync, spawn } from 'node:child_process'; import { readFileSync, mkdirSync, existsSync } from 'node:fs'; import { SIM, TMP, log, FAST_TIME, FAST_TIME_FILE } from './net.mjs'; // Under --fast-time the simulator runs the 60x profile too (igneum-harness-sim --override-params-file). const fastArgs = () => (FAST_TIME ? ['--override-params-file', FAST_TIME_FILE] : []); export function runSim(name, args, { timeoutMs = 60 * 60_000 } = {}) { if (!existsSync(SIM)) throw new Error(`missing ${SIM}; build igneum-harness-sim in the harness worktree`); mkdirSync(`${TMP}/sim`, { recursive: true }); const out = `${TMP}/sim/${name}.json`; const argv = [...args, ...fastArgs(), '--out', out]; log(`sim ${name}: igneum-harness-sim ${argv.join(' ')}`); const t0 = Date.now(); const r = spawnSync('nice', ['-n', '19', SIM, ...argv], { encoding: 'utf8', timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024 }); if (!existsSync(out)) throw new Error(`sim ${name} produced no report: ${(r.stderr || '').slice(-400)}`); const report = JSON.parse(readFileSync(out, 'utf8')); report.wall_s = (Date.now() - t0) / 1000; log(`sim ${name}: ${report.end_time_s} virtual s in ${report.wall_s.toFixed(0)} s wall`); return report; } /// Several simulations at once (each uses a couple of threads); returns reports in order. export async function runSims(jobs, { parallel = 3 } = {}) { const results = new Array(jobs.length); let next = 0; async function worker() { while (next < jobs.length) { const i = next++; const { name, args } = jobs[i]; mkdirSync(`${TMP}/sim`, { recursive: true }); const out = `${TMP}/sim/${name}.json`; const argv = [...args, ...fastArgs(), '--out', out]; log(`sim ${name}: igneum-harness-sim ${argv.join(' ')}`); const t0 = Date.now(); await new Promise((resolve) => { const p = spawn('nice', ['-n', '19', SIM, ...argv], { stdio: ['ignore', 'ignore', 'pipe'] }); let err = ''; p.stderr.on('data', d => { err += d; if (err.length > 4000) err = err.slice(-4000); }); p.on('exit', () => { if (!existsSync(out)) log(`sim ${name} FAILED: ${err.slice(-300)}`); resolve(); }); }); if (existsSync(out)) { results[i] = JSON.parse(readFileSync(out, 'utf8')); results[i].wall_s = (Date.now() - t0) / 1000; log(`sim ${name}: ${results[i].end_time_s} virtual s in ${results[i].wall_s.toFixed(0)} s wall`); } } } await Promise.all(Array.from({ length: Math.min(parallel, jobs.length) }, worker)); return results; } export function hist(h) { return Object.entries(h || {}).map(([d, n]) => `${d}:${n}`).join(' ') || 'none'; } export function maxDepth(h) { return Math.max(0, ...Object.keys(h || {}).map(Number)); }