scripts/bench-db.mjs
scripts/bench-db.mjsBrowse 13 files
4,850 tokens
17,787 bytes
Token encoding: o200k_base
Snapshot bfcf687
← Back to SKILL.md
1#!/usr/bin/env node2// Canonical store for all bench data: one SQLite file holding raw3// measurements and artifacts, written only by this importer — no4// derived statistics, no hand-entered numbers. The per-VM JSONL files5// in a run dir are the wire format; importing is idempotent (a run6// re-imports as a whole). Stats are a pure function of this DB7// (bench-analyze.mjs reads it and nothing else).8//9// node bench-db.mjs import <runDir...>10// node bench-db.mjs verify [runId]11// node bench-db.mjs export <out.db> <runId...>12// node bench-db.mjs ls13import fs from 'node:fs'14import path from 'node:path'15import zlib from 'node:zlib'16import crypto from 'node:crypto'17import { execFileSync } from 'node:child_process'18import { DatabaseSync } from 'node:sqlite'19import { loadConfig } from './config.mjs'20 21const SCHEMA = `22PRAGMA journal_mode=WAL;23PRAGMA foreign_keys=ON;24CREATE TABLE IF NOT EXISTS runs(25 run_id TEXT PRIMARY KEY,26 label TEXT,27 kind TEXT,28 started_at TEXT,29 imported_at TEXT NOT NULL,30 harness_sha TEXT,31 meta TEXT32);33CREATE TABLE IF NOT EXISTS samples(34 sample_id INTEGER PRIMARY KEY,35 run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,36 boot INTEGER NOT NULL,37 arm TEXT NOT NULL,38 block INTEGER NOT NULL,39 run INTEGER NOT NULL,40 route TEXT NOT NULL,41 phase TEXT NOT NULL,42 fingerprint TEXT,43 version TEXT,44 cpu TEXT,45 errors INTEGER NOT NULL DEFAULT 0,46 UNIQUE(run_id, boot, arm, block, run, route, phase)47);48CREATE TABLE IF NOT EXISTS measurements(49 sample_id INTEGER NOT NULL REFERENCES samples(sample_id) ON DELETE CASCADE,50 metric TEXT NOT NULL,51 value REAL NOT NULL,52 UNIQUE(sample_id, metric)53);54CREATE TABLE IF NOT EXISTS artifacts(55 artifact_id INTEGER PRIMARY KEY,56 run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,57 boot INTEGER,58 arm TEXT,59 name TEXT NOT NULL,60 kind TEXT,61 sha256 TEXT NOT NULL,62 bytes INTEGER NOT NULL,63 data BLOB NOT NULL,64 UNIQUE(run_id, boot, arm, name)65);66CREATE TABLE IF NOT EXISTS boots(67 run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,68 boot INTEGER NOT NULL,69 vm_name TEXT,70 UNIQUE(run_id, boot)71);72CREATE TABLE IF NOT EXISTS source_files(73 run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,74 name TEXT NOT NULL,75 sha256 TEXT NOT NULL,76 rows INTEGER NOT NULL,77 UNIQUE(run_id, name)78);79CREATE INDEX IF NOT EXISTS idx_samples_run ON samples(run_id);80CREATE INDEX IF NOT EXISTS idx_measurements_sample ON measurements(sample_id);81CREATE INDEX IF NOT EXISTS idx_artifacts_run ON artifacts(run_id);82`83 84// Row fields that identify a sample; every OTHER numeric field in a85// JSONL row is a measurement. New metrics therefore need no schema or86// importer change.87const IDENT = new Set([88 'vm',89 'arm',90 'block',91 'run',92 'route',93 'phase',94 'fp',95 'ver',96 'cpu',97 'errors',98 'payload',99 'round',100])101 102export function dbPath() {103 return path.join(loadConfig({ requireScope: false }).cacheDir, 'results.db')104}105 106export function openDb(file = dbPath()) {107 fs.mkdirSync(path.dirname(file), { recursive: true })108 const db = new DatabaseSync(file)109 db.exec(SCHEMA)110 return db111}112 113function harnessSha() {114 try {115 return execFileSync(116 'git',117 [118 '-C',119 path.dirname(new URL(import.meta.url).pathname),120 'rev-parse',121 'HEAD',122 ],123 { encoding: 'utf8' }124 ).trim()125 } catch {126 return null127 }128}129 130function artifactKind(name) {131 if (name.endsWith('.cpuprofile')) return 'cpuprofile'132 if (name.endsWith('.json')) return 'json'133 if (name.endsWith('.log') || name.endsWith('.txt')) return 'log'134 return 'file'135}136 137function walk(dir) {138 const out = []139 for (const e of fs.readdirSync(dir, { withFileTypes: true })) {140 const p = path.join(dir, e.name)141 if (e.isDirectory()) out.push(...walk(p))142 else if (e.isFile()) out.push(p)143 }144 return out145}146 147// Import one run dir: replaces the run's rows wholesale so re-imports148// (recovery, added boots, new artifacts) converge on the same state.149// A re-import that would SHRINK a run (fewer boots or samples than the150// db already holds — e.g. a cleaned-up run dir) is refused without151// force: claims must never silently lose data underneath them.152export function importRun(db, dir, { force = false } = {}) {153 const runId = path.basename(path.resolve(dir))154 const files = fs155 .readdirSync(dir)156 .filter((f) => /^results-vm\d+\.jsonl$/.test(f))157 .sort()158 .map((f) => path.join(dir, f))159 if (files.length === 0) throw new Error(`no results-vm*.jsonl in ${dir}`)160 const readJson = (f) => {161 try {162 return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8'))163 } catch {164 return null165 }166 }167 const meta = readJson('meta.json')168 const status = readJson('status.json')169 const prev = db170 .prepare(171 'SELECT COUNT(DISTINCT boot) boots, COUNT(*) n FROM samples WHERE run_id = ?'172 )173 .get(runId)174 db.exec('BEGIN')175 try {176 db.prepare('DELETE FROM runs WHERE run_id = ?').run(runId)177 let kind = 'e2e'178 const insSample = db.prepare(`INSERT INTO samples179 (run_id, boot, arm, block, run, route, phase, fingerprint, version, cpu, errors)180 VALUES (?,?,?,?,?,?,?,?,?,?,?)`)181 const insMeas = db.prepare(182 'INSERT INTO measurements (sample_id, metric, value) VALUES (?,?,?)'183 )184 let samples = 0185 let pendingRows = []186 const fileInfos = []187 for (const [boot, file] of files.entries()) {188 const content = fs.readFileSync(file, 'utf8')189 let fileRows = 0190 for (const line of content.trim().split('\n')) {191 if (!line) continue192 const row = JSON.parse(line)193 const micro = row.payload !== undefined194 if (micro) kind = 'micro'195 pendingRows.push({ boot, row, micro })196 fileRows++197 }198 fileInfos.push({199 boot,200 name: path.basename(file),201 sha256: crypto.createHash('sha256').update(content).digest('hex'),202 rows: fileRows,203 })204 }205 if (206 !force &&207 prev.n > 0 &&208 (files.length < prev.boots || pendingRows.length < prev.n)209 ) {210 throw new Error(211 `re-import of ${runId} would shrink it ` +212 `(${prev.boots} boots/${prev.n} samples in db, ` +213 `${files.length} boots/${pendingRows.length} samples in ${dir}) — ` +214 'pass --force only if the db copy is known bad'215 )216 }217 db.prepare(218 `INSERT INTO runs219 (run_id, label, kind, started_at, imported_at, harness_sha, meta)220 VALUES (?,?,?,?,?,?,?)`221 ).run(222 runId,223 meta?.label ?? runId.replace(/^run-/, '').replace(/-[a-z0-9]+$/, ''),224 kind,225 status?.startedAt ?? null,226 new Date().toISOString(),227 harnessSha(),228 meta ? JSON.stringify(meta) : null229 )230 // Provenance: which sandbox VM produced each boot (from231 // status.json, matched by the index in the VM name), and the exact232 // bytes each boot's JSONL contributed.233 const vmByIndex = new Map()234 for (const name of Object.keys(status?.vms ?? {})) {235 const idx = name.match(/-(\d+)-[a-z0-9]+$/)?.[1]236 if (idx !== undefined) vmByIndex.set(Number(idx), name)237 }238 const insBoot = db.prepare(239 'INSERT INTO boots (run_id, boot, vm_name) VALUES (?,?,?)'240 )241 const insFile = db.prepare(242 'INSERT INTO source_files (run_id, name, sha256, rows) VALUES (?,?,?,?)'243 )244 for (const f of fileInfos) {245 const n = Number(f.name.match(/^results-vm(\d+)\.jsonl$/)?.[1])246 insBoot.run(runId, f.boot, vmByIndex.get(n) ?? null)247 insFile.run(runId, f.name, f.sha256, f.rows)248 }249 for (const { boot, row, micro } of pendingRows) {250 const { lastInsertRowid: sid } = insSample.run(251 runId,252 boot,253 row.arm,254 micro ? 0 : (row.block ?? 0),255 micro ? (row.round ?? 0) : (row.run ?? 0),256 micro ? '' : (row.route ?? ''),257 micro ? row.payload : row.phase,258 row.fp ?? null,259 row.ver ?? null,260 row.cpu ?? null,261 row.errors ?? 0262 )263 samples++264 for (const [k, v] of Object.entries(row)) {265 if (IDENT.has(k)) continue266 if (typeof v !== 'number' || !Number.isFinite(v)) continue267 insMeas.run(sid, k, v)268 }269 }270 // Artifacts: everything under prof-vm<N>/ (CPU profiles etc.),271 // stored gzipped with the sha256 of the RAW content.272 const insArt = db.prepare(`INSERT INTO artifacts273 (run_id, boot, arm, name, kind, sha256, bytes, data) VALUES (?,?,?,?,?,?,?,?)`)274 let artifacts = 0275 for (const e of fs.readdirSync(dir)) {276 const m = e.match(/^prof-vm(\d+)$/)277 if (!m) continue278 const boot = Number(m[1])279 for (const f of walk(path.join(dir, e))) {280 const rel = path.relative(path.join(dir, e), f)281 const arm = rel.match(/^prof-([^/]+)\//)?.[1] ?? null282 const raw = fs.readFileSync(f)283 insArt.run(284 runId,285 boot,286 arm,287 rel,288 artifactKind(rel),289 crypto.createHash('sha256').update(raw).digest('hex'),290 raw.length,291 zlib.gzipSync(raw)292 )293 artifacts++294 }295 }296 db.exec('COMMIT')297 return { runId, boots: files.length, samples, artifacts }298 } catch (e) {299 db.exec('ROLLBACK')300 throw e301 }302}303 304// Rows for bench-stats.analyzeE2eRows, reconstructed from the DB —305// the stats layer never reads run dirs.306export function loadRows(db, runId) {307 const samples = db308 .prepare('SELECT * FROM samples WHERE run_id = ?')309 .all(runId)310 if (samples.length === 0) throw new Error(`no samples for run ${runId}`)311 const meas = db312 .prepare(313 `SELECT m.sample_id, m.metric, m.value FROM measurements m314 JOIN samples s ON s.sample_id = m.sample_id WHERE s.run_id = ?`315 )316 .all(runId)317 const byId = new Map()318 for (const s of samples) {319 byId.set(s.sample_id, {320 vm: Number(s.boot),321 arm: s.arm,322 block: Number(s.block),323 run: Number(s.run),324 route: s.route,325 phase: s.phase,326 fp: s.fingerprint,327 ver: s.version,328 errors: Number(s.errors),329 })330 }331 for (const m of meas) byId.get(m.sample_id)[m.metric] = m.value332 return [...byId.values()]333}334 335export function runMeta(db, runId) {336 const r = db.prepare('SELECT * FROM runs WHERE run_id = ?').get(runId)337 return r ? { ...r, meta: r.meta ? JSON.parse(r.meta) : null } : null338}339 340// Integrity checks. Everything here is mechanical: SQLite-level341// integrity, referential health, per-run shape (one fingerprint per342// arm, paired sample counts), and artifact hashes. Failures are343// data-integrity violations; notes are true facts a reader must not344// gloss over (e.g. a recovered run that is legitimately partial).345export function verify(db, runId) {346 const problems = []347 const notes = []348 const ic = db.prepare('PRAGMA integrity_check').all()349 if (!(ic.length === 1 && ic[0].integrity_check === 'ok')) {350 problems.push(`sqlite integrity_check: ${JSON.stringify(ic)}`)351 }352 const fk = db.prepare('PRAGMA foreign_key_check').all()353 if (fk.length > 0) problems.push(`foreign_key_check: ${fk.length} violations`)354 const runs = runId355 ? db.prepare('SELECT run_id FROM runs WHERE run_id = ?').all(runId)356 : db.prepare('SELECT run_id FROM runs').all()357 if (runId && runs.length === 0) problems.push(`run ${runId} not in db`)358 for (const { run_id } of runs) {359 const orphanSamples = db360 .prepare(361 `SELECT COUNT(*) c FROM samples s362 WHERE s.run_id = ? AND NOT EXISTS363 (SELECT 1 FROM measurements m WHERE m.sample_id = s.sample_id)`364 )365 .get(run_id).c366 if (orphanSamples > 0) {367 problems.push(`${run_id}: ${orphanSamples} samples with no measurements`)368 }369 const arms = db370 .prepare(371 `SELECT arm, COUNT(DISTINCT COALESCE(fingerprint,'') ||372 '/' || COALESCE(version,'')) fps, COUNT(*) n373 FROM samples WHERE run_id = ? GROUP BY arm`374 )375 .all(run_id)376 if (arms.length !== 2) {377 problems.push(`${run_id}: expected 2 arms, found ${arms.length}`)378 }379 for (const a of arms) {380 if (a.fps > 1) {381 problems.push(382 `${run_id}: arm ${a.arm} has ${a.fps} distinct fingerprints`383 )384 }385 }386 const planned = (() => {387 try {388 return JSON.parse(389 db.prepare('SELECT meta FROM runs WHERE run_id = ?').get(run_id).meta390 ).vms391 } catch {392 return undefined393 }394 })()395 const gotBoots = db396 .prepare('SELECT COUNT(DISTINCT boot) c FROM samples WHERE run_id = ?')397 .get(run_id).c398 if (planned && gotBoots < planned) {399 notes.push(`${run_id}: partial run (${gotBoots}/${planned} boots)`)400 }401 if (arms.length === 2 && arms[0].n !== arms[1].n) {402 problems.push(403 `${run_id}: unpaired sample counts ` +404 `(${arms[0].arm}=${arms[0].n} vs ${arms[1].arm}=${arms[1].n})`405 )406 }407 for (const art of db408 .prepare(409 'SELECT artifact_id, name, sha256, bytes, data FROM artifacts WHERE run_id = ?'410 )411 .all(run_id)) {412 const raw = zlib.gunzipSync(art.data)413 if (414 raw.length !== Number(art.bytes) ||415 crypto.createHash('sha256').update(raw).digest('hex') !== art.sha256416 ) {417 problems.push(`${run_id}: artifact ${art.name} fails hash/size check`)418 }419 }420 }421 return { failures: problems, notes }422}423 424export function exportRuns(db, outFile, runIds) {425 fs.rmSync(outFile, { force: true })426 const out = openDb(outFile)427 const copy = (table, cols) => {428 const ins = out.prepare(`INSERT INTO ${table} (${cols.join(',')})429 VALUES (${cols.map(() => '?').join(',')})`)430 return (row) => ins.run(...cols.map((c) => row[c]))431 }432 out.exec('BEGIN')433 const copyRun = copy('runs', [434 'run_id',435 'label',436 'kind',437 'started_at',438 'imported_at',439 'harness_sha',440 'meta',441 ])442 const copySample = copy('samples', [443 'sample_id',444 'run_id',445 'boot',446 'arm',447 'block',448 'run',449 'route',450 'phase',451 'fingerprint',452 'version',453 'cpu',454 'errors',455 ])456 const copyMeas = copy('measurements', ['sample_id', 'metric', 'value'])457 const copyBoot = copy('boots', ['run_id', 'boot', 'vm_name'])458 const copyFile = copy('source_files', ['run_id', 'name', 'sha256', 'rows'])459 const copyArt = copy('artifacts', [460 'run_id',461 'boot',462 'arm',463 'name',464 'kind',465 'sha256',466 'bytes',467 'data',468 ])469 for (const runId of runIds) {470 const run = db.prepare('SELECT * FROM runs WHERE run_id = ?').get(runId)471 if (!run) throw new Error(`run ${runId} not in db`)472 copyRun(run)473 for (const s of db474 .prepare('SELECT * FROM samples WHERE run_id = ?')475 .all(runId)) {476 copySample(s)477 for (const m of db478 .prepare('SELECT * FROM measurements WHERE sample_id = ?')479 .all(s.sample_id)) {480 copyMeas(m)481 }482 }483 for (const a of db484 .prepare('SELECT * FROM artifacts WHERE run_id = ?')485 .all(runId)) {486 copyArt(a)487 }488 for (const b of db489 .prepare('SELECT * FROM boots WHERE run_id = ?')490 .all(runId)) {491 copyBoot(b)492 }493 for (const f of db494 .prepare('SELECT * FROM source_files WHERE run_id = ?')495 .all(runId)) {496 copyFile(f)497 }498 }499 out.exec('COMMIT')500 out.close()501}502 503// ------------------------------------------------------------------ CLI504function isMain() {505 if (!process.argv[1]) return false506 try {507 return (508 fs.realpathSync(process.argv[1]) ===509 fs.realpathSync(new URL(import.meta.url).pathname)510 )511 } catch {512 return false513 }514}515if (isMain()) {516 const [cmd, ...rest] = process.argv.slice(2)517 const db = openDb()518 const report = ({ failures, notes }, label) => {519 for (const n of notes) console.log(`note: ${n}`)520 if (failures.length) {521 console.error(`VERIFY FAILED:\n ${failures.join('\n ')}`)522 process.exit(1)523 }524 console.log(`verify: ok${label ? ` (${label})` : ''}`)525 }526 if (cmd === 'import') {527 const force = rest.includes('--force')528 const dirs = rest.filter((a) => a !== '--force')529 if (dirs.length === 0) {530 console.error('usage: bench-db.mjs import [--force] <runDir...>')531 process.exit(1)532 }533 for (const dir of dirs) {534 let r535 try {536 r = importRun(db, dir, { force })537 } catch (e) {538 console.error(`IMPORT REFUSED: ${e.message}`)539 process.exit(1)540 }541 console.log(542 `${r.runId}: ${r.boots} boots, ${r.samples} samples, ${r.artifacts} artifacts`543 )544 }545 report(verify(db))546 } else if (cmd === 'verify') {547 report(verify(db, rest[0]), rest[0] ?? 'all runs')548 } else if (cmd === 'export') {549 const [out, ...ids] = rest550 if (!out || ids.length === 0) {551 console.error('usage: bench-db.mjs export <out.db> <runId...>')552 process.exit(1)553 }554 exportRuns(db, out, ids)555 console.log(`${out}: ${ids.length} runs`)556 } else if (cmd === 'ls') {557 for (const r of db558 .prepare(559 `SELECT r.run_id, r.kind, r.started_at,560 (SELECT COUNT(DISTINCT boot) FROM samples s WHERE s.run_id = r.run_id) boots,561 (SELECT COUNT(*) FROM samples s WHERE s.run_id = r.run_id) samples,562 (SELECT COUNT(*) FROM artifacts a WHERE a.run_id = r.run_id) artifacts563 FROM runs r ORDER BY r.started_at`564 )565 .all()) {566 console.log(567 `${r.run_id} ${r.kind} boots=${r.boots} samples=${r.samples} artifacts=${r.artifacts} ${r.started_at ?? ''}`568 )569 }570 } else {571 console.error('usage: bench-db.mjs import|verify|export|ls')572 process.exit(1)573 }574}575 Referenced from SKILL.md
SKILL.mdView in source ↗
Source excerpt starting at line 249.SKILL.mdView in source ↗249- `node scripts/bench-db.mjs ls` — all runs with sample/artifact counts.250- `node scripts/bench-db.mjs verify [runId]` — integrity checks:251 sqlite-level, referential, one fingerprint per arm, paired sample
Source excerpt starting at line 252.252 counts, artifact sha256. Run it before drawing on old data.253- `node scripts/bench-db.mjs export out.db <runId...>` — cut a254 self-contained db of specific runs (with their profiles) to send to