Files
yattee/Yattee/Services/Logging/PerformanceCounters.swift
Arkadiusz Fal 9412ca916c Add performance logging and background diagnostics
Performance Logging (Developer settings) logs a resource sample every 5s
in the foreground and background: CPU by area and thread, wakeups, GPU,
memory, disk, activity counters as rates (mpv property updates, player
time publishes, renders, saves, network, images, CloudKit events, log
entries, main run-loop wakeups), SwiftUI body evaluations of the player
views, and app/player/battery context. Each foreground/background
transition logs a summary of the period. MetricKit metrics and
diagnostics (CPU exceptions, hangs) are logged whenever logging is on.

Background diagnostics (iOS) log lifecycle, audio session and power
events with an app/player/render snapshot and a heartbeat that detects
suspension gaps and main-thread stalls. Logs are persisted per session
in Caches/Logs and the previous session is included in exports, so logs
survive the app being swiped away.
2026-10-24 19:36:51 +02:00

52 lines
1.5 KiB
Swift

//
// PerformanceCounters.swift
// Yattee
//
// Activity counters for performance logging. Each counter is reported as a
// rate by PerformanceMonitor, so a log shows what the app was busy doing.
//
import Foundation
import Synchronization
/// Named activity counters, safe to bump from any thread.
///
/// Counting is a no-op unless Performance Logging is on, so call sites can
/// stay in hot paths:
///
/// PerfCounter.count("mpv.prop.time-pos")
///
/// In a SwiftUI body, count evaluations with `let _ = PerfCounter.body("Name")`.
enum PerfCounter {
private static let isEnabled = Atomic<Bool>(false)
private static let counts = Mutex<[String: Int]>([:])
@inline(__always)
static var enabled: Bool { isEnabled.load(ordering: .relaxed) }
static func setEnabled(_ enabled: Bool) {
isEnabled.store(enabled, ordering: .relaxed)
}
static func count(_ key: String, by amount: Int = 1) {
guard enabled else { return }
counts.withLock { $0[key, default: 0] += amount }
}
/// Counts one body evaluation of the named view.
static func body(_ name: StaticString) {
guard enabled else { return }
let key = "view." + name.description
counts.withLock { $0[key, default: 0] += 1 }
}
/// Returns the counts accumulated since the last call and resets them.
static func take() -> [String: Int] {
counts.withLock {
defer { $0 = [:] }
return $0
}
}
}