Add CPU/Total column showing normalized system-wide CPU usage

- Add cpuUsageTotal property to ProcessItem (100% = all cores)
- Keep cpuUsage as per-core percentage (100% = 1 core)
- Display both columns: CPU/Core and CPU/Total
- CPU/Total values now sum to match the total system CPU

This clarifies the difference between per-core and system-wide CPU
measurements that was causing confusion.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hariel1985
2026-01-31 16:37:38 +01:00
szülő ca8cde3e2c
commit 5e8343dea8
4 fájl változott, egészen pontosan 37 új sor hozzáadva és 4 régi sor törölve

Fájl megtekintése

@@ -6,7 +6,8 @@ struct ProcessItem: Identifiable, Hashable {
let pid: pid_t
let name: String
let user: String
let cpuUsage: Double
let cpuUsage: Double // Per-core: 100% = 1 core fully utilized
let cpuUsageTotal: Double // Normalized: 100% = all cores fully utilized
let memoryUsage: Int64
let threadCount: Int32
let state: ProcessState
@@ -21,6 +22,7 @@ struct ProcessItem: Identifiable, Hashable {
name: String,
user: String,
cpuUsage: Double,
cpuUsageTotal: Double,
memoryUsage: Int64,
threadCount: Int32,
state: ProcessState,
@@ -33,6 +35,7 @@ struct ProcessItem: Identifiable, Hashable {
self.name = name
self.user = user
self.cpuUsage = cpuUsage
self.cpuUsageTotal = cpuUsageTotal
self.memoryUsage = memoryUsage
self.threadCount = threadCount
self.state = state
@@ -48,6 +51,7 @@ struct ProcessItem: Identifiable, Hashable {
static func == (lhs: ProcessItem, rhs: ProcessItem) -> Bool {
lhs.pid == rhs.pid &&
lhs.cpuUsage == rhs.cpuUsage &&
lhs.cpuUsageTotal == rhs.cpuUsageTotal &&
lhs.memoryUsage == rhs.memoryUsage &&
lhs.threadCount == rhs.threadCount &&
lhs.state == rhs.state

Fájl megtekintése

@@ -11,6 +11,7 @@ final class ProcessMonitor {
private var userCache: [uid_t: String] = [:]
private let timebaseInfo: mach_timebase_info_data_t
private var refreshCounter = 0
private let processorCount = Double(ProcessInfo.processInfo.processorCount)
init() {
var info = mach_timebase_info_data_t()
@@ -111,11 +112,15 @@ final class ProcessMonitor {
let icon = fetchIcon(pid: pid)
// Calculate normalized CPU (100% = all cores)
let cpuUsageTotal = cpuUsage / processorCount
return ProcessItem(
pid: pid,
name: name,
user: user,
cpuUsage: cpuUsage,
cpuUsageTotal: cpuUsageTotal,
memoryUsage: memoryUsage,
threadCount: threadCount,
state: state,
@@ -240,6 +245,7 @@ final class ProcessMonitor {
name: name,
user: user,
cpuUsage: 0,
cpuUsageTotal: 0,
memoryUsage: 0,
threadCount: 0,
state: state,

Fájl megtekintése

@@ -100,6 +100,7 @@ struct DetailRow: View {
name: "Safari",
user: "ariel",
cpuUsage: 12.5,
cpuUsageTotal: 1.56,
memoryUsage: 512 * 1024 * 1024,
threadCount: 42,
state: .running,

Fájl megtekintése

@@ -1,7 +1,7 @@
import SwiftUI
enum ProcessSortColumn: String {
case name, pid, cpu, memory, threads, user, state
case name, pid, cpu, cpuTotal, memory, threads, user, state
}
struct ProcessView: View {
@@ -42,6 +42,8 @@ struct ProcessView: View {
comparison = lhs.pid < rhs.pid ? .orderedAscending : (lhs.pid > rhs.pid ? .orderedDescending : .orderedSame)
case .cpu:
comparison = lhs.cpuUsage < rhs.cpuUsage ? .orderedAscending : (lhs.cpuUsage > rhs.cpuUsage ? .orderedDescending : .orderedSame)
case .cpuTotal:
comparison = lhs.cpuUsageTotal < rhs.cpuUsageTotal ? .orderedAscending : (lhs.cpuUsageTotal > rhs.cpuUsageTotal ? .orderedDescending : .orderedSame)
case .memory:
comparison = lhs.memoryUsage < rhs.memoryUsage ? .orderedAscending : (lhs.memoryUsage > rhs.memoryUsage ? .orderedDescending : .orderedSame)
case .threads:
@@ -100,13 +102,20 @@ struct ProcessView: View {
}
.width(60)
TableColumn("CPU %", value: \.cpuUsage) { process in
TableColumn("CPU/Core", value: \.cpuUsage) { process in
Text(String(format: "%.1f%%", process.cpuUsage))
.monospacedDigit()
.foregroundColor(cpuColor(process.cpuUsage))
}
.width(70)
TableColumn("CPU/Total", value: \.cpuUsageTotal) { process in
Text(String(format: "%.2f%%", process.cpuUsageTotal))
.monospacedDigit()
.foregroundColor(cpuColorTotal(process.cpuUsageTotal))
}
.width(70)
TableColumn("Memory", value: \.memoryUsage) { process in
Text(formatBytes(process.memoryUsage))
.monospacedDigit()
@@ -186,7 +195,9 @@ struct ProcessView: View {
// Use string representation of keypath to determine column
let keyPathString = String(describing: comparator)
if keyPathString.contains("cpuUsage") {
if keyPathString.contains("cpuUsageTotal") {
sortColumn = .cpuTotal
} else if keyPathString.contains("cpuUsage") {
sortColumn = .cpu
} else if keyPathString.contains("memoryUsage") {
sortColumn = .memory
@@ -274,6 +285,17 @@ struct ProcessView: View {
return .primary
}
private func cpuColorTotal(_ usage: Double) -> Color {
if usage > 10 {
return .red
} else if usage > 5 {
return .orange
} else if usage > 2 {
return .yellow
}
return .primary
}
private func stateColor(_ state: ProcessState) -> Color {
switch state {
case .running: return .green