Skip to content

feat(performance): attribute memory and CPU to agents in the monitor overview - #986

Merged
Juliusolsson05 merged 10 commits into
mainfrom
feat/performance-monitor-ux
Sep 18, 2026
Merged

Juliusolsson05 merged 10 commits into
mainfrom
feat/performance-monitor-ux

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Sep 17, 2026 •

Copy link
Copy Markdown
Owner

Fixes #984
Refs #944

Problem

The Performance Monitor (#951) could say Agent Code was heavy, but not which agent was responsible. Its charts were static pictures: no hover, no y-axis units, and each line scaled to its own peak, so a calm series and a stall drew the same shape. The process table showed raw session-ID prefixes nobody can find on screen. Two superseded debug surfaces (the old Performance panel and the system-perf badge/popover) and their backend endpoints were still in the tree, unreferenced.

Someone opening the monitor needs answers in order: is the app heavy right now, where did memory and CPU go over the last minutes, which agent is responsible (under the label the workspace already shows, e.g. A15), and is the UI keeping up.

What changed

Per-agent history (main). AgentUsageHistory keeps 15 minutes of per-session memory and CPU plus a per-owner composition, derived from the process pages the coordinator already caches. Attribution rules, unit-tested:

  • a process owned by exactly one session counts toward that agent, children included;
  • a process reachable from several sessions is counted once as shared, never once per owner;
  • app / agents / terminals / shared / other always sum to the page total;
  • a placeholder row for an undiscoverable root is a session, not a process;
  • sessions absent from the newest sample drop out of the ranking, and live sessions rank memory-descending (pinned as an IPC contract).

It lives in main rather than the helper because the question needs the minutes before the monitor opened, and helper query replies are capped at 256 KiB — too small for a large fleet's history. Served read-only via performance:monitor-agents, so it never waits on the helper.

Shared charts (components/charts). TimeSeriesChart (real pixel axes with rounded ceilings; runs break at missing readings and time gaps instead of drawing through them; crosshair snaps to real samples; one shared hover across charts covering the same window; thresholds; incident markers; full keyboard access with a live screen-reader readout), BarChart (nested series) and Sparkline (fixed caller-supplied ceiling so heights compare across rows). Geometry rules are pure, unit-tested functions in chartMath.

Monitor Overview rebuilt. Totals strip (app memory with share of system RAM and 15-minute delta, app CPU, heaviest agent, running counts, UI responsiveness, main heap, incidents — toned with the incident engine's own thresholds), stacked memory/CPU charts with one crosshair, then Agents by resource use: every running session under its workspace label (A15) with title, provider and tab; memory and share of the whole app; signed growth; CPU; process count; sparkline. Sortable by memory, CPU or growth — a leaking agent is often not the largest. Go to wakes a detached agent, focuses it, and closes the monitor only when navigation succeeds.

Timeline and Processes. Timeline moved onto the same charts: memory/CPU/responsiveness lanes, 1h and 6h ranges, Earlier/Later panning (the 7-day retention is finally reachable), and incident markers that select their incident. Processes shows agent label and title instead of session-ID prefixes; labels resolve through the workspace's own pane-label resolution, tested to navigate where they claim.

Agent Analytics uses the same chart components: per-day bars with wall-clock nested inside agent-hours (the containment is real; the gap is the parallelism), hover/keyboard readout, a parallelism tile, per-project share of all agent-hours.

Dead code removed. The old Performance panel (features/performance) and system-perf badge/popover (features/system-perf) with their endpoints performance:pane-stats and performance:system-stats, plus — found by review — the fully orphaned chain behind pane-stats: the ProcessTelemetry class, PerformanceService.recordPaneProcessStats, and the PanePerformanceStats/PanePerformanceSnapshot types. The "performance" feature-reference page moved to the monitor directory that now owns its commands, keeping the historical page id.

Design decisions

  • History in main, not the renderer: the trend must predate the panel being opened.
  • Growth as a first-class sort: "largest" and "leaking" are different questions.
  • Shared helpers counted once: charging a shared MCP server to every owner would overcount; its bytes are reported once under "shared".
  • Gaps break lines: a sleep gap or a missing reading is an interval nobody observed; drawing through it invents data, and a null is never shown as zero.
  • Thresholds reuse the incident engine's: a tile turning amber means the same thing as an incident being recorded; no second, drifting scale.
  • Decimal GB labels on binary divisions: matches what Activity Monitor / Task Manager print; users cross-check our numbers against those tools.

Independent reviews

Three independent Agent Code orchestration reviews ran on this PR (data layer, renderer/charts, PR-level conventions). All three verdicts: approve-with-comments, no blockers. Every blocker/major/minor finding was fixed in response:

  • the orphaned pane-telemetry chain was deleted (review C's major);
  • "heaviest agent" no longer depends on undocumented sort order; the ordering is now a pinned contract;
  • zero-share rows paint no bar; the growth column labels the measured window;
  • tests added for memory-descending ordering, the 256-session eviction cap, coordinator usage wiring (atomic pages, children summed, stop clears), and the shared in-flight preload read.

Tests and verification

Limitations and follow-ups

  • Drag-to-zoom on the timeline is out of scope; ranges + Earlier/Later panning only (noted in feat(performance): make the monitor's first page attribute memory and CPU to agents #984 as follow-up).
  • Per-agent history starts at launch and is not persisted across restarts; it fills over ~15 minutes.
  • RSS can double-count pages shared between processes — stated in the overview footnote.
  • Worst-case IPC payload (256 sessions × 180 points ≈ 1.7 MiB, ~23 ms clone per open window per 5 s poll) was measured by review and judged acceptable for a diagnostics panel; a top-K-history or decimation mitigation (~10–16× smaller) is a recorded follow-up if real fleets approach the cap.
  • useMonitor (pre-existing) stops polling if a snapshot invoke never settles; a watchdog re-arm is a recorded follow-up.
  • Renderer smoke tests run in jsdom; the charts' pointer behavior is covered by the keyboard-path tests plus shared geometry tests, not pixel assertions.

The Performance Monitor (issue #951) became the single performance
surface in Settings, the palette and Settings Bar, but the two surfaces
it replaced stayed in the tree unreferenced: the verbose Performance
panel (features/performance) and the system-perf badge/popover with its
1 Hz poller. Nothing mounted either one; only the feature-reference
catalog still imported them.

Deleting them takes their backend endpoints with it:
performance:pane-stats (ProcessTelemetry still serves sessionManager)
and performance:system-stats (MainProbe still feeds the monitor). The
"performance" feature-reference page moves to the monitor directory
that now owns its commands, keeping the historical page id so existing
links keep resolving.

Refs #984
The monitor could report what all of Agent Code used, but not which
agent was responsible, and a user opening it needs the trend from
BEFORE they opened it. AgentUsageHistory derives a per-session and
per-owner composition from each process page the coordinator already
caches, keeping 15 minutes (the monitor's recent window) in main.

Attribution rules, unit-tested: a process owned by exactly one session
counts toward that agent (its children included); a process reachable
from several sessions is counted once as shared, never once per owner;
app/agents/terminals/shared/other always sum to the page total; a
placeholder row for an undiscoverable root is visible as a session but
is not a process. Sessions absent from the newest page drop out of the
ranking so an exited agent's last reading cannot linger.

It lives in main, not the helper: the work is one linear pass per
sample, and a helper query reply is capped at 256 KiB — too small for a
large fleet's 15 minutes of history. Served read-only via
performance:monitor-agents with one shared in-flight preload promise.

Refs #984
TimeSeriesChart, BarChart and Sparkline replace the monitor's static
SVG pictures. The old charts had no hover, no axis units, and scaled
every series to its own peak, so a calm line and a stall drew the same
shape — height carried no information.

TimeSeriesChart: real pixel axes with rounded ceilings, runs that break
at missing readings and time gaps instead of drawing through them, a
crosshair that snaps to real samples (never interpolated values), an
optional shared hover so charts over the same window move one
crosshair, thresholds and incident markers, and full keyboard access
(arrows, Home, End, Escape) with a live screen-reader readout. BarChart
draws nested series (wall-clock inside agent-hours) with the same
readout. Sparkline draws a trend on a caller-supplied shared ceiling so
heights compare across table rows.

Geometry rules (ceilings, wall-aligned time ticks, gap detection,
nearest-sample search) are pure functions in chartMath with unit tests;
shared by the Performance Monitor and Agent Analytics so both behave
the same way.

Refs #984
The overview now answers, in reading order: is the app heavy, where did
memory and CPU go, which agent is responsible, is the UI keeping up.

- Totals strip: app memory with share of system RAM and 15-minute
  delta, app CPU, heaviest agent, running counts, UI responsiveness,
  main heap, incidents — toned with the incident engine's own
  thresholds so a color means the same thing everywhere.
- Memory and CPU stacked by owner with one crosshair across both.
- Agents by resource use: every running session under its workspace
  label (A15) with title, provider and tab; memory and share of the
  whole app; signed growth; CPU; process count; sparkline on a shared
  scale. Sortable by memory, CPU or growth — a leaking agent is often
  not the largest. Go to wakes a detached agent, focuses it and closes
  the monitor only when navigation succeeds.
- Timeline on interactive lanes with 1h/6h ranges, Earlier/Later
  panning and incident markers that select their incident.
- Processes shows agent label and title instead of session-ID
  prefixes; labels come from the same resolution the workspace uses,
  tested to navigate where they claim.

Drag-to-zoom on the timeline is a follow-up. Refs #984
Per-day bars move to the shared BarChart: wall-clock draws inside
agent-hours (the containment is real — agent-hours always include the
wall-clock union), so one bar shows both and the gap between them is
the parallelism. Hover or arrow keys read both figures; the old native
SVG titles appeared only after a delay and were unreachable from the
keyboard. Adds a parallelism tile and per-project share of all
agent-hours. Local-date parsing avoids the UTC off-by-one that would
print the previous day west of Greenwich.

Refs #984
Removing performance:pane-stats in the panel deletion left the chain
behind it fully dead: the ProcessTelemetry class had no importer but
its own test, PerformanceService.recordPaneProcessStats had no caller,
and PanePerformanceStats/PanePerformanceSnapshot existed only to serve
them. Dead code that keeps a test green is worse than none — it reads
as a protected contract. Found by review; same dead-chain scope the PR
already claimed.

Also repoints a transcriptParser WHY comment at the Performance
Monitor now that the system-perf popover it cited is gone.

Refs #986
- Heaviest-agent tile computes the max instead of trusting the
  sessions array's sort order, which is a main-side presentation
  choice no contract pinned.
- The growth column labels the window actually measured ('N min'),
  not a promised 15 minutes the data does not have right after launch.
- A zero-measured share row paints no bar sliver.
- Comments now record three deliberate decisions: decimal GB labels
  matching Activity Monitor (users cross-check against it), incident
  markers being a pointer shortcut while the incident list is the
  accessible path, and pinned dispatch labels (★1) being unresolvable
  by design so navigation must stay sessionId-based.

Refs #986
Three contracts the reviews found untested: sessions rank by memory
descending (the heaviest-agent tile and ranking table both treat
sessions[0] as the biggest consumer); the 256-session map cap evicts
the oldest-seen and re-admits a live evicted session with fresh
history; and the coordinator records usage only from validated
complete process pages (children summed into their owner) and clears
on stop. Mirrors the one-shared-in-flight read test for
getMonitorAgentUsage, whose comment claimed that contract.

Refs #986
…tor-ux

# Conflicts:
#	src/renderer/src/features/system-perf/ui/SystemPerfBadge.tsx
#	src/renderer/src/features/system-perf/ui/SystemPerfPopover.tsx
@Juliusolsson05
Juliusolsson05 merged commit 76ede41 into main Sep 18, 2026
2 checks passed
@Juliusolsson05
Juliusolsson05 deleted the feat/performance-monitor-ux branch September 18, 2026 01:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(performance): make the monitor's first page attribute memory and CPU to agents

1 participant