feat(system-perf): expand popover with heap-space + stress signals + on-demand snapshot#86
Merged
Merged
Conversation
…on-demand snapshot The badge popover used to show one chart (heap + RSS over time) and three rows of aggregate numbers. That answered "is the heap growing?" but not "WHICH part of the heap?" or "where do I look next?". The 20→600 MB jumps you see on heavy multi-agent days were observable in the chart but not attributable. This PR adds — visible only when the popover is expanded — the diagnostic vocabulary you'd normally need to drop into Chrome DevTools for: per-space heap breakdown, leak smoke alarms, main-thread stall signal, renderer-process heap, and an on-demand snapshot capture. What was added, with the reasoning: 1. **Per-space heap breakdown** (`v8.getHeapSpaceStatistics()`) shown as the top six spaces sorted by used size, with a proportional bar. Tells you which v8 space is growing — `old_space` rising = retention (a real leak); `new_space` rising = churn but usually fine; `large_object_space` rising = giant buffers/strings (often the smoking gun on agent-heavy workflows). 2. **Detached + native contexts** from the same `getHeapStatistics()` call we already use. `detachedContexts > 0` is one of the strongest leak smoke alarms in v8 — colored red when nonzero. `nativeContexts` should track BrowserWindow count; surfaced for completeness. 3. **Event-loop p99 delay** sampled by a dedicated `monitorEventLoopDelay()` owned by the IPC handler. Reset on each 1 Hz poll so the value is a per-second window, independent of PerformanceService's own 5 s probe. Colored green/amber/red at 16 ms / 50 ms thresholds (the 60 fps budget and a "user notices" stall respectively). 4. **Renderer-process heap** via `window.performance.memory`, read synchronously in the poller (no IPC bounce — the renderer can read its own JS heap directly). Lets you tell which process is actually leaking when the main badge is fine but the app feels slow. 5. **On-demand heap snapshot button** that calls `v8.writeHeapSnapshot()` to STATE_DIR/heap-snapshots/manual-… and offers a "Reveal in Finder" follow-up. Same artifact the watchdog auto-writes at 75 % of cap, but user-initiated for the case you want to inspect the heap WHILE the growth is happening, not after it crosses the watchdog threshold. Multi-second STW pause and a 100 MB-to-3 GB file, so explicit-user-gesture only. Implementation surface: - `src/shared/performance/types.ts` — extended `SystemPerformanceStats` with `heapSpaces`, `detachedContexts`, `nativeContexts`, `eventLoopDelay`, optional `rendererHeap`. Thick WHY-comments on each field per repo policy. - `src/main/ipc/performance.ts` — extended `performance:system-stats` to collect the new fields in one shot (still well under 1 ms per tick). Added `performance:write-heap-snapshot` and `performance:reveal-path` handlers. Owns a local `monitorEventLoopDelay` so the IPC view of p99 isn't disturbed by PerformanceService's probe. - `src/preload/api/performance.ts` — exposes the two new IPC methods. - `src/renderer/src/features/system-perf/useSystemPerfPoller.ts` — reads `window.performance.memory` per tick and stamps it onto each buffer entry. Typed accessor returns null when the Chromium API is absent (e.g. tests) so the popover hides the row instead of showing zeros. - `src/renderer/src/features/system-perf/ui/SystemPerfPopover.tsx` — widened to 520 px, added four new sections + the capture button. Reuses the shared `formatBytes` helper landed in PR #85. Net: 5 files changed, +568/-36. Tests pass; build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The badge popover used to show one chart (heap + RSS over time) and three rows of aggregate numbers. That answered "is the heap growing?" but not "WHICH part of the heap?" or "where do I look next?" — the 20 MB → 600 MB jumps on heavy multi-agent days were visible but not attributable.
This PR adds — visible only when the popover is expanded — the diagnostic vocabulary you'd otherwise need Chrome DevTools for. The header badge itself is unchanged.
What gets added
Per-space heap breakdown (
v8.getHeapSpaceStatistics()) — the top six v8 spaces sorted by used size with proportional bars.old_spacerising = retention (real leak);large_object_spacerising = giant buffers/strings (typical agent-heavy smoking gun);new_spacerising = churn but usually fine.Detached + native contexts —
detachedContexts > 0is one of the strongest leak smoke alarms v8 exposes (colored red when nonzero). Free signal — already in thegetHeapStatistics()call we make.Event-loop p99 delay — dedicated
monitorEventLoopDelay()owned by the IPC handler so its 1 Hz reset window is independent of PerformanceService's 5 s probe. Color thresholds at 16 ms (60 fps budget) and 50 ms ("user notices").Renderer-process heap —
window.performance.memoryread synchronously in the poller (no IPC bounce; renderer reads its own JS heap directly). Lets you tell which process is leaking when main says fine but the app feels slow.On-demand heap snapshot button —
v8.writeHeapSnapshot()toSTATE_DIR/heap-snapshots/manual-…with a "Reveal in Finder" follow-up. Same artifact the watchdog writes at 75 % of cap, but user-initiated so you can capture WHILE growth is happening. Multi-second STW pause + 100 MB-to-3 GB file → explicit-gesture only.What this doesn't do (and why)
PerformanceObserverfor'gc'entries.inspector.SessionHeapProfiler.startSampling) — Tier 3 from the original sketch; bigger commitment, save for if Tier 1 + snapshot isn't enough.Implementation surface
src/shared/performance/types.ts— extendedSystemPerformanceStatswithheapSpaces,detachedContexts,nativeContexts,eventLoopDelay, optionalrendererHeap. Thick WHY-comments on each field per repo policy.src/main/ipc/performance.ts— extendedperformance:system-statsto collect everything in one shot (still well under 1 ms/tick). Newperformance:write-heap-snapshot+performance:reveal-pathhandlers. Owns a localmonitorEventLoopDelay.src/preload/api/performance.ts— exposes the two new IPC methods.src/renderer/src/features/system-perf/useSystemPerfPoller.ts— readswindow.performance.memoryper tick. Typed null-safe accessor for environments where the Chromium API is absent.src/renderer/src/features/system-perf/ui/SystemPerfPopover.tsx— widened to 520 px, four new sections + capture button. Reuses the sharedformatByteshelper from PR Trim dead exports and consolidate duplicated helpers #85.Test plan
npm run test:review-fixespassesnpm run build:appsucceedsAGENT_CODE_PERF=1 npm run dev, open badge popover, verify heap-space rows render, detached-contexts shows 0, event-loop p99 has a real number, renderer-heap row appears, capture button writes a file and reveal works.Stats
5 files changed, +551/-37.
🤖 Generated with Claude Code