From 109a813bf447625f08347dfde749d3df120b1432 Mon Sep 17 00:00:00 2001 From: teamleaderleo Date: Sat, 26 Sep 2026 10:04:04 -0400 Subject: [PATCH] perf: skip the per-flush stat and mkdir in the event log writer 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) --- Sources/CmuxEventLogWriter.swift | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/Sources/CmuxEventLogWriter.swift b/Sources/CmuxEventLogWriter.swift index aae79d557e08..64dde29ec712 100644 --- a/Sources/CmuxEventLogWriter.swift +++ b/Sources/CmuxEventLogWriter.swift @@ -137,18 +137,14 @@ final class CmuxEventLogWriter: @unchecked Sendable { private func append(_ lines: [String]) { guard !lines.isEmpty else { return } do { - try FileManager.default.createDirectory( - at: eventLogURL.deletingLastPathComponent(), - withIntermediateDirectories: true - ) let fileManager = FileManager.default - if !fileManager.fileExists(atPath: eventLogURL.path) { - _ = fileManager.createFile(atPath: eventLogURL.path, contents: nil) - } - var handle = try FileHandle(forWritingTo: eventLogURL) + // Every agent hook event flushes here, so the steady state is one + // open + seek + write. Directory and file creation run only when the + // open fails, and the end-of-file offset doubles as the current size + // instead of a separate attributesOfItem stat per flush. + var handle = try openForAppending(fileManager: fileManager) defer { try? handle.close() } - try handle.seekToEnd() - var currentSize = Self.fileSize(at: eventLogURL, fileManager: fileManager) + var currentSize = try handle.seekToEnd() // One file segment at a time bounds the extra buffer to the rotation limit, // except for an indivisible oversized record (the existing write-whole policy). var batchData = Data() @@ -178,6 +174,20 @@ final class CmuxEventLogWriter: @unchecked Sendable { } } + private func openForAppending(fileManager: FileManager) throws -> FileHandle { + if let handle = try? FileHandle(forWritingTo: eventLogURL) { + return handle + } + try fileManager.createDirectory( + at: eventLogURL.deletingLastPathComponent(), + withIntermediateDirectories: true + ) + if !fileManager.fileExists(atPath: eventLogURL.path) { + _ = fileManager.createFile(atPath: eventLogURL.path, contents: nil) + } + return try FileHandle(forWritingTo: eventLogURL) + } + private func rotate(fileManager: FileManager) throws { let currentSize = Self.fileSize(at: eventLogURL, fileManager: fileManager) let rotatedURL = eventLogURL.appendingPathExtension("1")