cmux-tui: jsonschema 0.34 cuts ~440 MB of idle memory; add a memory bench

Merged
TE
1mo ago
+580-138

Each cmux-tui daemon in cmux NEXT used about 240 MB at idle. There are two of them (the app session and Chief), so together they took about 480 MB, more than half of the app's idle memory. Almost all of it was the compiled JSON Schema validator for app manifests.

Every daemon validates the first-party apps at startup and keeps the validator for the life of the process. jsonschema 0.30 cloned $ref subtrees while compiling the v2 manifest schema (55 refs, 19 defs), which retained 166 MiB per daemon. jsonschema 0.34 deduplicates $ref validator nodes. Our code only uses Validator, validator_for and draft202012::new, so the bump needs no code changes.

This PR also checks in scripts/cmux-next/bench_memory.py, so the measurement can be re-run. It reports RSS and physical footprint for each process the app owns: the app, CEF helpers, WebKit processes, cmux-tui daemons, acpmux and the browser host. It runs at 1/10/30 workspaces of terminals, chats and browser tabs, and can churn workspaces to check for leaks.

Before / after

Footprint (Activity Monitor's "Memory") summed over every process the app owns, on one fleet mini, measured back to back with bench_memory.py in launch mode. "Before" is nightly-next 3823415584601; "after" is the same bundle with this PR's cmux-tui.

ScenarioBeforeAfterChange
Idle, 1 workspace1032 MB596 MB−436 MB (−42%)
10 workspaces1292 MB846 MB−446 MB
30 workspaces1697 MB1282 MB−415 MB
cmux-tui app-session daemon, idle319 MB76 MB−243 MB
cmux-tui Chief daemon, idle237 MB39 MB−198 MB

The workspaces cycle through terminal + chat, terminal + browser and terminal.

Top contributors (nightly-next, 30 workspaces, capture mini)

From the 30-workspace run, grouped by process kind (footprint):

#Process kindCountFootprint
1WebKit WebContent (one per agent chat page, plus app pages)14976 MB
2cmux-tui app-session daemon1280 MB
3cmux-tui Chief daemon1241 MB
4App1208 MB
5CEF utility (network, storage)244 MB
6CEF GPU128 MB
7WebKit GPU115 MB
8acpmux daemon113 MB
9WebKit networking15 MB
10acpmux router14 MB
11cmux-browser-host13 MB

That is every process the app owns; there are fewer than 15 kinds. The 15 largest single processes are the two daemons, the app, and 12 WebContent processes of 74–78 MB each.

Hidden browser tabs create no CEF renderer until they are shown, and they already hibernate. Each chat tab's page keeps its own WebContent process until the tab closes. A follow-up PR hibernates hidden chat pages.

A one-hour churn (959 workspace open/close cycles at 10 workspaces) grew the app by about 0.4 MB per cycle (115 MB → 557 MB). That leak is being tracked down separately. The app-session tui daemon grew 38 MB over the first 106 cycles, then about 4 MB per 100 cycles.

Testing

  • cargo test -p cmux-app-manifest validating_a_manifest_retains_under_32_mib counts the bytes still allocated after validating a sample package.
    • Red on the test commit: manifest validation retained 166 MiB, on Linux and macOS (hosted run 38227732966).
    • Green after the bump: passed on Linux and macOS (hosted run 38229463711).
  • Full hosted cmux-tui verification: hosted run 38231315916: green except every_safe_transport_operation_has_a_noun_first_path (a git.checkpoint.diff CLI-catalog check this PR does not touch, failing on base). Release-path darwin, Linux and Windows artifacts built, including the app servers with their updated lockfiles.
  • Focused hosted run on the final head (after the review fixes): green, run 38241191087.
  • Ran bench_memory.py on a capture mini (attach mode) and on a fleet mini (launch mode). The after app launched with this PR's cmux-tui and created 30 workspaces.
  • A review subagent checked the jsonschema 0.30 → 0.34 diff against our call sites. Draft, default draft, format and regex behavior are unchanged; only the enum error text differs (is not one of "a" or "b"), and nothing matches on it. Its two bench safety findings are fixed: cleanup now ends only processes this run launched, and the bench uses the launched app's own socket.

Changelog

  • Changed: cmux NEXT uses about 400 MB less memory at idle; each background cmux-tui daemon drops from about 240 MB.

🤖 Generated with Claude Code