CPU und GPU zwischen Auslastung und Temperatur umschaltbar
Die M-Serie-Konvention gilt auch auf dem M5: Tp* sind die CPU-Kerne, Tg* die GPU-Cluster. Gezeigt wird der wärmste Punkt, nicht der Durchschnitt — gedrosselt wird nach dem heißesten Kern. Gemessen: CPU 76 °C, GPU 70 °C. Die Fühlerliste wird zur Laufzeit gesucht statt fest verdrahtet: welche es gibt, hängt am Modell. Auf diesem Mac sind es 285 Temperaturfühler. Zwei Leistungsprobleme, gefunden bevor sie jemandem auffallen konnten: Die Suche geht alle 3486 Keys durch und braucht knapp eine Sekunde. Auf dem Hauptthread wäre das eine spürbare Startverzögerung — sie läuft jetzt im Hintergrund, bis dahin zeigt die Oberfläche "—" statt einer erfundenen Zahl. Fünfzig Fühler zu lesen kostete 34 ms je Abtastung, bei 1 Hz also 3,4 % CPU. Zwei Maßnahmen: Typ und Größe je Key werden zwischengespeichert (sie stehen in der Firmware fest, sie bei jedem Lesen zu erfragen verdoppelte die IOKit-Aufrufe), und Temperaturen werden höchstens alle zwei Sekunden neu gelesen — sie bewegen sich in einer Sekunde ohnehin kaum. Ergebnis: 6 ms Grundlast, 18 ms alle zwei Sekunden, im Mittel gut 1 %. Dabei noch eine Eigenheit gefunden: die Byte-Reihenfolge im SMC ist nicht durchgängig. Messwerte wie B0AV kommen little-endian aus dem Akku-Baustein, die Metadaten des SMC selbst dagegen big-endian. #KEY little-endian gelesen ergibt 2 651 652 096 statt 3486 — die Fühlersuche brach damit still ab und alle Temperaturen blieben leer. Lüfterdrehzahlen stehen jetzt im Sensor-Popover, mit Balken zwischen Minimum und Maximum der Firmware: "2321 U/min" allein sagt niemandem, ob das viel ist. Die Steuerung selbst kommt mit Phase 6 — Schreibzugriff auf den SMC verlangt root — und der Popover sagt das auch. 145 Tests grün.
This commit is contained in:
@@ -80,6 +80,8 @@ private struct CompactMetric: View {
|
||||
let model: MetricsModel
|
||||
let metric: MetricKind
|
||||
|
||||
private var mode: MetricDisplayMode { model.displayMode(for: metric) }
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Label {
|
||||
@@ -94,10 +96,11 @@ private struct CompactMetric: View {
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
Text(MetricSummary.value(metric, model.snapshot))
|
||||
Text(MetricSummary.value(metric, model.snapshot, mode: mode))
|
||||
.font(Onyx.Font.metric)
|
||||
.foregroundStyle(MetricSummary.color(metric, model.snapshot))
|
||||
.animation(Onyx.Motion.value, value: MetricSummary.value(metric, model.snapshot))
|
||||
.foregroundStyle(MetricSummary.color(metric, model.snapshot, mode: mode))
|
||||
.animation(Onyx.Motion.value,
|
||||
value: MetricSummary.value(metric, model.snapshot, mode: mode))
|
||||
|
||||
if let detail = MetricSummary.detail(metric, model.snapshot) {
|
||||
Text(detail)
|
||||
@@ -120,7 +123,8 @@ private struct WideMetric: View {
|
||||
CompactMetric(model: model, metric: metric)
|
||||
.frame(width: 84)
|
||||
Sparkline(values: model.series(metric),
|
||||
tint: MetricSummary.color(metric, model.snapshot))
|
||||
tint: MetricSummary.color(metric, model.snapshot,
|
||||
mode: model.displayMode(for: metric)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,6 +228,37 @@ private struct SensorList: View {
|
||||
/// Menüleiste nicht auseinanderlaufen.
|
||||
public enum MetricSummary {
|
||||
|
||||
/// Der anzuzeigende Wert, abhängig vom gewählten Modus.
|
||||
public static func value(_ metric: MetricKind, _ snapshot: MetricsSnapshot,
|
||||
mode: MetricDisplayMode) -> String {
|
||||
guard mode == .temperature, metric.supportsDisplayModes else {
|
||||
return value(metric, snapshot)
|
||||
}
|
||||
let celsius = metric == .cpu ? snapshot.cpu.temperature : snapshot.gpuTemperature
|
||||
// Solange die Fühlersuche im Hintergrund läuft, gibt es keinen Wert.
|
||||
// „—" ist dann die richtige Auskunft, nicht eine 0.
|
||||
return celsius.map(MetricFormat.temperature) ?? "—"
|
||||
}
|
||||
|
||||
public static func color(_ metric: MetricKind, _ snapshot: MetricsSnapshot,
|
||||
mode: MetricDisplayMode) -> Color {
|
||||
guard mode == .temperature, metric.supportsDisplayModes else {
|
||||
return color(metric, snapshot)
|
||||
}
|
||||
let celsius = metric == .cpu ? snapshot.cpu.temperature : snapshot.gpuTemperature
|
||||
return temperatureColor(celsius ?? 0)
|
||||
}
|
||||
|
||||
public static func fraction(_ metric: MetricKind, _ snapshot: MetricsSnapshot,
|
||||
mode: MetricDisplayMode) -> Double {
|
||||
guard mode == .temperature, metric.supportsDisplayModes else {
|
||||
return fraction(metric, snapshot)
|
||||
}
|
||||
let celsius = metric == .cpu ? snapshot.cpu.temperature : snapshot.gpuTemperature
|
||||
// Auf 100 °C normiert: darüber wird ohnehin gedrosselt.
|
||||
return min(max((celsius ?? 0) / 100, 0), 1)
|
||||
}
|
||||
|
||||
public static func value(_ metric: MetricKind, _ snapshot: MetricsSnapshot) -> String {
|
||||
switch metric {
|
||||
case .cpu: MetricFormat.percent(snapshot.cpu.total)
|
||||
|
||||
Reference in New Issue
Block a user