Files
codegraph/scripts/benchmarks/measure-index.cjs
Colby Mchenry 4297b8e2ed fix: restore call graph relationships and Steps effects (#1862)
* 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
2026-09-15 05:30:09 +00:00

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 }));
}
})();