UNPKG

pi-lens

Version:

Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo

72 lines (71 loc) • 3.04 kB
/** * Production event-loop occupancy monitor (#192 Phase 2). * * pi-lens runs on pi's TUI event loop; a long synchronous block freezes * keystrokes. Our telemetry historically logged phase *durations*, which can't * distinguish a TUI-freezing synchronous burst from harmless async/subprocess * time — that blind spot let a ~1.5s enumeration freeze through (#188/#191). * * This wraps Node's native `perf_hooks.monitorEventLoopDelay()` — a histogram * of how late the loop services its own timer, i.e. how long it was blocked — * with **no per-event JS overhead**. `max` ≈ the worst synchronous block since * the last reset. */ import { monitorEventLoopDelay } from "node:perf_hooks"; const NS_PER_MS = 1e6; let histogram; let monitorUnavailable = false; /** * Start the monitor (idempotent). Call once, as early as possible, so startup * blocks are captured. Cheap — the sampling is native; nothing runs per event. * * Purely observational: if the runtime doesn't implement * `perf_hooks.monitorEventLoopDelay` (e.g. Bun < 1.3, which throws * `ERR_NOT_IMPLEMENTED` on the call), we degrade to "no stats" rather than let * the throw abort extension load. `getEventLoopStats()` already tolerates an * absent histogram, so every caller keeps working without telemetry. */ export function startEventLoopMonitor(resolutionMs = 20) { if (histogram || monitorUnavailable) return; try { const h = monitorEventLoopDelay({ resolution: resolutionMs }); h.enable(); histogram = h; } catch (err) { monitorUnavailable = true; console.error(`[pi-lens] event-loop occupancy telemetry disabled (runtime lacks monitorEventLoopDelay): ${err?.message ?? String(err)}`); } } const safeMs = (ns) => Number.isFinite(ns) ? Math.round((ns / NS_PER_MS) * 10) / 10 : 0; /** Current occupancy stats, or undefined if the monitor was never started. */ export function getEventLoopStats() { if (!histogram) return undefined; return { maxMs: safeMs(histogram.max), p99Ms: safeMs(histogram.percentile(99)), meanMs: safeMs(histogram.mean), }; } /** Reset the histogram — e.g. at session/turn boundaries for per-window stats. */ export function resetEventLoopMonitor() { histogram?.reset(); } /** * Decide whether a freeze is worth persisting to `latency.log`. Pure so the * threshold logic is testable without the (vitest-flaky) native histogram. * Logs only a *new* worst block (`maxMs > lastLoggedMs + deltaMs`) above a * floor (`minMs`), so a turn that froze worse than ever before is recorded * once — not the same growing max every turn. */ export function shouldLogWorstBlock(maxMs, lastLoggedMs, minMs = 60, deltaMs = 25) { return maxMs >= minMs && maxMs > lastLoggedMs + deltaMs; } /** Test-only: stop and clear the monitor so cases don't leak into each other. */ export function _stopEventLoopMonitorForTest() { histogram?.disable(); histogram = undefined; monitorUnavailable = false; }