Skip to content

perf: skip the per-flush stat and mkdir in the event log writer - #14828

Merged
teamleaderleo merged 1 commit into
mainfrom
perf/event-log-flush-no-stat
Sep 26, 2026
Merged

teamleaderleo merged 1 commit into
mainfrom
perf/event-log-flush-no-stat

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Every cmux event (agent hooks, feed items, sidebar status updates) flushes the durable log at ~/.cmuxterm/events.jsonl. Each flush ran createDirectory, fileExists, open, seek, and then attributesOfItem to learn the file size. With several Claude sessions running, that stat was the largest idle CPU cost in the app.

This change reads the size from the seekToEnd() offset and only creates the directory and file when the open fails. Rotation logic is unchanged.

Evidence

  • Attach-only sample of a running 0.64.25 (build 106) app with active Claude sessions, 10 s: the com.cmuxterm.event-log queue was the busiest non-main queue (450 busy samples), and 353 of those were _FileManagerImpl.attributesOfItem, mostly bridging and freeing the attribute dictionary.
  • Per-flush micro benchmark (one 650-byte line per flush, 3000 flushes, swiftc -O -DDEBUG, alternating runs): 430 to 650 us of CPU per flush before, 195 to 265 us after. Wall time on that machine was dominated by system load, so CPU time is the number to read.
  • scripts/benchmark-event-log-writes.py --baseline upstream/main: behavior checks pass for both variants, including the 16 MiB rotation crossing.

Verification

No local app build (this Mac is not allowed to run them). Relying on CI for compile and the CmuxEventLogWriterTests suite.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Cuts idle CPU cost in the durable event log writer by reading the file size from the seekToEnd() offset instead of a separate per-flush stat, and only creating the directory and file when the open fails. Per-flush CPU drops from 430–650 us to 195–265 us; rotation and write behavior are unchanged.

Verification

  • Micro benchmark and behavior checks (including the 16 MiB rotation crossing) pass for both variants.
  • Compile and CmuxEventLogWriterTests are covered by CI.

Written for commit 109a813. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Event log writes now handle cases where the log file or its parent directory does not yet exist, creating them as needed so appending can proceed. The writer also checks the open file’s current size when appending. Existing log rotation and batched writing behavior remain unchanged.

Every cmux event (agent hook, feed item, sidebar status) flushes the
durable log. Each flush ran createDirectory, fileExists, open, seek and
then attributesOfItem to learn the file size. An attach-only sample of a
running 0.64.25 app with active Claude sessions put 353 of the event-log
queue's 450 busy samples in attributesOfItem, which made this queue the
largest idle CPU consumer in the process.

The end-of-file offset from seekToEnd is the size, and the directory and
file only need creating when the open fails. A per-flush micro benchmark
(one 650-byte line per flush, 3000 flushes, swiftc -O) drops from
430-650 us to 195-265 us of CPU per flush. Rotation behavior is unchanged
(scripts/benchmark-event-log-writes.py behavior checks pass, including
the 16 MiB crossing case).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: fbfc9258-b854-4742-836c-b2b53d1b5409

📥 Commits

Reviewing files that changed from the base of the PR and between 1dfd0e6 and 109a813.

📒 Files selected for processing (1)
  • Sources/CmuxEventLogWriter.swift

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.


📝 Walkthrough

Walkthrough

The event log writer now opens the log through an append helper and reads its current size from the handle’s end-of-file offset. The helper creates the parent directory and file when needed, then retries opening the log.

Changes

Event log append handling

Layer / File(s) Summary
Open the event log for appending
Sources/CmuxEventLogWriter.swift
append(_:) uses openForAppending and reads the current size from the handle. The helper opens the existing log or creates the directory and file before retrying. Rotation and batched writes remain in place.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Refactor

Suggested reviewers: austinywang

Merge Risk: ⚪ Minimal · up to 109a8

No actionable merge-blocking issue remains; the event log continues to append and rotate as before.

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main performance change: avoiding per-flush stat and directory creation in the event log writer.
Description check ✅ Passed The description explains the problem, implementation, performance evidence, behavior checks, and CI limitation. It does not use the template headings or include the checklist, but the required technic…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Cloud Persistent Session And Early Input ✅ Passed PASS: The pull request changes only Sources/CmuxEventLogWriter.swift. The diff optimizes event-log file opening, EOF sizing, and existing rotation. It does not change Cloud terminal creation, cmux-t…
Cmux Swift Actor Isolation ✅ Passed The diff only changes event-log file-opening and size handling in CmuxEventLogWriter.swift. It adds no model, service protocol, UI store, or new shared Sendable reference type. `CmuxEventLogWriter…
Cmux Swift Blocking Runtime ✅ Passed The diff does not introduce or materially expand any synchronization primitive listed by the check. It replaces existing synchronous file setup, seekToEnd(), and file-size stat work with `openForApp…
Cmux Browser Automation Off-Main ✅ Passed PASS. The pull request changes only Sources/CmuxEventLogWriter.swift. The diff updates event-log file opening, seeking, and rotation logic. It contains no browser.* command, WebKit callback, scree…
Cmux Expensive Synchronous Load ✅ Passed The diff does not add an expensive synchronous agent-history load. CmuxEventLogWriter performs event-log file I/O on its existing .utility DispatchQueue, and enqueue schedules `flushPendingLin…
Cmux Cache Substitution Correctness ✅ Passed The diff does not substitute a cache for an authoritative read. append(_:) opens the event-log file and immediately obtains its current end offset with handle.seekToEnd(). That offset is a fresh f…
Cmux No Hacky Sleeps ✅ Passed PASS. The authoritative PR diff changes only Sources/CmuxEventLogWriter.swift, which is Swift. The custom check covers TypeScript, JavaScript, shell, and non-Swift build/runtime scripts. The changed…
Cmux Algorithmic Complexity ✅ Passed The changed production code remains linear. append(_:) performs one pass over the pending lines and does not add nested scans, sorting, filtering, or per-target rescans. The pending queue has an exp…
Cmux Swift Concurrency ✅ Passed PASS — The diff only changes synchronous file-opening and size-reading logic in Sources/CmuxEventLogWriter.swift. The existing DispatchQueue and its two .async calls are identical in the base an…
Cmux Swift @Concurrent ✅ Passed The diff changes only synchronous append(_:) logic and adds synchronous openForAppending(fileManager:). It adds no async, nonisolated async, or @concurrent function. append(_:) remains rea…
Cmux Swift Package Boundaries ✅ Passed The diff changes only the existing app-specific CmuxEventLogWriter implementation. It adds a private open fallback and replaces a per-flush stat with seekToEnd(). It does not introduce a new featu…
Cmux Swiftpm Lockfiles ✅ Passed PASS: The authoritative PR diff changes only Sources/CmuxEventLogWriter.swift. It does not change a Package.swift, Package.resolved, .gitignore, workflow, or Xcode package reference. The patch…
Cmux Swift Logging ✅ Passed The PR changes only event-log file opening and size tracking in Sources/CmuxEventLogWriter.swift. It adds no print, debugPrint, dump, NSLog, ad hoc diagnostic output, or new log statement. T…
Cmux User-Facing Error Privacy ✅ Passed PASS. The PR changes only event-log file I/O and adds a developer-only performance comment. It does not add or change user-facing errors, alerts, CLI output, API bodies, or recovery copy. The existing…
Cmux Full Internationalization ✅ Passed The PR changes only Sources/CmuxEventLogWriter.swift and adds no user-facing text. The added lines are developer-only comments and file I/O code. Existing logger messages are unchanged, and no catal…
Cmux Swiftui State Layout ✅ Passed PASS: The PR changes only Sources/CmuxEventLogWriter.swift. The diff updates Foundation file I/O in CmuxEventLogWriter; it adds no SwiftUI import, view, ObservableObject, @Published, `@Observa…
Cmux Architecture Rethink ✅ Passed PASS: The diff is a small local file-I/O optimization in CmuxEventLogWriter. It adds one private openForAppending helper and uses the existing file handle offset as the size. It does not add sleep…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS. The PR changes only Sources/CmuxEventLogWriter.swift. The diff updates event-log file opening, sizing, and rotation logic. It does not add or materially change any NSWindow, NSPanel, `NSWi…
Cmux Source Artifacts ✅ Passed The PR changes only Sources/CmuxEventLogWriter.swift. The diff contains hand-written Swift source that updates event-log append behavior. It does not add logs, screenshots, recordings, temporary dir…
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS. The PR changes only Sources/CmuxEventLogWriter.swift. The new openForAppending(fileManager:) helper is private, has no test/debug naming, and performs production file-opening behavior. The…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@teamleaderleo
teamleaderleo merged commit 443d050 into main Sep 26, 2026
63 of 64 checks passed
@teamleaderleo
teamleaderleo deleted the perf/event-log-flush-no-stat branch September 26, 2026 14:24
@github-actions

Copy link
Copy Markdown
Contributor

Merge receipt for 109a813bf4: every check was green at merge (16 verified; 14 skipped by policy). Full suite runs on main after merge.

teamleaderleo added a commit to teamleaderleo/cmux that referenced this pull request Sep 26, 2026
Every event flush (agent hooks, feed items, sidebar metadata; about 75
per minute on a busy machine) opened ~/.cmuxterm/events.jsonl, seeked,
wrote, and closed it. manaflow-ai#14828 already dropped the per-flush stat and
mkdir; this keeps the descriptor open on the event-log queue instead.

The log is opened with O_APPEND | O_CREAT, so writes always land at the
current end of file even when another cmux process sharing the log
appended since the last flush; a seek-then-write handle could overwrite
those lines. The rotation size comes from fstat on the open descriptor.
The handle is reused only while the path still names the same file
(device and inode match) and is reopened after an external rotation,
deletion, or write failure. Directory creation still runs only when the
open reports a missing parent. Rotation limits are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rustybret pushed a commit to rustybret/bmux that referenced this pull request Sep 26, 2026
2123231 fix(cloud): offer Pi in the cloud agent menu and vm.cloud_agent_open (manaflow-ai#14819)
909fcc7 fix(settings): stop promising a Tailscale QR the pairing window no longer shows (manaflow-ai#14817)
5d2bc04 fix(custom-sidebar): render Menu nodes so context-menu submenus appear (manaflow-ai#14808)
45815f7 Keep a collapsed sidebar group folded when the workspace below it closes (manaflow-ai#10169)
4bf0ea0 perf(shell): stop spawning tmux and rm on every prompt when idle (manaflow-ai#14833)
443d050 perf: skip the per-flush stat and mkdir in the event log writer (manaflow-ai#14828)
teamleaderleo added a commit that referenced this pull request Sep 26, 2026
* test: event-log flushes must reuse one open handle

Each durable event flush reopened ~/.cmuxterm/events.jsonl, created its
directory, seeked, and stat'ed it. The first test fails until the writer
keeps its handle open across flushes. The second pins the safety
condition for that change: after another process rotates or deletes the
shared log, the next flush lands in the file now at the path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* perf: keep the durable event log open across flushes

Every event flush (agent hooks, feed items, sidebar metadata; about 75
per minute on a busy machine) opened ~/.cmuxterm/events.jsonl, seeked,
wrote, and closed it. #14828 already dropped the per-flush stat and
mkdir; this keeps the descriptor open on the event-log queue instead.

The log is opened with O_APPEND | O_CREAT, so writes always land at the
current end of file even when another cmux process sharing the log
appended since the last flush; a seek-then-write handle could overwrite
those lines. The rotation size comes from fstat on the open descriptor.
The handle is reused only while the path still names the same file
(device and inode match) and is reopened after an external rotation,
deletion, or write failure. Directory creation still runs only when the
open reports a missing parent. Rotation limits are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Cover external rotation that leaves a replacement log at the path

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

1 participant