From 00a1bbd109aa2a75834cb5cf7a4ef1f62bb362b4 Mon Sep 17 00:00:00 2001 From: Alan Wizemann Date: Tue, 5 May 2026 12:07:43 +0200 Subject: [PATCH] feat(scarfmon): split nous.readCache into fileExists/readFile/decode/bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Last perf capture showed nous.readCache as a single 60-second interval — but the function does three things (transport.fileExists, transport.readFile, JSONDecoder). Splitting the measure points so the next capture localizes which step actually owns the wall-clock. Adds: - nous.readCache.fileExists (interval) — SSH `test -e` round-trip - nous.readCache.readFile (interval) — SSH `cat` round-trip - nous.readCache.bytes (event) — payload size of the cache file - nous.readCache.decode (interval) — JSON parsing cost If the next 60-second beach ball localizes to readFile, we know the cache file is somehow huge or the SSH read is hung; if it's fileExists, the path resolution is the issue; if decode, we have malformed JSON. All three wear the same outer wrapper so the existing nous.readCache total stays for trend comparison. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Services/NousModelCatalogService.swift | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/scarf/Packages/ScarfCore/Sources/ScarfCore/Services/NousModelCatalogService.swift b/scarf/Packages/ScarfCore/Sources/ScarfCore/Services/NousModelCatalogService.swift index 2913bc0..4a277b9 100644 --- a/scarf/Packages/ScarfCore/Sources/ScarfCore/Services/NousModelCatalogService.swift +++ b/scarf/Packages/ScarfCore/Sources/ScarfCore/Services/NousModelCatalogService.swift @@ -98,19 +98,36 @@ public struct NousModelCatalogService: Sendable { public func readCache() -> NousModelsCache? { ScarfMon.measure(.diskIO, "nous.readCache") { let transport = context.makeTransport() - guard transport.fileExists(cachePath) else { return nil } + // Split into separate measure points so the next perf + // capture localizes the 60-second observed beach ball + // — was it the fileExists probe, the read itself, or + // the JSON decode? Each on its own ScarfMon row. + let exists = ScarfMon.measure(.diskIO, "nous.readCache.fileExists") { + transport.fileExists(cachePath) + } + guard exists else { return nil } do { - let data = try transport.readFile(cachePath) - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - let cache = try decoder.decode(NousModelsCache.self, from: data) - guard cache.version == NousModelsCache.currentVersion else { - Self.logger.info("nous models cache schema mismatch (got v\(cache.version), expected v\(NousModelsCache.currentVersion)); ignoring") - return nil + let data = try ScarfMon.measure(.diskIO, "nous.readCache.readFile") { + try transport.readFile(cachePath) + } + ScarfMon.event(.diskIO, "nous.readCache.bytes", count: 1, bytes: data.count) + return ScarfMon.measure(.diskIO, "nous.readCache.decode") { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + do { + let cache = try decoder.decode(NousModelsCache.self, from: data) + guard cache.version == NousModelsCache.currentVersion else { + Self.logger.info("nous models cache schema mismatch (got v\(cache.version), expected v\(NousModelsCache.currentVersion)); ignoring") + return Optional.none + } + return cache + } catch { + Self.logger.warning("couldn't decode nous models cache: \(error.localizedDescription, privacy: .public)") + return Optional.none + } } - return cache } catch { - Self.logger.warning("couldn't decode nous models cache: \(error.localizedDescription, privacy: .public)") + Self.logger.warning("couldn't read nous models cache: \(error.localizedDescription, privacy: .public)") return nil } }