cmux-tui: jsonschema 0.34 cuts ~440 MB of idle memory; add a memory bench
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.
| Scenario | Before | After | Change |
|---|---|---|---|
| Idle, 1 workspace | 1032 MB | 596 MB | −436 MB (−42%) |
| 10 workspaces | 1292 MB | 846 MB | −446 MB |
| 30 workspaces | 1697 MB | 1282 MB | −415 MB |
| cmux-tui app-session daemon, idle | 319 MB | 76 MB | −243 MB |
| cmux-tui Chief daemon, idle | 237 MB | 39 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 kind | Count | Footprint |
|---|---|---|---|
| 1 | WebKit WebContent (one per agent chat page, plus app pages) | 14 | 976 MB |
| 2 | cmux-tui app-session daemon | 1 | 280 MB |
| 3 | cmux-tui Chief daemon | 1 | 241 MB |
| 4 | App | 1 | 208 MB |
| 5 | CEF utility (network, storage) | 2 | 44 MB |
| 6 | CEF GPU | 1 | 28 MB |
| 7 | WebKit GPU | 1 | 15 MB |
| 8 | acpmux daemon | 1 | 13 MB |
| 9 | WebKit networking | 1 | 5 MB |
| 10 | acpmux router | 1 | 4 MB |
| 11 | cmux-browser-host | 1 | 3 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_mibcounts 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).
- Red on the test commit:
- Full hosted cmux-tui verification: hosted run 38231315916: green except
every_safe_transport_operation_has_a_noun_first_path(agit.checkpoint.diffCLI-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.pyon a capture mini (attach mode) and on a fleet mini (launch mode). The after app launched with this PR'scmux-tuiand 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