mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Frequency and Timeout settings
This commit is contained in:
parent
0d61a4024f
commit
51b698d189
4 changed files with 166 additions and 12 deletions
|
|
@ -5614,6 +5614,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Controls how often system metrics refresh while monitoring is active." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Copy event link" : {
|
"Copy event link" : {
|
||||||
"comment" : "A tooltip for a button that copies the link of a reminder event to the clipboard.",
|
"comment" : "A tooltip for a button that copies the link of a reminder event to the clipboard.",
|
||||||
|
|
@ -9617,6 +9620,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"High-frequency updates without a timeout can increase battery usage." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"History Size" : {
|
"History Size" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -12581,6 +12587,9 @@
|
||||||
},
|
},
|
||||||
"Monitor clipboard changes and keep a history of recent copies. Use Cmd+Shift+V to quickly access clipboard history." : {
|
"Monitor clipboard changes and keep a history of recent copies. Use Cmd+Shift+V to quickly access clipboard history." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Monitoring Behavior" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Monitoring Status" : {
|
"Monitoring Status" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -15677,6 +15686,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Sampling can continue while the notch is closed when the timeout is disabled." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Save" : {
|
"Save" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -17864,6 +17876,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Stats update interval" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Status & Actions" : {
|
"Status & Actions" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -19121,6 +19136,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Update interval" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Update Timer" : {
|
"Update Timer" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
|
|
@ -19955,6 +19973,9 @@
|
||||||
},
|
},
|
||||||
"When disabled, all keyboard shortcuts will be inactive. You can still use the UI controls." : {
|
"When disabled, all keyboard shortcuts will be inactive. You can still use the UI controls." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"When enabled, stats monitoring stops a few seconds after the notch closes." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"When enabled, the Dynamic Island will expand horizontally to match the animation size" : {
|
"When enabled, the Dynamic Island will expand horizontally to match the animation size" : {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2260,6 +2260,8 @@ private struct TimerPresetComponentControl: View {
|
||||||
struct StatsSettings: View {
|
struct StatsSettings: View {
|
||||||
@ObservedObject var statsManager = StatsManager.shared
|
@ObservedObject var statsManager = StatsManager.shared
|
||||||
@Default(.enableStatsFeature) var enableStatsFeature
|
@Default(.enableStatsFeature) var enableStatsFeature
|
||||||
|
@Default(.statsStopWhenNotchCloses) var statsStopWhenNotchCloses
|
||||||
|
@Default(.statsUpdateInterval) var statsUpdateInterval
|
||||||
@Default(.showCpuGraph) var showCpuGraph
|
@Default(.showCpuGraph) var showCpuGraph
|
||||||
@Default(.showMemoryGraph) var showMemoryGraph
|
@Default(.showMemoryGraph) var showMemoryGraph
|
||||||
@Default(.showGpuGraph) var showGpuGraph
|
@Default(.showGpuGraph) var showGpuGraph
|
||||||
|
|
@ -2271,6 +2273,21 @@ struct StatsSettings: View {
|
||||||
[showCpuGraph, showMemoryGraph, showGpuGraph, showNetworkGraph, showDiskGraph].filter { $0 }.count
|
[showCpuGraph, showMemoryGraph, showGpuGraph, showNetworkGraph, showDiskGraph].filter { $0 }.count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var formattedUpdateInterval: String {
|
||||||
|
let seconds = Int(statsUpdateInterval.rounded())
|
||||||
|
if seconds >= 60 {
|
||||||
|
return "60 s (1 min)"
|
||||||
|
} else if seconds == 1 {
|
||||||
|
return "1 s"
|
||||||
|
} else {
|
||||||
|
return "\(seconds) s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var shouldShowStatsBatteryWarning: Bool {
|
||||||
|
!statsStopWhenNotchCloses && statsUpdateInterval <= 5
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
|
|
@ -2302,6 +2319,45 @@ struct StatsSettings: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
if enableStatsFeature {
|
if enableStatsFeature {
|
||||||
|
Section {
|
||||||
|
Defaults.Toggle("Stop monitoring after closing the notch", key: .statsStopWhenNotchCloses)
|
||||||
|
.help("When enabled, stats monitoring stops a few seconds after the notch closes.")
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
HStack {
|
||||||
|
Text("Update interval")
|
||||||
|
Spacer()
|
||||||
|
Text(formattedUpdateInterval)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider(value: $statsUpdateInterval, in: 1...60, step: 1)
|
||||||
|
.accessibilityLabel("Stats update interval")
|
||||||
|
|
||||||
|
Text("Controls how often system metrics refresh while monitoring is active.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldShowStatsBatteryWarning {
|
||||||
|
Label {
|
||||||
|
Text("High-frequency updates without a timeout can increase battery usage.")
|
||||||
|
} icon: {
|
||||||
|
Image(systemName: "exclamationmark.triangle.fill")
|
||||||
|
}
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.orange)
|
||||||
|
.padding(.top, 4)
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Monitoring Behavior")
|
||||||
|
} footer: {
|
||||||
|
Text("Sampling can continue while the notch is closed when the timeout is disabled.")
|
||||||
|
.multilineTextAlignment(.trailing)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle("CPU Usage", key: .showCpuGraph)
|
Defaults.Toggle("CPU Usage", key: .showCpuGraph)
|
||||||
Defaults.Toggle("Memory Usage", key: .showMemoryGraph)
|
Defaults.Toggle("Memory Usage", key: .showMemoryGraph)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
import Defaults
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import IOKit
|
import IOKit
|
||||||
import IOKit.ps
|
import IOKit.ps
|
||||||
|
|
@ -442,6 +443,11 @@ class StatsManager: ObservableObject {
|
||||||
private var isProcessRefreshInFlight = false
|
private var isProcessRefreshInFlight = false
|
||||||
private let gpuCollector = GPUInfoCollector()
|
private let gpuCollector = GPUInfoCollector()
|
||||||
private let cpuSensorCollector = CPUSensorCollector()
|
private let cpuSensorCollector = CPUSensorCollector()
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
private let minUpdateInterval: TimeInterval = 1.0
|
||||||
|
private let maxUpdateInterval: TimeInterval = 60.0
|
||||||
|
private let notchCloseStopDelay: TimeInterval = 3.0
|
||||||
|
private let tabSwitchStopDelay: TimeInterval = 0.1
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
private init() {
|
private init() {
|
||||||
|
|
@ -462,6 +468,23 @@ class StatsManager: ObservableObject {
|
||||||
// Initialize baseline disk stats
|
// Initialize baseline disk stats
|
||||||
let initialDiskStats = getDiskStats()
|
let initialDiskStats = getDiskStats()
|
||||||
previousDiskStats = initialDiskStats
|
previousDiskStats = initialDiskStats
|
||||||
|
|
||||||
|
Defaults.publisher(.statsUpdateInterval, options: []).sink { [weak self] change in
|
||||||
|
self?.handleUpdateIntervalChange(change.newValue)
|
||||||
|
}.store(in: &cancellables)
|
||||||
|
|
||||||
|
Defaults.publisher(.statsStopWhenNotchCloses, options: []).sink { [weak self] change in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
if change.newValue {
|
||||||
|
if self.isMonitoring && self.lastNotchState == "closed" {
|
||||||
|
self.scheduleDelayedStop(after: self.notchCloseStopDelay)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.delayedStopTimer?.invalidate()
|
||||||
|
self.delayedStopTimer = nil
|
||||||
|
}
|
||||||
|
}.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|
@ -495,12 +518,14 @@ class StatsManager: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Stop monitoring after 3 seconds (when notch is closed or stats tab is not active)
|
if notchIsOpen && currentView != "stats" {
|
||||||
let delay = notchIsOpen ? 0.1 : 3.0 // Stop quickly when switching tabs, slowly when closing notch
|
scheduleDelayedStop(after: tabSwitchStopDelay)
|
||||||
delayedStopTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self] _ in
|
} else if !notchIsOpen {
|
||||||
DispatchQueue.main.async {
|
if Defaults[.statsStopWhenNotchCloses] {
|
||||||
self?.stopMonitoring()
|
scheduleDelayedStop(after: notchCloseStopDelay)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
scheduleDelayedStop(after: tabSwitchStopDelay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -525,13 +550,10 @@ class StatsManager: ObservableObject {
|
||||||
networkTotals = .zero
|
networkTotals = .zero
|
||||||
diskTotals = .zero
|
diskTotals = .zero
|
||||||
|
|
||||||
// Start periodic monitoring (every 1 second)
|
scheduleMonitoringTimer()
|
||||||
monitoringTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
self.updateSystemStats()
|
self.updateSystemStats()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
print("StatsManager: Monitoring started")
|
print("StatsManager: Monitoring started")
|
||||||
|
|
@ -569,6 +591,59 @@ class StatsManager: ObservableObject {
|
||||||
cpuFrequency = nil
|
cpuFrequency = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func scheduleMonitoringTimer() {
|
||||||
|
monitoringTimer?.invalidate()
|
||||||
|
|
||||||
|
let configuredInterval = Defaults[.statsUpdateInterval]
|
||||||
|
let interval = validatedUpdateInterval(configuredInterval)
|
||||||
|
|
||||||
|
if abs(interval - configuredInterval) > 0.0001 {
|
||||||
|
Defaults[.statsUpdateInterval] = interval
|
||||||
|
}
|
||||||
|
|
||||||
|
monitoringTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
Task { @MainActor in
|
||||||
|
self.updateSystemStats()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleUpdateIntervalChange(_ newValue: Double) {
|
||||||
|
let interval = validatedUpdateInterval(newValue)
|
||||||
|
|
||||||
|
if abs(interval - newValue) > 0.0001 {
|
||||||
|
Defaults[.statsUpdateInterval] = interval
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard isMonitoring else { return }
|
||||||
|
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.scheduleMonitoringTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func validatedUpdateInterval(_ value: Double) -> TimeInterval {
|
||||||
|
min(max(value, minUpdateInterval), maxUpdateInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func scheduleDelayedStop(after delay: TimeInterval) {
|
||||||
|
guard delay > 0 else {
|
||||||
|
stopMonitoring()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delayedStopTimer?.invalidate()
|
||||||
|
|
||||||
|
delayedStopTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self] _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.stopMonitoring()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private Methods
|
// MARK: - Private Methods
|
||||||
@MainActor
|
@MainActor
|
||||||
private func updateSystemStats() {
|
private func updateSystemStats() {
|
||||||
|
|
|
||||||
|
|
@ -446,6 +446,8 @@ extension Defaults.Keys {
|
||||||
// MARK: Stats Feature
|
// MARK: Stats Feature
|
||||||
static let enableStatsFeature = Key<Bool>("enableStatsFeature", default: false)
|
static let enableStatsFeature = Key<Bool>("enableStatsFeature", default: false)
|
||||||
static let autoStartStatsMonitoring = Key<Bool>("autoStartStatsMonitoring", default: true)
|
static let autoStartStatsMonitoring = Key<Bool>("autoStartStatsMonitoring", default: true)
|
||||||
|
static let statsStopWhenNotchCloses = Key<Bool>("statsStopWhenNotchCloses", default: false)
|
||||||
|
static let statsUpdateInterval = Key<Double>("statsUpdateInterval", default: 1.0)
|
||||||
static let showCpuGraph = Key<Bool>("showCpuGraph", default: true)
|
static let showCpuGraph = Key<Bool>("showCpuGraph", default: true)
|
||||||
static let showMemoryGraph = Key<Bool>("showMemoryGraph", default: true)
|
static let showMemoryGraph = Key<Bool>("showMemoryGraph", default: true)
|
||||||
static let showGpuGraph = Key<Bool>("showGpuGraph", default: true)
|
static let showGpuGraph = Key<Bool>("showGpuGraph", default: true)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue