perf(dashboard): cache gh CLI checks and defer SQLite integrity scan

Cold-start dashboard responsiveness went from ~99s to ~6-11s. CPU profiling
identified two synchronous-spawn hotspots blocking the event loop:

- `GitHubTrackingReconciler` scanned up to 200 done tasks per startup,
  each call into `getIssue` invoking `isGhAvailable()` + `isGhAuthenticated()`
  via `execFileSync`. `gh auth status` makes a network roundtrip, so 400
  sync spawns ≈ 71s of pure event-loop blocking (69% of cold-start CPU).
  Memoized both checks with a 60s TTL; `resetGhAvailabilityCache()` is
  exported for login/logout flows that need immediate invalidation.

- `PRAGMA integrity_check(100)` walks every page of the SQLite file (~7s
  per database, multiple DBs × projects). The deferred check was scheduled
  3s after init — right in the responsiveness-critical window. Pushed to
  60s so the user is already interacting before it runs; check itself is
  unchanged.

Also yields the event loop between major InProcessRuntime init phases and
between self-healing recovery steps (34 per project), defers orphan-task
AI agent resumption by 30s (env-overridable, auto-zero under Vitest), and
ships an opt-in `FUSION_TRACE_EL_LAG=/path/to/file` event-loop lag tracer
that diagnosed all of the above.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-26 23:41:28 -07:00
parent cac08d733b
commit 390bd7f923
10 changed files with 184 additions and 25 deletions

View File

@@ -2180,6 +2180,31 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
});
}
// ── Event-loop lag tracer (debug aid) ──
// Polls every 50ms and logs whenever the loop was blocked by >150ms since
// the previous tick. Pinpoints which synchronous operation is hogging the
// event loop during startup. Disabled unless FUSION_TRACE_EL_LAG is set
// to a file path (writes to that file with raw timestamps so log output
// doesn't pollute the analysis).
if (process.env.FUSION_TRACE_EL_LAG) {
const lagPath = process.env.FUSION_TRACE_EL_LAG;
const fs = await import("node:fs");
const lagStream = fs.createWriteStream(lagPath, { flags: "w" });
const LAG_THRESHOLD_MS = 150;
const POLL_MS = 50;
const traceStart = performance.now();
let last = traceStart;
setInterval(() => {
const now = performance.now();
const delta = now - last - POLL_MS;
last = now;
if (delta > LAG_THRESHOLD_MS) {
const tSinceStart = Math.round(now - traceStart);
lagStream.write(`t+${tSinceStart}ms: blocked ${Math.round(delta)}ms\n`);
}
}, POLL_MS).unref();
}
const server = app.listen(selectedPort, selectedHost);
server.on("error", (err: NodeJS.ErrnoException) => {