mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
103 lines
4.4 KiB
JavaScript
103 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
// Phase 0 baseline: parse the four boot milestones out of backend.log and prove
|
|
// they are present and correctly ordered. main.js emits one line per milestone:
|
|
// [perf] <name> t=<ms-since-launch>
|
|
// We read the most recent launch block (delimited by "===== launch ... =====")
|
|
// so a re-run measures the latest boot, not a stale one.
|
|
//
|
|
// Usage:
|
|
// node scripts/perf/parse-timing.js [--log <path>] [--json]
|
|
//
|
|
// Exit 0 if all four present and ordered, 1 otherwise. Designed to be the
|
|
// assertion step of the Phase 0 test: launch the packaged app, then run this.
|
|
|
|
'use strict';
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
// Canonical order. app-launch must be first and first-agent-response last;
|
|
// first-paint and backend-http-ready sit between and may swap (lazy backend
|
|
// paints the shell before Python answers, eager backend does the reverse), so
|
|
// we only require they fall between the bookends, not a fixed order vs each other.
|
|
const REQUIRED = ['app-launch', 'first-paint', 'backend-http-ready', 'first-agent-response'];
|
|
|
|
function parseArgs(argv) {
|
|
const out = { log: null, json: false };
|
|
for (let i = 0; i < argv.length; i++) {
|
|
if (argv[i] === '--log') out.log = argv[++i];
|
|
else if (argv[i] === '--json') out.json = true;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Mirrors getAuthTokenFilePath()/getBackendLogPath() in electron/main.js.
|
|
function defaultLogPath() {
|
|
if (process.platform === 'darwin') {
|
|
return path.join(os.homedir(), 'Library', 'Application Support', 'OpenSwarm', 'data', 'backend.log');
|
|
} else if (process.platform === 'win32') {
|
|
return path.join(process.env.APPDATA || os.homedir(), 'OpenSwarm', 'data', 'backend.log');
|
|
}
|
|
const xdg = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
|
|
return path.join(xdg, 'OpenSwarm', 'data', 'backend.log');
|
|
}
|
|
|
|
function fail(msg, report) {
|
|
if (report && report.json) { process.stdout.write(JSON.stringify({ ok: false, error: msg, ...report.data }, null, 2) + '\n'); }
|
|
else process.stderr.write(`FAIL: ${msg}\n`);
|
|
process.exit(1);
|
|
}
|
|
|
|
function main() {
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const logPath = args.log ? path.resolve(args.log) : defaultLogPath();
|
|
|
|
let raw;
|
|
try { raw = fs.readFileSync(logPath, 'utf8'); }
|
|
catch { fail(`cannot read log at ${logPath} (launch the packaged app first)`, { json: args.json, data: { logPath } }); return; }
|
|
|
|
// Take only the last launch block so we measure the most recent boot.
|
|
const marker = '===== launch';
|
|
const lastIdx = raw.lastIndexOf(marker);
|
|
const block = lastIdx >= 0 ? raw.slice(lastIdx) : raw;
|
|
|
|
const marks = {};
|
|
const re = /\[perf\]\s+(\S+)\s+t=(\d+)/g;
|
|
let m;
|
|
while ((m = re.exec(block)) !== null) {
|
|
// First occurrence wins (matches main.js one-shot guard); ignore later dupes.
|
|
if (!(m[1] in marks)) marks[m[1]] = Number(m[2]);
|
|
}
|
|
|
|
const report = { json: args.json, data: { logPath, marks } };
|
|
const missing = REQUIRED.filter((k) => !(k in marks));
|
|
if (missing.length) fail(`missing milestone(s): ${missing.join(', ')}`, report);
|
|
|
|
// Bookends: app-launch earliest, first-agent-response latest.
|
|
const t = marks;
|
|
if (t['app-launch'] !== Math.min(...REQUIRED.map((k) => t[k]))) fail('app-launch is not the earliest milestone', report);
|
|
if (t['first-agent-response'] !== Math.max(...REQUIRED.map((k) => t[k]))) fail('first-agent-response is not the latest milestone', report);
|
|
for (const k of REQUIRED) if (!Number.isFinite(t[k]) || t[k] < 0) fail(`milestone ${k} has invalid t=${t[k]}`, report);
|
|
|
|
const ordered = [...REQUIRED].sort((a, b) => t[a] - t[b]);
|
|
const result = {
|
|
ok: true,
|
|
logPath,
|
|
marks,
|
|
order: ordered.map((k) => `${k}@${t[k]}ms`),
|
|
durations: {
|
|
'launch->first-paint': t['first-paint'] - t['app-launch'],
|
|
'launch->backend-ready': t['backend-http-ready'] - t['app-launch'],
|
|
'launch->first-agent-response': t['first-agent-response'] - t['app-launch'],
|
|
},
|
|
};
|
|
|
|
if (args.json) { process.stdout.write(JSON.stringify(result, null, 2) + '\n'); return; }
|
|
process.stdout.write('\nPASS: all four boot milestones present and ordered.\n');
|
|
process.stdout.write(` log: ${logPath}\n`);
|
|
for (const k of ordered) process.stdout.write(` ${k.padEnd(22)} ${String(t[k]).padStart(8)} ms\n`);
|
|
process.stdout.write(`\n launch -> first-agent-response: ${result.durations['launch->first-agent-response']} ms\n\n`);
|
|
}
|
|
|
|
main();
|