mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-03 19:11:38 +08:00
* docs: record regression audit and validation * test: cover audited call and extraction regressions * test: cover audited calls and selector guards * test: cover audited calls and selector guards * test: cover audited calls and selector guards * test: cover audited calls and selector guards * test: cover audited calls and selector guards * fix: retain qualified call sites and restore language-specific getters * fix: resolve typed calls and bound store actions * fix: retain qualified call sites in the native kernel * docs: explain restored call and Steps coverage * test: isolate watchdog and cover imported store aliases * refactor: expose import lookup through resolver context * refactor: remove import resolver cycle * test: canonicalize Claude config temp paths * docs: record review fixes and large TypeScript timings * test: cover store cache edits and document verification * perf: cache per-file store eligibility * fix: observe large benchmarks without a wall timeout * docs: explain completed indexing benchmarks docs: explain completed indexing benchmarks
129 lines
6.8 KiB
JavaScript
129 lines
6.8 KiB
JavaScript
// Standalone benchmark harness; see docs/benchmarks/regression-audit-2026-09.md.
|
|
// Arguments: built engine directory, fixture root, existing output directory, native|wasm.
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { performance, monitorEventLoopDelay } = require('node:perf_hooks');
|
|
const { DatabaseSync } = require('node:sqlite');
|
|
const [engine, root, out, backend] = process.argv.slice(2);
|
|
if (!engine || !root || !out || !['native', 'wasm'].includes(backend)) {
|
|
throw new Error('Usage: node measure-index.cjs BUILT_ENGINE FRESH_FIXTURE EXISTING_OUTPUT native|wasm');
|
|
}
|
|
if (fs.existsSync(path.join(root, '.codegraph'))) {
|
|
throw new Error('Refusing an existing fixture index; preserve it and use a fresh fixture.');
|
|
}
|
|
const begin = performance.now();
|
|
const tracePath = path.join(out, 'progress.ndjson');
|
|
// Exclusive creation prevents accidental reuse of a previous run's evidence.
|
|
const traceFd = fs.openSync(tracePath, 'wx');
|
|
let phase = 'opening';
|
|
function progress(event, details = {}) {
|
|
fs.writeSync(traceFd, JSON.stringify({ event, phase, epochMs: Date.now(),
|
|
wallMs: performance.now() - begin, cpu: process.cpuUsage(), rss: process.memoryUsage().rss,
|
|
...details }) + '\n');
|
|
}
|
|
const loopDelay = monitorEventLoopDelay({ resolution: 20 });
|
|
loopDelay.enable();
|
|
let previousLoop = performance.eventLoopUtilization();
|
|
const heartbeat = setInterval(() => {
|
|
const current = performance.eventLoopUtilization();
|
|
progress('heartbeat', { eventLoop: performance.eventLoopUtilization(current, previousLoop),
|
|
delayMaxMs: loopDelay.max / 1e6 });
|
|
previousLoop = current;
|
|
loopDelay.reset();
|
|
}, 5000);
|
|
heartbeat.unref();
|
|
progress('start');
|
|
const { CodeGraph } = require(path.join(engine, 'dist/index.js'));
|
|
const { DatabaseConnection } = require(path.join(engine, 'dist/db/index.js'));
|
|
const loader = require(path.join(engine, 'dist/extraction/kernel/loader.js'));
|
|
const result = { engine, root, backend, node: process.version, stages: [] };
|
|
let cg;
|
|
const orig = CodeGraph.prototype.resolveReferencesBatched;
|
|
CodeGraph.prototype.resolveReferencesBatched = async function (...args) {
|
|
const stage = { name: 'resolution-and-synthesis', startEpochMs: Date.now(), memoryBefore: process.memoryUsage(), refsBefore: this.db.getDb().prepare('SELECT count(*) AS n FROM unresolved_refs').get().n };
|
|
const start = performance.now(), cpu = process.cpuUsage();
|
|
progress('stage-start', { name: stage.name });
|
|
try { const value = await orig.apply(this, args); stage.stats = value.stats; return value; }
|
|
finally {
|
|
Object.assign(stage, { endEpochMs: Date.now(), wallMs: performance.now() - start, cpu: process.cpuUsage(cpu), memoryAfter: process.memoryUsage() });
|
|
result.stages.push(stage);
|
|
progress('stage-complete', { stage });
|
|
phase = 'finalizing';
|
|
}
|
|
};
|
|
const origMaintenance = DatabaseConnection.prototype.runMaintenance;
|
|
DatabaseConnection.prototype.runMaintenance = async function (...args) {
|
|
phase = 'maintenance';
|
|
const start = performance.now(), cpu = process.cpuUsage();
|
|
progress('stage-start', { name: 'database-maintenance' });
|
|
try { return await origMaintenance.apply(this, args); }
|
|
finally {
|
|
const stage = { name: 'database-maintenance', wallMs: performance.now() - start,
|
|
cpu: process.cpuUsage(cpu) };
|
|
result.stages.push(stage);
|
|
progress('stage-complete', { stage });
|
|
phase = 'finalizing';
|
|
}
|
|
};
|
|
(async () => {
|
|
try {
|
|
// getKernel() deliberately ignores CODEGRAPH_KERNEL=0; kernelSupports()
|
|
// is the actual per-call routing predicate used by extraction.
|
|
result.nativeLoaded = loader.kernelSupports('typescript');
|
|
if (result.nativeLoaded !== (backend === 'native')) throw new Error('Wrong extraction backend');
|
|
cg = CodeGraph.initSync(root);
|
|
result.openMs = performance.now() - begin;
|
|
const start = performance.now(), cpu = process.cpuUsage();
|
|
result.indexStartEpochMs = Date.now();
|
|
let lastProgress = 0;
|
|
phase = 'indexing';
|
|
progress('index-start');
|
|
result.index = await cg.indexAll({ onProgress: p => {
|
|
const now = performance.now();
|
|
// Preserve every resolution/synthesis batch, but avoid one disk write per
|
|
// scanned/parsed file. Heartbeats continue while asynchronous work waits.
|
|
if (phase !== p.phase || p.phase === 'resolving' || p.phase === 'linking' ||
|
|
now - lastProgress >= 1000 || (p.total > 0 && p.current === p.total)) {
|
|
phase = p.phase;
|
|
progress('progress', { progress: p });
|
|
lastProgress = now;
|
|
}
|
|
} });
|
|
result.indexEndEpochMs = Date.now();
|
|
result.indexMs = performance.now() - start;
|
|
result.indexCpu = process.cpuUsage(cpu);
|
|
progress('index-complete', { indexMs: result.indexMs, indexCpu: result.indexCpu, index: result.index });
|
|
// Save the completed index measurement BEFORE potentially expensive full DB
|
|
// checks. A stopped validation must not look like an indexing timeout.
|
|
fs.writeFileSync(path.join(out, 'index-result.json'), JSON.stringify(result, null, 2));
|
|
phase = 'verification';
|
|
progress('verification-start');
|
|
if (!result.index.success || result.index.filesErrored) throw new Error('Index did not finish cleanly');
|
|
const db = new DatabaseSync(path.join(root, '.codegraph/codegraph.db'), { readOnly: true });
|
|
result.counts = Object.fromEntries(['files','nodes','edges','unresolved_refs'].map(table => [table, db.prepare(`SELECT count(*) AS n FROM ${table}`).get().n]));
|
|
result.languages = db.prepare('SELECT language,count(*) AS n FROM files GROUP BY language').all();
|
|
result.integrity = db.prepare('PRAGMA integrity_check').all();
|
|
result.foreignKeys = db.prepare('PRAGMA foreign_key_check').all();
|
|
result.orphans = db.prepare('SELECT count(*) AS n FROM edges e LEFT JOIN nodes s ON s.id=e.source LEFT JOIN nodes t ON t.id=e.target WHERE s.id IS NULL OR t.id IS NULL').get().n;
|
|
db.close();
|
|
if (result.integrity.length !== 1 || result.integrity[0].integrity_check !== 'ok' ||
|
|
result.foreignKeys.length || result.orphans) throw new Error('Database verification failed');
|
|
progress('verification-complete');
|
|
} catch (e) { result.error = e.stack; process.exitCode = 1; }
|
|
finally {
|
|
phase = 'closing';
|
|
progress('closing');
|
|
try { cg?.close(); }
|
|
catch (e) { result.closeError = e.stack; process.exitCode = 1; }
|
|
result.totalInsideProcessMs = performance.now() - begin;
|
|
result.maxRSSKiB = process.resourceUsage().maxRSS;
|
|
result.finalMemory = process.memoryUsage();
|
|
fs.writeFileSync(path.join(out, 'result.json'), JSON.stringify(result, null, 2));
|
|
progress('complete', { error: result.error, closeError: result.closeError });
|
|
clearInterval(heartbeat);
|
|
loopDelay.disable();
|
|
fs.closeSync(traceFd);
|
|
console.log(JSON.stringify({ indexMs: result.indexMs, stages: result.stages.map(x => ({ wallMs: x.wallMs, stats: x.stats })), error: result.error }));
|
|
}
|
|
})();
|