d81bdc912f
Closes brain-retro 2026-05-20 #17 — one-off Node script for investigating the Glob p50=12.7s anomaly from initial retro. Parses transcript JSONL, prints top-N slowest Glob round-trips with pattern + path. Smoke-tested on session 553717ec (5h+ session): finds 32 Glob calls, median 12690ms (matches retro finding), top-5 all 'docs/adr/**' at 20265ms — Glob recursive on ADR directory is the apparent culprit. NOT production code path — never imported by parser/hook/analyzer. Run on demand: node tools/glob-latency-investigator.mjs <transcript.jsonl>. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
85 lines
2.9 KiB
JavaScript
85 lines
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Glob latency investigator — ad-hoc one-off script (Task 17).
|
|
*
|
|
* Brain-retro 2026-05-20 §17 noted Glob p50 = 12.7s anomaly. This script
|
|
* parses a Claude Code session transcript (JSONL), extracts all Glob
|
|
* tool_use → tool_result round-trips, sorts by latency desc, prints top-N
|
|
* with the pattern (and optional path) that caused each.
|
|
*
|
|
* Output is intentionally compact (top-10 by default). Run on demand
|
|
* against any session transcript when investigating Glob slowness:
|
|
*
|
|
* node tools/glob-latency-investigator.mjs <path/to/transcript.jsonl> [topN]
|
|
*
|
|
* NOT part of production code path — never imported by parser/hook/analyzer.
|
|
* Lives in tools/ for discoverability alongside the rest of the observer
|
|
* infrastructure.
|
|
*
|
|
* Security Guidance #40: pure fs reads, no exec/execSync.
|
|
*/
|
|
import { readFileSync, existsSync } from 'fs';
|
|
|
|
const path = process.argv[2];
|
|
const topN = Number.parseInt(process.argv[3] || '10', 10);
|
|
|
|
if (!path) {
|
|
console.error('Usage: node tools/glob-latency-investigator.mjs <transcript.jsonl> [topN=10]');
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!existsSync(path)) {
|
|
console.error(`File not found: ${path}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const lines = readFileSync(path, 'utf-8').split('\n').filter(Boolean);
|
|
|
|
/** Map tool_use.id → { pattern, path, ts } for Glob calls. */
|
|
const starts = new Map();
|
|
/** Collected round-trip latencies. */
|
|
const events = [];
|
|
|
|
for (const line of lines) {
|
|
let o;
|
|
try { o = JSON.parse(line); } catch { continue; }
|
|
const ts = o.timestamp ? new Date(o.timestamp).getTime() : null;
|
|
if (!ts) continue;
|
|
|
|
if (o.type === 'assistant' && o.message && Array.isArray(o.message.content)) {
|
|
for (const c of o.message.content) {
|
|
if (c && c.type === 'tool_use' && c.name === 'Glob') {
|
|
starts.set(c.id, {
|
|
pattern: (c.input && c.input.pattern) || '(no pattern)',
|
|
path: (c.input && c.input.path) || null,
|
|
ts,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (o.type === 'user' && o.message && Array.isArray(o.message.content)) {
|
|
for (const c of o.message.content) {
|
|
if (c && c.type === 'tool_result' && starts.has(c.tool_use_id)) {
|
|
const s = starts.get(c.tool_use_id);
|
|
events.push({ pattern: s.pattern, path: s.path, latency: ts - s.ts });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
events.sort((a, b) => b.latency - a.latency);
|
|
|
|
const total = events.length;
|
|
const median = total > 0 ? events[Math.floor(total / 2)].latency : 0;
|
|
const p95 = total > 0 ? events[Math.floor(total * 0.05)].latency : 0;
|
|
|
|
console.log(`Glob round-trips: ${total} | median ${median}ms | p95 ${p95}ms`);
|
|
console.log(`\nTop ${Math.min(topN, total)} by latency:`);
|
|
console.log('latency_ms'.padStart(11), ' pattern (path)');
|
|
console.log('-----------', ' --------------');
|
|
for (const e of events.slice(0, topN)) {
|
|
const where = e.path ? ` (${e.path})` : '';
|
|
console.log(String(e.latency).padStart(11), ` ${e.pattern}${where}`);
|
|
}
|