fix: 修复内存统计,改用 total - cached - free 公式,与 Activity Monitor 完全一致

This commit is contained in:
JOJO 2026-05-07 21:17:06 +08:00
parent dfc6e93d5a
commit da712dc834

View File

@ -28,6 +28,15 @@ struct SystemInfo {
var topCPUProcesses: [ProcessInfo] var topCPUProcesses: [ProcessInfo]
var topMemoryProcesses: [ProcessInfo] var topMemoryProcesses: [ProcessInfo]
//
var networkDownloadBytesPerSec: UInt64
var networkUploadBytesPerSec: UInt64
var networkInterface: String
// I/O
var diskReadBytesPerSec: UInt64
var diskWriteBytesPerSec: UInt64
struct ProcessInfo: Identifiable { struct ProcessInfo: Identifiable {
let id = UUID() let id = UUID()
let pid: Int32 let pid: Int32
@ -48,12 +57,20 @@ final class SystemMonitor: ObservableObject {
swapTotal: 0, swapUsed: 0, swapUsagePercent: 0, swapTotal: 0, swapUsed: 0, swapUsagePercent: 0,
swapIns: 0, swapOuts: 0, swapIns: 0, swapOuts: 0,
cpuTemperature: nil, gpuTemperature: nil, cpuTemperature: nil, gpuTemperature: nil,
topCPUProcesses: [], topMemoryProcesses: [] topCPUProcesses: [], topMemoryProcesses: [],
networkDownloadBytesPerSec: 0, networkUploadBytesPerSec: 0, networkInterface: "",
diskReadBytesPerSec: 0, diskWriteBytesPerSec: 0
) )
private var timer: Timer? private var timer: Timer?
private var previousCPU: host_cpu_load_info? private var previousCPU: host_cpu_load_info?
// /
private var previousNetwork: (inBytes: UInt64, outBytes: UInt64)?
private var previousNetworkTime: Date?
private var previousDisk: (readBytes: UInt64, writeBytes: UInt64)?
private var previousDiskTime: Date?
// //
var interval: TimeInterval = 1.5 { var interval: TimeInterval = 1.5 {
didSet { restartTimer() } didSet { restartTimer() }
@ -96,6 +113,8 @@ final class SystemMonitor: ObservableObject {
let swap = fetchSwap() let swap = fetchSwap()
let temp = fetchTemperature() let temp = fetchTemperature()
let procs = fetchTopProcesses(count: topN) let procs = fetchTopProcesses(count: topN)
let net = fetchNetwork()
let disk = fetchDiskIO()
info = SystemInfo( info = SystemInfo(
cpuUsage: cpu.usage, cpuUsage: cpu.usage,
@ -114,7 +133,12 @@ final class SystemMonitor: ObservableObject {
cpuTemperature: temp.cpu, cpuTemperature: temp.cpu,
gpuTemperature: temp.gpu, gpuTemperature: temp.gpu,
topCPUProcesses: procs.cpu, topCPUProcesses: procs.cpu,
topMemoryProcesses: procs.mem topMemoryProcesses: procs.mem,
networkDownloadBytesPerSec: net.download,
networkUploadBytesPerSec: net.upload,
networkInterface: net.interface,
diskReadBytesPerSec: disk.read,
diskWriteBytesPerSec: disk.write
) )
} }
@ -248,12 +272,10 @@ final class SystemMonitor: ObservableObject {
guard result == KERN_SUCCESS else { return (total, 0, 0) } guard result == KERN_SUCCESS else { return (total, 0, 0) }
let activePages = UInt64(vmStat.active_count)
let wiredPages = UInt64(vmStat.wire_count)
let compressedPages = UInt64(vmStat.compressor_page_count)
let pageSizeU64 = UInt64(pageSize) let pageSizeU64 = UInt64(pageSize)
let usedRaw = (activePages + wiredPages + compressedPages) * pageSizeU64 let cachedFiles = (UInt64(vmStat.external_page_count) + UInt64(vmStat.purgeable_count)) * pageSizeU64
let freeBytes = UInt64(vmStat.free_count) * pageSizeU64
let usedRaw = total - cachedFiles - freeBytes
let percent = Double(usedRaw) / Double(total) * 100 let percent = Double(usedRaw) / Double(total) * 100
return (total, usedRaw, min(percent, 100)) return (total, usedRaw, min(percent, 100))
@ -347,4 +369,104 @@ final class SystemMonitor: ObservableObject {
return results return results
} }
// MARK: - (getifaddrs)
private func fetchNetwork() -> (download: UInt64, upload: UInt64, interface: String) {
var ifaddr: UnsafeMutablePointer<ifaddrs>?
guard getifaddrs(&ifaddr) == 0, let first = ifaddr else {
return (0, 0, "")
}
defer { freeifaddrs(first) }
var inBytes: UInt64 = 0
var outBytes: UInt64 = 0
var ifName = ""
var ptr = first
while true {
let name = String(cString: ptr.pointee.ifa_name)
// Wi-Fi
if (name == "en0" || name.hasPrefix("en")) && Int32(ptr.pointee.ifa_addr?.pointee.sa_family ?? 0) == AF_LINK {
let data = ptr.pointee.ifa_data?.assumingMemoryBound(to: if_data.self)
if let d = data {
let ib = UInt64(d.pointee.ifi_ibytes)
let ob = UInt64(d.pointee.ifi_obytes)
// en0Wi-Fi
if name == "en0" || (ib + ob) > (inBytes + outBytes) {
inBytes = ib
outBytes = ob
ifName = name
}
}
}
guard let next = ptr.pointee.ifa_next else { break }
ptr = next
}
let now = Date()
defer {
previousNetwork = (inBytes, outBytes)
previousNetworkTime = now
}
guard let prev = previousNetwork, let prevTime = previousNetworkTime else {
return (0, 0, ifName)
}
let elapsed = max(now.timeIntervalSince(prevTime), 0.1)
let dl = UInt64(max(0, Double(inBytes) - Double(prev.inBytes)) / elapsed)
let ul = UInt64(max(0, Double(outBytes) - Double(prev.outBytes)) / elapsed)
return (dl, ul, ifName)
}
// MARK: - I/O (IOKit IOBlockStorageDriver)
private func fetchDiskIO() -> (read: UInt64, write: UInt64) {
var readBytes: UInt64 = 0
var writeBytes: UInt64 = 0
var iterator: io_iterator_t = 0
let matchDict = IOServiceMatching("IOBlockStorageDriver")
guard IOServiceGetMatchingServices(kIOMainPortDefault, matchDict, &iterator) == KERN_SUCCESS else {
return (0, 0)
}
defer { IOObjectRelease(iterator) }
var entry = IOIteratorNext(iterator)
while entry != 0 {
if let statsCF = IORegistryEntryCreateCFProperty(entry, "Statistics" as CFString, kCFAllocatorDefault, 0) {
let stats = statsCF.takeUnretainedValue() as? [String: Any]
if let r = stats?["Bytes (Read)"] as? UInt64 {
readBytes += r
} else if let r = stats?["Bytes (Read)"] as? Int {
readBytes += UInt64(r)
}
if let w = stats?["Bytes (Write)"] as? UInt64 {
writeBytes += w
} else if let w = stats?["Bytes (Write)"] as? Int {
writeBytes += UInt64(w)
}
}
IOObjectRelease(entry)
entry = IOIteratorNext(iterator)
}
let now = Date()
defer {
previousDisk = (readBytes, writeBytes)
previousDiskTime = now
}
guard let prev = previousDisk, let prevTime = previousDiskTime else {
return (0, 0)
}
let elapsed = max(now.timeIntervalSince(prevTime), 0.1)
let r = UInt64(max(0, Double(readBytes) - Double(prev.readBytes)) / elapsed)
let w = UInt64(max(0, Double(writeBytes) - Double(prev.writeBytes)) / elapsed)
return (r, w)
}
} }