fix: 修复内存统计,改用 total - cached - free 公式,与 Activity Monitor 完全一致
This commit is contained in:
parent
dfc6e93d5a
commit
da712dc834
@ -28,6 +28,15 @@ struct SystemInfo {
|
||||
var topCPUProcesses: [ProcessInfo]
|
||||
var topMemoryProcesses: [ProcessInfo]
|
||||
|
||||
// 网络速度
|
||||
var networkDownloadBytesPerSec: UInt64
|
||||
var networkUploadBytesPerSec: UInt64
|
||||
var networkInterface: String
|
||||
|
||||
// 磁盘 I/O
|
||||
var diskReadBytesPerSec: UInt64
|
||||
var diskWriteBytesPerSec: UInt64
|
||||
|
||||
struct ProcessInfo: Identifiable {
|
||||
let id = UUID()
|
||||
let pid: Int32
|
||||
@ -48,12 +57,20 @@ final class SystemMonitor: ObservableObject {
|
||||
swapTotal: 0, swapUsed: 0, swapUsagePercent: 0,
|
||||
swapIns: 0, swapOuts: 0,
|
||||
cpuTemperature: nil, gpuTemperature: nil,
|
||||
topCPUProcesses: [], topMemoryProcesses: []
|
||||
topCPUProcesses: [], topMemoryProcesses: [],
|
||||
networkDownloadBytesPerSec: 0, networkUploadBytesPerSec: 0, networkInterface: "",
|
||||
diskReadBytesPerSec: 0, diskWriteBytesPerSec: 0
|
||||
)
|
||||
|
||||
private var timer: Timer?
|
||||
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 {
|
||||
didSet { restartTimer() }
|
||||
@ -96,6 +113,8 @@ final class SystemMonitor: ObservableObject {
|
||||
let swap = fetchSwap()
|
||||
let temp = fetchTemperature()
|
||||
let procs = fetchTopProcesses(count: topN)
|
||||
let net = fetchNetwork()
|
||||
let disk = fetchDiskIO()
|
||||
|
||||
info = SystemInfo(
|
||||
cpuUsage: cpu.usage,
|
||||
@ -114,7 +133,12 @@ final class SystemMonitor: ObservableObject {
|
||||
cpuTemperature: temp.cpu,
|
||||
gpuTemperature: temp.gpu,
|
||||
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) }
|
||||
|
||||
let activePages = UInt64(vmStat.active_count)
|
||||
let wiredPages = UInt64(vmStat.wire_count)
|
||||
let compressedPages = UInt64(vmStat.compressor_page_count)
|
||||
|
||||
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
|
||||
|
||||
return (total, usedRaw, min(percent, 100))
|
||||
@ -347,4 +369,104 @@ final class SystemMonitor: ObservableObject {
|
||||
|
||||
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)
|
||||
// en0(Wi-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)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user