Skip to content
This repository was archived by the owner on Nov 16, 2025. It is now read-only.

Commit f9d0b6e

Browse files
committed
perf: fix CPU overload on startup and clean up old cache data
- Limit concurrent file processing to max 4 tasks (was using all CPU cores) - Add 1.5s startup delay before heavy processing begins - Clean up ALL old UserDefaults permanent cache entries (was leaving hundreds) - Increment cache version to 7 to force migration and cleanup - Add 0.5s delay between providers during initial load This prevents the app from becoming unresponsive on startup when processing hundreds of Claude log files. The old implementation was using all CPU cores and leaving massive amounts of data in UserDefaults.
1 parent ec0659b commit f9d0b6e

File tree

3 files changed

+38
-18
lines changed

3 files changed

+38
-18
lines changed

VibeMeter/Core/Services/ClaudeLogCacheManager.swift

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public final class ClaudeLogCacheManager: @unchecked Sendable {
1313
private let cacheVersionKey = "com.vibemeter.claudeLogCacheVersion"
1414

1515
// Cache schema version - increment this when parser format changes
16-
private let currentCacheVersion = 6 // Incremented for database migration
16+
private let currentCacheVersion = 7 // Force cleanup of old permanent cache entries
1717

1818
// Cache validity duration
1919
private let cacheValidityDuration: TimeInterval = 300 // 5 minutes
@@ -340,21 +340,20 @@ public final class ClaudeLogCacheManager: @unchecked Sendable {
340340
private func migrateToDatabase() async {
341341
logger.info("Starting migration from UserDefaults to database")
342342

343-
// Clean up old UserDefaults keys
344-
let oldKeys = [
345-
"com.vibemeter.claudeLogCache",
346-
"com.vibemeter.claudeFileHashCache",
347-
"com.vibemeter.claudeLogPermanentCache",
348-
"com.vibemeter.claudeLogPermanentCacheMetadata"
349-
]
343+
// Clean up ALL old UserDefaults keys including individual permanent cache entries
344+
let allKeys = userDefaults.dictionaryRepresentation().keys
345+
var removedCount = 0
350346

351-
for key in oldKeys {
352-
if userDefaults.object(forKey: key) != nil {
347+
for key in allKeys {
348+
if key.contains("claudeLog") || key.contains("claudeFileHash") {
353349
userDefaults.removeObject(forKey: key)
354-
logger.debug("Removed old UserDefaults key: \(key)")
350+
removedCount += 1
355351
}
356352
}
357353

358-
logger.info("Migration complete - old UserDefaults cache cleared")
354+
// Also remove the file hash cache
355+
userDefaults.removeObject(forKey: "com.vibemeter.claudeFileHashCache")
356+
357+
logger.info("Migration complete - removed \(removedCount) old UserDefaults cache entries")
359358
}
360359
}

VibeMeter/Core/Services/ClaudeLogProcessor.swift

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,22 @@ actor ClaudeLogProcessor {
5353

5454
let collector = ResultCollector(cache: cache)
5555

56-
// Use all available processors for maximum parallelism
57-
let processorCount = ProcessInfo.processInfo.activeProcessorCount
58-
logger.info("Processing \(fileURLs.count) log files using \(processorCount) processors")
56+
// Limit concurrent tasks to prevent CPU overload
57+
let maxConcurrentTasks = min(4, ProcessInfo.processInfo.activeProcessorCount)
58+
logger.info("Processing \(fileURLs.count) log files using max \(maxConcurrentTasks) concurrent tasks")
5959

60-
// Process all files concurrently with TRUE parallelism
60+
// Process files with controlled concurrency
6161
await withTaskGroup(of: ([ClaudeLogEntry], String, Data)?.self, returning: Void.self) { group in
62-
// Add all tasks at once - Swift concurrency will manage the actual parallelism
63-
for fileURL in fileURLs {
62+
var fileIndex = 0
63+
64+
// Start initial batch of tasks
65+
for _ in 0..<min(maxConcurrentTasks, fileURLs.count) {
66+
let fileURL = fileURLs[fileIndex]
6467
group.addTask(priority: .background) { [self] in
6568
// Process file without actor isolation to allow true parallelism
6669
return await self.processFileParallel(fileURL, existingCache: cache, cacheManager: cacheManager)
6770
}
71+
fileIndex += 1
6872
}
6973

7074
// Collect results as they complete
@@ -78,6 +82,15 @@ actor ClaudeLogProcessor {
7882
await collector.incrementProcessedCount()
7983
}
8084

85+
// Start next task if there are more files
86+
if fileIndex < fileURLs.count {
87+
let fileURL = fileURLs[fileIndex]
88+
group.addTask(priority: .background) { [self] in
89+
return await self.processFileParallel(fileURL, existingCache: cache, cacheManager: cacheManager)
90+
}
91+
fileIndex += 1
92+
}
93+
8194
// Send progress update every 10 files or on last file
8295
let shouldUpdateProgress = processedCount % 10 == 0 || processedCount == fileURLs.count
8396

VibeMeter/Core/Services/MultiProviderDataOrchestrator.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ public final class MultiProviderDataOrchestrator {
136136

137137
// Trigger initial data refresh for providers with existing tokens
138138
Task {
139+
// Add a small delay to let the UI settle before heavy processing
140+
try? await Task.sleep(nanoseconds: 1_500_000_000) // 1.5 seconds
141+
139142
let loggedInProviders = loginManager.loggedInProviders
140143
logger.info("Starting initial data refresh for \(loggedInProviders.count) logged-in providers")
141144

@@ -146,6 +149,11 @@ public final class MultiProviderDataOrchestrator {
146149
logger.info("Claude: Initial check - hasToken: \(hasToken)")
147150
}
148151
await refreshData(for: provider, showSyncedMessage: false)
152+
153+
// Small delay between providers to prevent simultaneous heavy processing
154+
if provider == .claude {
155+
try? await Task.sleep(nanoseconds: 500_000_000) // 0.5 seconds
156+
}
149157
}
150158

151159
// Start monitoring systems

0 commit comments

Comments
 (0)