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 pid: pid_t
|
||||||
let name: String
|
let name: String
|
||||||
let user: 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 memoryUsage: Int64
|
||||||
let threadCount: Int32
|
let threadCount: Int32
|
||||||
let state: ProcessState
|
let state: ProcessState
|
||||||
@@ -21,6 +22,7 @@ struct ProcessItem: Identifiable, Hashable {
|
|||||||
name: String,
|
name: String,
|
||||||
user: String,
|
user: String,
|
||||||
cpuUsage: Double,
|
cpuUsage: Double,
|
||||||
|
cpuUsageTotal: Double,
|
||||||
memoryUsage: Int64,
|
memoryUsage: Int64,
|
||||||
threadCount: Int32,
|
threadCount: Int32,
|
||||||
state: ProcessState,
|
state: ProcessState,
|
||||||
@@ -33,6 +35,7 @@ struct ProcessItem: Identifiable, Hashable {
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.user = user
|
self.user = user
|
||||||
self.cpuUsage = cpuUsage
|
self.cpuUsage = cpuUsage
|
||||||
|
self.cpuUsageTotal = cpuUsageTotal
|
||||||
self.memoryUsage = memoryUsage
|
self.memoryUsage = memoryUsage
|
||||||
self.threadCount = threadCount
|
self.threadCount = threadCount
|
||||||
self.state = state
|
self.state = state
|
||||||
@@ -48,6 +51,7 @@ struct ProcessItem: Identifiable, Hashable {
|
|||||||
static func == (lhs: ProcessItem, rhs: ProcessItem) -> Bool {
|
static func == (lhs: ProcessItem, rhs: ProcessItem) -> Bool {
|
||||||
lhs.pid == rhs.pid &&
|
lhs.pid == rhs.pid &&
|
||||||
lhs.cpuUsage == rhs.cpuUsage &&
|
lhs.cpuUsage == rhs.cpuUsage &&
|
||||||
|
lhs.cpuUsageTotal == rhs.cpuUsageTotal &&
|
||||||
lhs.memoryUsage == rhs.memoryUsage &&
|
lhs.memoryUsage == rhs.memoryUsage &&
|
||||||
lhs.threadCount == rhs.threadCount &&
|
lhs.threadCount == rhs.threadCount &&
|
||||||
lhs.state == rhs.state
|
lhs.state == rhs.state
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ final class ProcessMonitor {
|
|||||||
private var userCache: [uid_t: String] = [:]
|
private var userCache: [uid_t: String] = [:]
|
||||||
private let timebaseInfo: mach_timebase_info_data_t
|
private let timebaseInfo: mach_timebase_info_data_t
|
||||||
private var refreshCounter = 0
|
private var refreshCounter = 0
|
||||||
|
private let processorCount = Double(ProcessInfo.processInfo.processorCount)
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
var info = mach_timebase_info_data_t()
|
var info = mach_timebase_info_data_t()
|
||||||
@@ -111,11 +112,15 @@ final class ProcessMonitor {
|
|||||||
|
|
||||||
let icon = fetchIcon(pid: pid)
|
let icon = fetchIcon(pid: pid)
|
||||||
|
|
||||||
|
// Calculate normalized CPU (100% = all cores)
|
||||||
|
let cpuUsageTotal = cpuUsage / processorCount
|
||||||
|
|
||||||
return ProcessItem(
|
return ProcessItem(
|
||||||
pid: pid,
|
pid: pid,
|
||||||
name: name,
|
name: name,
|
||||||
user: user,
|
user: user,
|
||||||
cpuUsage: cpuUsage,
|
cpuUsage: cpuUsage,
|
||||||
|
cpuUsageTotal: cpuUsageTotal,
|
||||||
memoryUsage: memoryUsage,
|
memoryUsage: memoryUsage,
|
||||||
threadCount: threadCount,
|
threadCount: threadCount,
|
||||||
state: state,
|
state: state,
|
||||||
@@ -240,6 +245,7 @@ final class ProcessMonitor {
|
|||||||
name: name,
|
name: name,
|
||||||
user: user,
|
user: user,
|
||||||
cpuUsage: 0,
|
cpuUsage: 0,
|
||||||
|
cpuUsageTotal: 0,
|
||||||
memoryUsage: 0,
|
memoryUsage: 0,
|
||||||
threadCount: 0,
|
threadCount: 0,
|
||||||
state: state,
|
state: state,
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ struct DetailRow: View {
|
|||||||
name: "Safari",
|
name: "Safari",
|
||||||
user: "ariel",
|
user: "ariel",
|
||||||
cpuUsage: 12.5,
|
cpuUsage: 12.5,
|
||||||
|
cpuUsageTotal: 1.56,
|
||||||
memoryUsage: 512 * 1024 * 1024,
|
memoryUsage: 512 * 1024 * 1024,
|
||||||
threadCount: 42,
|
threadCount: 42,
|
||||||
state: .running,
|
state: .running,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum ProcessSortColumn: String {
|
enum ProcessSortColumn: String {
|
||||||
case name, pid, cpu, memory, threads, user, state
|
case name, pid, cpu, cpuTotal, memory, threads, user, state
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProcessView: View {
|
struct ProcessView: View {
|
||||||
@@ -42,6 +42,8 @@ struct ProcessView: View {
|
|||||||
comparison = lhs.pid < rhs.pid ? .orderedAscending : (lhs.pid > rhs.pid ? .orderedDescending : .orderedSame)
|
comparison = lhs.pid < rhs.pid ? .orderedAscending : (lhs.pid > rhs.pid ? .orderedDescending : .orderedSame)
|
||||||
case .cpu:
|
case .cpu:
|
||||||
comparison = lhs.cpuUsage < rhs.cpuUsage ? .orderedAscending : (lhs.cpuUsage > rhs.cpuUsage ? .orderedDescending : .orderedSame)
|
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:
|
case .memory:
|
||||||
comparison = lhs.memoryUsage < rhs.memoryUsage ? .orderedAscending : (lhs.memoryUsage > rhs.memoryUsage ? .orderedDescending : .orderedSame)
|
comparison = lhs.memoryUsage < rhs.memoryUsage ? .orderedAscending : (lhs.memoryUsage > rhs.memoryUsage ? .orderedDescending : .orderedSame)
|
||||||
case .threads:
|
case .threads:
|
||||||
@@ -100,13 +102,20 @@ struct ProcessView: View {
|
|||||||
}
|
}
|
||||||
.width(60)
|
.width(60)
|
||||||
|
|
||||||
TableColumn("CPU %", value: \.cpuUsage) { process in
|
TableColumn("CPU/Core", value: \.cpuUsage) { process in
|
||||||
Text(String(format: "%.1f%%", process.cpuUsage))
|
Text(String(format: "%.1f%%", process.cpuUsage))
|
||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
.foregroundColor(cpuColor(process.cpuUsage))
|
.foregroundColor(cpuColor(process.cpuUsage))
|
||||||
}
|
}
|
||||||
.width(70)
|
.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
|
TableColumn("Memory", value: \.memoryUsage) { process in
|
||||||
Text(formatBytes(process.memoryUsage))
|
Text(formatBytes(process.memoryUsage))
|
||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
@@ -186,7 +195,9 @@ struct ProcessView: View {
|
|||||||
// Use string representation of keypath to determine column
|
// Use string representation of keypath to determine column
|
||||||
let keyPathString = String(describing: comparator)
|
let keyPathString = String(describing: comparator)
|
||||||
|
|
||||||
if keyPathString.contains("cpuUsage") {
|
if keyPathString.contains("cpuUsageTotal") {
|
||||||
|
sortColumn = .cpuTotal
|
||||||
|
} else if keyPathString.contains("cpuUsage") {
|
||||||
sortColumn = .cpu
|
sortColumn = .cpu
|
||||||
} else if keyPathString.contains("memoryUsage") {
|
} else if keyPathString.contains("memoryUsage") {
|
||||||
sortColumn = .memory
|
sortColumn = .memory
|
||||||
@@ -274,6 +285,17 @@ struct ProcessView: View {
|
|||||||
return .primary
|
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 {
|
private func stateColor(_ state: ProcessState) -> Color {
|
||||||
switch state {
|
switch state {
|
||||||
case .running: return .green
|
case .running: return .green
|
||||||
|
|||||||
Reference in New Issue
Block a user