import { spawnSync } from "node:child_process"; type CommandCase = { name: string; args: string[]; }; type Sample = { ms: number; exitCode: number | null; signal: NodeJS.Signals | null; }; type CaseSummary = ReturnType; const DEFAULT_RUNS = 8; const DEFAULT_TIMEOUT_MS = 30_000; const DEFAULT_ENTRY = "dist/entry.js"; const DEFAULT_CASES: CommandCase[] = [ { name: "--version", args: ["--version"] }, { name: "--help", args: ["--help"] }, { name: "health --json", args: ["health", "--json"] }, { name: "status --json", args: ["status", "--json"] }, { name: "status", args: ["status"] }, ]; function parseFlagValue(flag: string): string | undefined { const idx = process.argv.indexOf(flag); if (idx === -1) { return undefined; } return process.argv[idx + 1]; } function parsePositiveInt(raw: string | undefined, fallback: number): number { if (!raw) { return fallback; } const parsed = Number.parseInt(raw, 10); if (!Number.isFinite(parsed) || parsed <= 0) { return fallback; } return parsed; } function median(values: number[]): number { if (values.length === 0) { return 0; } const sorted = [...values].toSorted((a, b) => a - b); const mid = Math.floor(sorted.length / 2); if (sorted.length % 2 === 0) { return (sorted[mid - 1] + sorted[mid]) / 2; } return sorted[mid]; } function percentile(values: number[], p: number): number { if (values.length === 0) { return 0; } const sorted = [...values].toSorted((a, b) => a - b); const index = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length)); return sorted[index]; } function runCase(params: { entry: string; runCase: CommandCase; runs: number; timeoutMs: number; }): Sample[] { const results: Sample[] = []; for (let i = 0; i < params.runs; i += 1) { const started = process.hrtime.bigint(); const proc = spawnSync(process.execPath, [params.entry, ...params.runCase.args], { cwd: process.cwd(), env: { ...process.env, OPENCLAW_HIDE_BANNER: "1", }, stdio: ["ignore", "ignore", "pipe"], encoding: "utf8", timeout: params.timeoutMs, maxBuffer: 16 * 1024 * 1024, }); const ms = Number(process.hrtime.bigint() - started) / 1e6; results.push({ ms, exitCode: proc.status, signal: proc.signal, }); } return results; } function summarize(samples: Sample[]) { const values = samples.map((entry) => entry.ms); const total = values.reduce((sum, value) => sum + value, 0); const avg = values.length > 0 ? total / values.length : 0; const min = values.length > 0 ? Math.min(...values) : 0; const max = values.length > 0 ? Math.max(...values) : 0; return { avg, p50: median(values), p95: percentile(values, 95), min, max, }; } function formatMs(value: number): string { return `${value.toFixed(1)}ms`; } function collectExitSummary(samples: Sample[]): string { const buckets = new Map(); for (const sample of samples) { const key = sample.signal != null ? `signal:${sample.signal}` : `code:${sample.exitCode == null ? "null" : String(sample.exitCode)}`; buckets.set(key, (buckets.get(key) ?? 0) + 1); } return [...buckets.entries()].map(([key, count]) => `${key}x${count}`).join(", "); } function printSuite(params: { title: string; entry: string; runs: number; timeoutMs: number; }): Map { console.log(params.title); console.log(`Entry: ${params.entry}`); const suite = new Map(); for (const commandCase of DEFAULT_CASES) { const samples = runCase({ entry: params.entry, runCase: commandCase, runs: params.runs, timeoutMs: params.timeoutMs, }); const stats = summarize(samples); const exitSummary = collectExitSummary(samples); suite.set(commandCase.name, stats); console.log( `${commandCase.name.padEnd(13)} avg=${formatMs(stats.avg)} p50=${formatMs(stats.p50)} p95=${formatMs(stats.p95)} min=${formatMs(stats.min)} max=${formatMs(stats.max)} exits=[${exitSummary}]`, ); } console.log(""); return suite; } async function main(): Promise { const entryPrimary = parseFlagValue("--entry-primary") ?? parseFlagValue("--entry") ?? DEFAULT_ENTRY; const entrySecondary = parseFlagValue("--entry-secondary"); const runs = parsePositiveInt(parseFlagValue("--runs"), DEFAULT_RUNS); const timeoutMs = parsePositiveInt(parseFlagValue("--timeout-ms"), DEFAULT_TIMEOUT_MS); console.log(`Node: ${process.version}`); console.log(`Runs per command: ${runs}`); console.log(`Timeout: ${timeoutMs}ms`); console.log(""); const primaryResults = printSuite({ title: "Primary entry", entry: entryPrimary, runs, timeoutMs, }); if (entrySecondary) { const secondaryResults = printSuite({ title: "Secondary entry", entry: entrySecondary, runs, timeoutMs, }); console.log("Delta (secondary - primary, avg)"); for (const commandCase of DEFAULT_CASES) { const primary = primaryResults.get(commandCase.name); const secondary = secondaryResults.get(commandCase.name); if (!primary || !secondary) { continue; } const delta = secondary.avg - primary.avg; const pct = primary.avg > 0 ? (delta / primary.avg) * 100 : 0; const sign = delta > 0 ? "+" : ""; console.log( `${commandCase.name.padEnd(13)} ${sign}${formatMs(delta)} (${sign}${pct.toFixed(1)}%)`, ); } } } await main();