Files
portal/tools/glob-latency-investigator.mjs
T
Дмитрий d81bdc912f tools(observer): add Glob latency investigator (ad-hoc script)
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>
2026-05-20 13:47:43 +03:00

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