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 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)
|
||||||
|
// 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