Add real-time progress feedback to multi-run simulations
Switch from exec() to spawn() for streaming stderr, add onProgress callback to runner, and emit per-run progress lines from workers. CI now shows live percentage, tick count, and era during long runs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { exec } from 'node:child_process';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { resolve as pathResolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -74,20 +74,30 @@ interface WorkerResult {
|
||||
function spawnWorker(runId: number, seed: number): Promise<WorkerResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const workerPath = new URL('./worker.ts', import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1');
|
||||
const cmd = `npx tsx "${workerPath}" --strategy ${strategyName} --ticks ${totalTicks} --seed ${seed} --run-id ${runId}`;
|
||||
exec(cmd, { maxBuffer: 200 * 1024 * 1024 }, (error, stdout, stderr) => {
|
||||
if (stderr) process.stderr.write(stderr);
|
||||
if (error) {
|
||||
reject(new Error(`Run #${runId} (seed ${seed}) failed: ${error.message}`));
|
||||
const tsxBin = pathResolve(__dirname, '..', 'node_modules', '.bin', 'tsx');
|
||||
const child = spawn(tsxBin, [workerPath, '--strategy', strategyName, '--ticks', String(totalTicks), '--seed', String(seed), '--run-id', String(runId)], {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
child.stdout.on('data', (chunk: Buffer) => { stdout += chunk; });
|
||||
child.stderr.on('data', (chunk: Buffer) => { process.stderr.write(chunk); });
|
||||
|
||||
child.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new Error(`Run #${runId} (seed ${seed}) exited with code ${code}`));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = JSON.parse(stdout) as WorkerResult;
|
||||
resolve(result);
|
||||
resolve(JSON.parse(stdout) as WorkerResult);
|
||||
} catch (e) {
|
||||
reject(new Error(`Run #${runId} (seed ${seed}) produced invalid JSON: ${(e as Error).message}`));
|
||||
}
|
||||
});
|
||||
|
||||
child.on('error', (err) => {
|
||||
reject(new Error(`Run #${runId} (seed ${seed}) failed to spawn: ${err.message}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface SimulationConfig {
|
||||
seed?: number;
|
||||
verbose?: boolean;
|
||||
silent?: boolean;
|
||||
onProgress?: (tick: number, totalTicks: number, era: string) => void;
|
||||
}
|
||||
|
||||
export interface EraTransition {
|
||||
@@ -139,8 +140,11 @@ export function runSimulation(config: SimulationConfig): SimulationResult {
|
||||
allMetrics.push(collectMetrics(state));
|
||||
}
|
||||
|
||||
if (!config.silent && tick % progressInterval === 0) {
|
||||
printProgress(tick, config.totalTicks, state, startTime);
|
||||
if (tick % progressInterval === 0) {
|
||||
if (!config.silent) {
|
||||
printProgress(tick, config.totalTicks, state, startTime);
|
||||
}
|
||||
config.onProgress?.(tick, config.totalTicks, state.meta.currentEra);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,18 @@ const strategy = strategyName === 'random' ? new RandomStrategy()
|
||||
|
||||
process.stderr.write(`[Run #${runId}] Starting (seed ${seed}, ${totalTicks} ticks, ${strategyName})...\n`);
|
||||
|
||||
const result = runSimulation({ totalTicks, decisionInterval, strategy, seed, verbose: false, silent: true });
|
||||
const result = runSimulation({
|
||||
totalTicks,
|
||||
decisionInterval,
|
||||
strategy,
|
||||
seed,
|
||||
verbose: false,
|
||||
silent: true,
|
||||
onProgress: (tick, total, era) => {
|
||||
const pct = ((tick / total) * 100).toFixed(0);
|
||||
process.stderr.write(`[Run #${runId}] ${pct}% (tick ${tick.toLocaleString()}/${total.toLocaleString()}) — ${era}\n`);
|
||||
},
|
||||
});
|
||||
const report = generateJsonReport(result, { totalTicks, decisionInterval, strategy, seed });
|
||||
|
||||
const output = {
|
||||
|
||||
Reference in New Issue
Block a user