Initial MacTaskManager release

This commit is contained in:
2026-08-14 13:07:44 -04:00
commit 9795096656
47 changed files with 5173 additions and 0 deletions
@@ -0,0 +1,794 @@
import Charts
import SwiftUI
struct PerformanceView: View {
@Environment(SystemMonitor.self) private var monitor
@AppStorage("performanceSelectedMetric") private var selectedMetric: PerformanceMetric = .cpu
@AppStorage("performanceCPUDisplayMode") private var cpuDisplayMode: CPUDisplayMode = .summary
@State private var selectedVolumeID: String?
private enum PerformanceMetric: String, CaseIterable, Identifiable {
case cpu = "CPU"
case memory = "Memory"
case disk = "Disk"
case gpu = "GPU"
case network = "Network"
var id: String { rawValue }
var icon: String {
switch self {
case .cpu: "cpu"
case .memory: "memorychip"
case .disk: "internaldrive"
case .gpu: "rectangle.3.group"
case .network: "network"
}
}
var color: Color {
switch self {
case .cpu: .blue
case .memory: .purple
case .disk: .green
case .gpu: .pink
case .network: .teal
}
}
}
var body: some View {
VStack(spacing: 0) {
PageHeader(title: "Performance") {
UpdateStatus()
}
HStack(spacing: 0) {
ScrollView {
VStack(spacing: 10) {
metricCard(.cpu, value: monitor.snapshot.cpuPercent, detail: "\(ProcessInfo.processInfo.activeProcessorCount) logical processors")
metricCard(.memory, value: monitor.snapshot.memoryPercent, detail: "\(Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.memoryUsed))) of \(Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.memoryTotal)))")
metricCard(
.disk,
value: monitor.snapshot.diskActivePercent,
valueText: Formatters.rate(monitor.snapshot.diskThroughput),
detail: "\(Formatters.percent(monitor.snapshot.diskActivePercent)) active • System volume"
)
ForEach(monitor.snapshot.removableVolumes) { volume in
volumeCard(volume)
}
metricCard(
.gpu,
value: monitor.snapshot.gpuPercent,
detail: "\(monitor.snapshot.gpuCoreCount) cores • \(Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.gpuMemoryBytes))) in use"
)
metricCard(
.network,
value: 0,
valueText: Formatters.rate(monitor.snapshot.networkReceiveRate + monitor.snapshot.networkSendRate),
detail: "\(Formatters.rate(monitor.snapshot.networkReceiveRate))\(Formatters.rate(monitor.snapshot.networkSendRate))"
)
}
.padding(14)
}
.frame(width: 245)
.background(Color(nsColor: .controlBackgroundColor).opacity(0.45))
Divider()
detail
}
}
}
private func metricCard(_ metric: PerformanceMetric, value: Double, valueText: String? = nil, detail: String) -> some View {
Button {
selectedVolumeID = nil
selectedMetric = metric
} label: {
HStack(spacing: 12) {
Image(systemName: metric.icon)
.font(.title3)
.foregroundStyle(metric.color)
.frame(width: 30)
VStack(alignment: .leading, spacing: 3) {
Text(metric.rawValue).font(.headline)
Text(valueText ?? Formatters.percent(value)).font(.title3.monospacedDigit())
Text(detail).font(.caption).foregroundStyle(.secondary).lineLimit(1)
}
Spacer()
}
.padding(12)
.frame(maxWidth: .infinity, alignment: .leading)
.background(selectedVolumeID == nil && selectedMetric == metric ? metric.color.opacity(0.13) : Color(nsColor: .windowBackgroundColor))
.overlay {
RoundedRectangle(cornerRadius: 9)
.stroke(
selectedVolumeID == nil && selectedMetric == metric ? metric.color : Color.secondary.opacity(0.15),
lineWidth: selectedVolumeID == nil && selectedMetric == metric ? 1.5 : 1
)
}
.clipShape(RoundedRectangle(cornerRadius: 9))
}
.buttonStyle(.plain)
}
private func volumeCard(_ volume: VolumeSnapshot) -> some View {
Button { selectedVolumeID = volume.id } label: {
HStack(spacing: 12) {
Image(systemName: volume.isEjectable ? "externaldrive.badge.checkmark" : "externaldrive")
.font(.title3)
.foregroundStyle(.orange)
.frame(width: 30)
VStack(alignment: .leading, spacing: 3) {
Text(volume.name).font(.headline).lineLimit(1)
Text(Formatters.percent(volume.usedPercent)).font(.title3.monospacedDigit())
Text("\(Formatters.bytes.string(fromByteCount: Int64(volume.capacity)))\(volume.fileSystem)")
.font(.caption).foregroundStyle(.secondary).lineLimit(1)
}
Spacer()
}
.padding(12)
.frame(maxWidth: .infinity, alignment: .leading)
.background(selectedVolumeID == volume.id ? Color.orange.opacity(0.13) : Color(nsColor: .windowBackgroundColor))
.overlay {
RoundedRectangle(cornerRadius: 9)
.stroke(selectedVolumeID == volume.id ? Color.orange : Color.secondary.opacity(0.15), lineWidth: selectedVolumeID == volume.id ? 1.5 : 1)
}
.clipShape(RoundedRectangle(cornerRadius: 9))
}
.buttonStyle(.plain)
.accessibilityLabel("\(volume.name), removable disk")
.accessibilityValue("\(Formatters.percent(volume.usedPercent)) used")
}
@ViewBuilder
private var detail: some View {
if let selectedVolumeID,
let volume = monitor.snapshot.removableVolumes.first(where: { $0.id == selectedVolumeID }) {
RemovableDiskDetail(volume: volume)
} else {
switch selectedMetric {
case .cpu:
CPUPerformanceDetail(
subtitle: processorName,
value: monitor.snapshot.cpuPercent,
history: monitor.cpuHistory,
coreHistories: monitor.coreHistories,
coreValues: monitor.snapshot.corePercents,
displayMode: $cpuDisplayMode,
stats: [
("Processes", "\(monitor.snapshot.processCount)"),
("Threads", "\(monitor.snapshot.threadCount)"),
("Uptime", Formatters.uptime(monitor.snapshot.uptime)),
("Logical processors", "\(ProcessInfo.processInfo.activeProcessorCount)")
]
)
case .memory:
PerformanceDetail(
title: "Memory",
subtitle: Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.memoryTotal)),
color: .purple,
value: monitor.snapshot.memoryPercent,
history: monitor.memoryHistory,
stats: [
("In use", Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.memoryUsed))),
("Available", Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.memoryTotal - monitor.snapshot.memoryUsed))),
("Active", Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.memoryActive))),
("Cached", Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.memoryCached))),
("Wired", Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.memoryWired))),
("Compressed", Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.memoryCompressed))),
("Swap", "\(Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.swapUsed))) / \(Formatters.bytes.string(fromByteCount: Int64(monitor.snapshot.swapTotal)))"),
("Type", monitor.snapshot.memoryType),
("Frequency / speed", monitor.snapshot.memorySpeed),
("Manufacturer", monitor.snapshot.memoryManufacturer)
]
)
case .disk:
DiskPerformanceDetail(
snapshot: monitor.snapshot,
readHistory: monitor.diskReadHistory,
writeHistory: monitor.diskWriteHistory,
latencyHistory: monitor.diskLatencyHistory,
activeHistory: monitor.diskActiveHistory
)
case .gpu:
GPUPerformanceDetail(
name: "\(processorName) GPU",
snapshot: monitor.snapshot,
history: monitor.gpuHistory,
rendererHistory: monitor.gpuRendererHistory,
tilerHistory: monitor.gpuTilerHistory
)
case .network:
NetworkPerformanceDetail(
interface: monitor.snapshot.networkInterface,
receiveRate: monitor.snapshot.networkReceiveRate,
sendRate: monitor.snapshot.networkSendRate,
receiveHistory: monitor.networkReceiveHistory,
sendHistory: monitor.networkSendHistory
)
}
}
}
private var processorName: String {
var size = 0
guard sysctlbyname("machdep.cpu.brand_string", nil, &size, nil, 0) == 0, size > 1 else {
return "Apple Silicon"
}
var buffer = [CChar](repeating: 0, count: size)
guard sysctlbyname("machdep.cpu.brand_string", &buffer, &size, nil, 0) == 0 else {
return "Apple Silicon"
}
return String(decoding: buffer.prefix { $0 != 0 }.map(UInt8.init(bitPattern:)), as: UTF8.self)
}
}
private struct RemovableDiskDetail: View {
let volume: VolumeSnapshot
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 18) {
VStack(alignment: .leading, spacing: 4) {
Text(volume.name).font(.system(size: 30, weight: .semibold))
Text("\(volume.fileSystem)\(volume.mountPath)")
.foregroundStyle(.secondary)
}
HStack {
Label(volume.isEjectable ? "Ejectable" : "Removable", systemImage: "externaldrive")
.foregroundStyle(.orange)
Spacer()
Text(Formatters.percent(volume.usedPercent))
.font(.system(size: 34, weight: .light).monospacedDigit())
}
ProgressView(value: volume.usedPercent, total: 100)
.tint(.orange)
StatsGrid(stats: [
("Used", Formatters.bytes.string(fromByteCount: Int64(volume.used))),
("Available", Formatters.bytes.string(fromByteCount: Int64(volume.available))),
("Capacity", Formatters.bytes.string(fromByteCount: Int64(volume.capacity))),
("Ejectable", volume.isEjectable ? "Yes" : "No")
])
}
.padding(22)
}
}
}
private struct GPUPerformanceDetail: View {
let name: String
let snapshot: SystemSnapshot
let history: [MetricSample]
let rendererHistory: [MetricSample]
let tilerHistory: [MetricSample]
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 18) {
HStack(alignment: .firstTextBaseline) {
VStack(alignment: .leading, spacing: 4) {
Text("GPU").font(.system(size: 30, weight: .semibold))
Text(name).foregroundStyle(.secondary)
}
Spacer()
Text(Formatters.percent(snapshot.gpuPercent))
.font(.system(size: 34, weight: .light).monospacedDigit())
}
VStack(alignment: .leading, spacing: 8) {
Text("Overall utilization").font(.caption).foregroundStyle(.secondary)
Chart(history) { sample in
AreaMark(x: .value("Time", sample.date), y: .value("Utilization", sample.value))
.foregroundStyle(Color.pink.opacity(0.12))
LineMark(x: .value("Time", sample.date), y: .value("Utilization", sample.value))
.foregroundStyle(.pink).lineStyle(.init(lineWidth: 2))
}
.chartYScale(domain: 0...100)
.chartXAxis(.hidden)
.chartYAxis {
AxisMarks(position: .trailing, values: [0, 25, 50, 75, 100]) { value in
AxisGridLine().foregroundStyle(Color.pink.opacity(0.16))
AxisValueLabel { if let number = value.as(Int.self) { Text("\(number)%") } }
}
}
.frame(height: 240)
Text("60 seconds").font(.caption).foregroundStyle(.secondary)
}
.padding(16)
.background(Color(nsColor: .controlBackgroundColor).opacity(0.42))
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay { RoundedRectangle(cornerRadius: 12).stroke(Color.pink.opacity(0.16)) }
HStack(spacing: 14) {
engineChart(title: "Renderer", value: snapshot.gpuRendererPercent, history: rendererHistory, color: .pink)
engineChart(title: "Tiler", value: snapshot.gpuTilerPercent, history: tilerHistory, color: .purple)
}
StatsGrid(stats: [
("Utilization", Formatters.percent(snapshot.gpuPercent)),
("Renderer", Formatters.percent(snapshot.gpuRendererPercent)),
("Tiler", Formatters.percent(snapshot.gpuTilerPercent)),
("GPU cores", "\(snapshot.gpuCoreCount)"),
("Memory in use", Formatters.bytes.string(fromByteCount: Int64(snapshot.gpuMemoryBytes))),
("Allocated", Formatters.bytes.string(fromByteCount: Int64(snapshot.gpuAllocatedBytes)))
])
}
.padding(22)
}
}
private func engineChart(title: String, value: Double, history: [MetricSample], color: Color) -> some View {
VStack(alignment: .leading, spacing: 6) {
HStack {
Text(title).font(.caption).foregroundStyle(.secondary)
Spacer()
Text(Formatters.percent(value)).font(.callout.monospacedDigit())
}
Chart(history) { sample in
AreaMark(x: .value("Time", sample.date), y: .value(title, sample.value))
.foregroundStyle(color.opacity(0.1))
LineMark(x: .value("Time", sample.date), y: .value(title, sample.value))
.foregroundStyle(color)
}
.chartYScale(domain: 0...100)
.chartXAxis(.hidden)
.chartYAxis(.hidden)
.frame(height: 92)
}
.padding(14)
.frame(maxWidth: .infinity)
.background(Color(nsColor: .controlBackgroundColor).opacity(0.42))
.clipShape(RoundedRectangle(cornerRadius: 10))
.overlay { RoundedRectangle(cornerRadius: 10).stroke(color.opacity(0.16)) }
.accessibilityElement(children: .combine)
.accessibilityLabel("\(title) GPU utilization")
.accessibilityValue(Formatters.percent(value))
}
}
private struct DiskPerformanceDetail: View {
let snapshot: SystemSnapshot
let readHistory: [MetricSample]
let writeHistory: [MetricSample]
let latencyHistory: [MetricSample]
let activeHistory: [MetricSample]
private var maximumRate: Double {
let maximum = (readHistory.map(\.value) + writeHistory.map(\.value) + [snapshot.diskReadRate, snapshot.diskWriteRate]).max() ?? 0
return max(1024, maximum * 1.15)
}
private var maximumLatency: Double {
max(0.1, ((latencyHistory.map(\.value) + [snapshot.diskLatencyMilliseconds]).max() ?? 0) * 1.15)
}
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 18) {
HStack(alignment: .firstTextBaseline) {
VStack(alignment: .leading, spacing: 4) {
Text("Disk 0").font(.system(size: 30, weight: .semibold))
Text("System volume • APFS • Solid-state").foregroundStyle(.secondary)
}
Spacer()
VStack(alignment: .trailing, spacing: 2) {
Text(Formatters.rate(snapshot.diskThroughput))
.font(.system(size: 30, weight: .light).monospacedDigit())
Text("\(Formatters.percent(snapshot.diskActivePercent)) active")
.font(.caption).foregroundStyle(.secondary)
}
}
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Disk transfer rate").font(.caption).foregroundStyle(.secondary)
Spacer()
Label("Read", systemImage: "arrow.down").foregroundStyle(.green)
Label("Write", systemImage: "arrow.up").foregroundStyle(.orange)
}
.font(.caption)
Chart {
ForEach(readHistory) { sample in
AreaMark(x: .value("Time", sample.date), y: .value("Read", sample.value))
.foregroundStyle(Color.green.opacity(0.1))
LineMark(x: .value("Time", sample.date), y: .value("Read", sample.value))
.foregroundStyle(by: .value("Direction", "Read"))
}
ForEach(writeHistory) { sample in
LineMark(x: .value("Time", sample.date), y: .value("Write", sample.value))
.foregroundStyle(by: .value("Direction", "Write"))
}
}
.chartForegroundStyleScale(["Read": Color.green, "Write": Color.orange])
.chartYScale(domain: 0...maximumRate)
.chartXAxis(.hidden)
.chartYAxis {
AxisMarks(position: .trailing) { value in
AxisGridLine().foregroundStyle(Color.green.opacity(0.14))
AxisValueLabel {
if let rate = value.as(Double.self) { Text(Formatters.rate(rate)) }
}
}
}
.chartLegend(.hidden)
.frame(height: 220)
Text("60 seconds").font(.caption).foregroundStyle(.secondary)
}
.padding(16)
.background(Color(nsColor: .controlBackgroundColor).opacity(0.42))
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay { RoundedRectangle(cornerRadius: 12).stroke(Color.green.opacity(0.16)) }
HStack(spacing: 14) {
miniChart(
title: "Average response time",
value: String(format: "%.2f ms", snapshot.diskLatencyMilliseconds),
history: latencyHistory,
maximum: maximumLatency,
color: .orange
)
miniChart(
title: "Active time",
value: Formatters.percent(snapshot.diskActivePercent),
history: activeHistory,
maximum: 100,
color: .green
)
}
StatsGrid(stats: [
("Read speed", Formatters.rate(snapshot.diskReadRate)),
("Write speed", Formatters.rate(snapshot.diskWriteRate)),
("Response time", String(format: "%.2f ms", snapshot.diskLatencyMilliseconds)),
("IOPS", String(format: "%.0f", snapshot.diskOperationsPerSecond)),
("Used space", Formatters.bytes.string(fromByteCount: Int64(snapshot.diskTotal - snapshot.diskFree))),
("Capacity", Formatters.bytes.string(fromByteCount: Int64(snapshot.diskTotal)))
])
}
.padding(22)
}
}
private func miniChart(
title: String,
value: String,
history: [MetricSample],
maximum: Double,
color: Color
) -> some View {
VStack(alignment: .leading, spacing: 6) {
HStack {
Text(title).font(.caption).foregroundStyle(.secondary)
Spacer()
Text(value).font(.callout.monospacedDigit())
}
Chart(history) { sample in
AreaMark(x: .value("Time", sample.date), y: .value(title, sample.value))
.foregroundStyle(color.opacity(0.1))
LineMark(x: .value("Time", sample.date), y: .value(title, sample.value))
.foregroundStyle(color)
}
.chartYScale(domain: 0...maximum)
.chartXAxis(.hidden)
.chartYAxis(.hidden)
.frame(height: 92)
.accessibilityLabel(title)
.accessibilityValue(value)
}
.padding(14)
.frame(maxWidth: .infinity)
.background(Color(nsColor: .controlBackgroundColor).opacity(0.42))
.clipShape(RoundedRectangle(cornerRadius: 10))
.overlay { RoundedRectangle(cornerRadius: 10).stroke(color.opacity(0.16)) }
}
}
private struct NetworkPerformanceDetail: View {
let interface: String
let receiveRate: Double
let sendRate: Double
let receiveHistory: [MetricSample]
let sendHistory: [MetricSample]
private var maximumRate: Double {
let maximum = (receiveHistory.map(\.value) + sendHistory.map(\.value) + [receiveRate, sendRate]).max() ?? 0
return max(1024, maximum * 1.15)
}
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
HStack(alignment: .firstTextBaseline) {
VStack(alignment: .leading, spacing: 4) {
Text("Network").font(.system(size: 30, weight: .semibold))
Text(interface).foregroundStyle(.secondary)
}
Spacer()
Text(Formatters.rate(receiveRate + sendRate))
.font(.system(size: 30, weight: .light).monospacedDigit())
}
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Throughput").font(.caption).foregroundStyle(.secondary)
Spacer()
Label("Receive", systemImage: "arrow.down").foregroundStyle(.teal)
Label("Send", systemImage: "arrow.up").foregroundStyle(.purple)
}
.font(.caption)
Chart {
ForEach(receiveHistory) { sample in
AreaMark(x: .value("Time", sample.date), y: .value("Receive", sample.value))
.foregroundStyle(Color.teal.opacity(0.1))
LineMark(x: .value("Time", sample.date), y: .value("Receive", sample.value))
.foregroundStyle(by: .value("Direction", "Receive"))
}
ForEach(sendHistory) { sample in
LineMark(x: .value("Time", sample.date), y: .value("Send", sample.value))
.foregroundStyle(by: .value("Direction", "Send"))
}
}
.chartForegroundStyleScale(["Receive": Color.teal, "Send": Color.purple])
.chartYScale(domain: 0...maximumRate)
.chartXAxis(.hidden)
.chartYAxis {
AxisMarks(position: .trailing) { value in
AxisGridLine().foregroundStyle(Color.teal.opacity(0.14))
AxisValueLabel {
if let rate = value.as(Double.self) { Text(Formatters.rate(rate)) }
}
}
}
.chartLegend(.hidden)
.frame(height: 280)
Text("60 seconds").font(.caption).foregroundStyle(.secondary)
}
.padding(16)
.background(Color(nsColor: .controlBackgroundColor).opacity(0.42))
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay { RoundedRectangle(cornerRadius: 12).stroke(Color.teal.opacity(0.16)) }
StatsGrid(stats: [
("Receive", Formatters.rate(receiveRate)),
("Send", Formatters.rate(sendRate)),
("Combined", Formatters.rate(receiveRate + sendRate)),
("Interface", interface)
])
}
.padding(22)
}
}
}
private enum CPUDisplayMode: String {
case summary = "Summary view"
case logicalProcessors = "Logical processors"
}
private struct CPUPerformanceDetail: View {
let subtitle: String
let value: Double
let history: [MetricSample]
let coreHistories: [[MetricSample]]
let coreValues: [Double]
@Binding var displayMode: CPUDisplayMode
let stats: [(String, String)]
private var chartData: [MetricSample] {
if !history.isEmpty { return history }
return [MetricSample(date: .now.addingTimeInterval(-60), value: value), MetricSample(date: .now, value: value)]
}
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
HStack(alignment: .firstTextBaseline) {
VStack(alignment: .leading, spacing: 4) {
Text("CPU").font(.system(size: 30, weight: .semibold))
Text(subtitle).foregroundStyle(.secondary)
}
Spacer()
Text(Formatters.percent(value))
.font(.system(size: 34, weight: .light).monospacedDigit())
}
cpuGraph
.contextMenu {
Button {
displayMode = .summary
} label: {
Label("Summary view", systemImage: displayMode == .summary ? "checkmark" : "chart.xyaxis.line")
}
Button {
displayMode = .logicalProcessors
} label: {
Label("Logical processors", systemImage: displayMode == .logicalProcessors ? "checkmark" : "square.grid.3x3")
}
}
.help("Right-click to switch between summary and logical processor graphs")
StatsGrid(stats: stats)
}
.padding(22)
}
}
private var cpuGraph: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text(displayMode == .summary ? "% Utilization" : "Utilization by logical processor")
.font(.caption)
.foregroundStyle(.secondary)
Spacer()
Label(displayMode.rawValue, systemImage: displayMode == .summary ? "chart.xyaxis.line" : "square.grid.3x3")
.font(.caption)
.foregroundStyle(.secondary)
}
if displayMode == .summary {
Chart(chartData) { sample in
AreaMark(x: .value("Time", sample.date), y: .value("Usage", sample.value))
.foregroundStyle(Color.blue.opacity(0.13))
LineMark(x: .value("Time", sample.date), y: .value("Usage", sample.value))
.foregroundStyle(.blue)
.lineStyle(.init(lineWidth: 2))
}
.chartYScale(domain: 0...100)
.chartXAxis(.hidden)
.chartYAxis {
AxisMarks(position: .trailing, values: [0, 25, 50, 75, 100]) { value in
AxisGridLine().foregroundStyle(Color.blue.opacity(0.18))
AxisValueLabel { if let number = value.as(Int.self) { Text("\(number)%") } }
}
}
.frame(height: 280)
} else {
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 4), spacing: 8) {
ForEach(coreValues.indices, id: \.self) { index in
CoreChart(
index: index,
value: coreValues[index],
history: index < coreHistories.count ? coreHistories[index] : []
)
}
}
.frame(minHeight: 280)
}
Text("60 seconds").font(.caption).foregroundStyle(.secondary)
}
.padding(16)
.background(Color(nsColor: .controlBackgroundColor).opacity(0.42))
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay {
RoundedRectangle(cornerRadius: 12)
.stroke(Color.blue.opacity(0.16), lineWidth: 1)
}
}
}
private struct CoreChart: View {
let index: Int
let value: Double
let history: [MetricSample]
private var data: [MetricSample] {
if !history.isEmpty { return history }
return [MetricSample(date: .now.addingTimeInterval(-60), value: value), MetricSample(date: .now, value: value)]
}
var body: some View {
ZStack(alignment: .topLeading) {
Chart(data) { sample in
AreaMark(x: .value("Time", sample.date), y: .value("Usage", sample.value))
.foregroundStyle(Color.blue.opacity(0.1))
LineMark(x: .value("Time", sample.date), y: .value("Usage", sample.value))
.foregroundStyle(.blue)
.lineStyle(.init(lineWidth: 1.25))
}
.chartYScale(domain: 0...100)
.chartXAxis(.hidden)
.chartYAxis(.hidden)
.background {
GridPattern().stroke(Color.blue.opacity(0.12), lineWidth: 0.5)
}
VStack(alignment: .leading, spacing: 1) {
Text("CPU \(index)").font(.caption2.weight(.medium))
Text(Formatters.percent(value)).font(.caption2.monospacedDigit()).foregroundStyle(.secondary)
}
.padding(6)
}
.frame(height: 62)
.clipShape(RoundedRectangle(cornerRadius: 5))
.overlay { RoundedRectangle(cornerRadius: 5).stroke(Color.blue.opacity(0.28), lineWidth: 1) }
.accessibilityElement(children: .ignore)
.accessibilityLabel("CPU \(index), \(Formatters.percent(value)) utilization")
}
}
private struct GridPattern: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
for fraction in [0.25, 0.5, 0.75] {
let x = rect.width * fraction
let y = rect.height * fraction
path.move(to: CGPoint(x: x, y: 0))
path.addLine(to: CGPoint(x: x, y: rect.height))
path.move(to: CGPoint(x: 0, y: y))
path.addLine(to: CGPoint(x: rect.width, y: y))
}
return path
}
}
private struct StatsGrid: View {
let stats: [(String, String)]
var body: some View {
LazyVGrid(columns: [.init(.flexible()), .init(.flexible())], spacing: 18) {
ForEach(Array(stats.enumerated()), id: \.offset) { _, stat in
VStack(alignment: .leading, spacing: 4) {
Text(stat.0).font(.caption).foregroundStyle(.secondary)
Text(stat.1).font(.title3.monospacedDigit()).lineLimit(1)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}
private struct PerformanceDetail: View {
let title: String
let subtitle: String
let color: Color
let value: Double
let history: [MetricSample]
let stats: [(String, String)]
var chartData: [MetricSample] {
if !history.isEmpty { return history }
return [MetricSample(date: .now.addingTimeInterval(-60), value: value), MetricSample(date: .now, value: value)]
}
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
HStack(alignment: .firstTextBaseline) {
VStack(alignment: .leading, spacing: 4) {
Text(title).font(.system(size: 30, weight: .semibold))
Text(subtitle).foregroundStyle(.secondary)
}
Spacer()
Text(Formatters.percent(value))
.font(.system(size: 34, weight: .light).monospacedDigit())
}
VStack(alignment: .leading, spacing: 8) {
Text("% Utilization").font(.caption).foregroundStyle(.secondary)
Chart(chartData) { sample in
AreaMark(x: .value("Time", sample.date), y: .value("Usage", sample.value))
.foregroundStyle(color.opacity(0.13))
LineMark(x: .value("Time", sample.date), y: .value("Usage", sample.value))
.foregroundStyle(color)
.lineStyle(.init(lineWidth: 2))
}
.chartYScale(domain: 0...100)
.chartXAxis(.hidden)
.chartYAxis {
AxisMarks(position: .trailing, values: [0, 25, 50, 75, 100]) { value in
AxisGridLine().foregroundStyle(color.opacity(0.18))
AxisValueLabel { if let number = value.as(Int.self) { Text("\(number)%") } }
}
}
.frame(minHeight: 280)
Text("60 seconds").font(.caption).foregroundStyle(.secondary)
}
.padding(16)
.background(Color(nsColor: .controlBackgroundColor).opacity(0.42))
.clipShape(RoundedRectangle(cornerRadius: 12))
StatsGrid(stats: stats)
}
.padding(22)
}
}
}