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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user