// // SystemMonitor.swift // AudioVUMeter // // System resource monitoring for CPU, RAM, Disk, and Network // Uses mach kernel APIs for accurate system statistics // import Foundation import Darwin /// System resource monitor class class SystemMonitor: ObservableObject { // MARK: - Published Properties @Published var cpuUsage: Double = 0 @Published var memoryUsage: Double = 0 @Published var diskActivity: Double = 0 @Published var networkActivity: Double = 0 // Additional details @Published var cpuUserUsage: Double = 0 @Published var cpuSystemUsage: Double = 0 @Published var memoryUsed: UInt64 = 0 @Published var memoryTotal: UInt64 = 0 @Published var networkBytesIn: UInt64 = 0 @Published var networkBytesOut: UInt64 = 0 // MARK: - Private Properties private var updateTimer: Timer? private var previousCPUInfo: host_cpu_load_info? private var previousNetworkBytes: (in: UInt64, out: UInt64) = (0, 0) private var previousDiskBytes: (read: UInt64, write: UInt64) = (0, 0) private let updateInterval: TimeInterval = 0.5 // MARK: - Public Methods /// Start monitoring system resources func startMonitoring() { // Get initial values previousCPUInfo = getCPULoadInfo() previousNetworkBytes = getNetworkBytes() previousDiskBytes = getDiskBytes() // Start update timer updateTimer = Timer.scheduledTimer(withTimeInterval: updateInterval, repeats: true) { [weak self] _ in self?.updateMetrics() } // Initial update updateMetrics() } /// Stop monitoring func stopMonitoring() { updateTimer?.invalidate() updateTimer = nil } // MARK: - Private Methods private func updateMetrics() { DispatchQueue.global(qos: .background).async { [weak self] in guard let self = self else { return } let cpu = self.calculateCPUUsage() let memory = self.calculateMemoryUsage() let disk = self.calculateDiskActivity() let network = self.calculateNetworkActivity() DispatchQueue.main.async { self.cpuUsage = cpu.total self.cpuUserUsage = cpu.user self.cpuSystemUsage = cpu.system self.memoryUsage = memory.percentage self.memoryUsed = memory.used self.memoryTotal = memory.total self.diskActivity = disk self.networkActivity = network.percentage self.networkBytesIn = network.bytesIn self.networkBytesOut = network.bytesOut } } } // MARK: - CPU Monitoring private func getCPULoadInfo() -> host_cpu_load_info? { var cpuLoadInfo = host_cpu_load_info() var count = mach_msg_type_number_t(MemoryLayout.stride / MemoryLayout.stride) let result = withUnsafeMutablePointer(to: &cpuLoadInfo) { $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &count) } } return result == KERN_SUCCESS ? cpuLoadInfo : nil } private func calculateCPUUsage() -> (total: Double, user: Double, system: Double) { guard let currentInfo = getCPULoadInfo(), let previousInfo = previousCPUInfo else { return (0, 0, 0) } let userDiff = Double(currentInfo.cpu_ticks.0 - previousInfo.cpu_ticks.0) let systemDiff = Double(currentInfo.cpu_ticks.1 - previousInfo.cpu_ticks.1) let idleDiff = Double(currentInfo.cpu_ticks.2 - previousInfo.cpu_ticks.2) let niceDiff = Double(currentInfo.cpu_ticks.3 - previousInfo.cpu_ticks.3) let totalTicks = userDiff + systemDiff + idleDiff + niceDiff guard totalTicks > 0 else { return (0, 0, 0) } let userPercent = (userDiff / totalTicks) * 100 let systemPercent = (systemDiff / totalTicks) * 100 let totalPercent = ((userDiff + systemDiff + niceDiff) / totalTicks) * 100 previousCPUInfo = currentInfo return (min(totalPercent, 100), min(userPercent, 100), min(systemPercent, 100)) } // MARK: - Memory Monitoring private func calculateMemoryUsage() -> (percentage: Double, used: UInt64, total: UInt64) { var stats = vm_statistics64() var count = mach_msg_type_number_t(MemoryLayout.stride / MemoryLayout.stride) let result = withUnsafeMutablePointer(to: &stats) { $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { host_statistics64(mach_host_self(), HOST_VM_INFO64, $0, &count) } } guard result == KERN_SUCCESS else { return (0, 0, 0) } let pageSize = UInt64(vm_kernel_page_size) let totalMemory = ProcessInfo.processInfo.physicalMemory // Calculate used memory let activeMemory = UInt64(stats.active_count) * pageSize let wiredMemory = UInt64(stats.wire_count) * pageSize let compressedMemory = UInt64(stats.compressor_page_count) * pageSize let usedMemory = activeMemory + wiredMemory + compressedMemory let percentage = (Double(usedMemory) / Double(totalMemory)) * 100 return (min(percentage, 100), usedMemory, totalMemory) } // MARK: - Disk Monitoring private func getDiskBytes() -> (read: UInt64, write: UInt64) { // Use IOKit for disk statistics // Simplified implementation - returns approximate values var readBytes: UInt64 = 0 var writeBytes: UInt64 = 0 // Get disk statistics from system let task = Process() task.launchPath = "/usr/bin/iostat" task.arguments = ["-d", "-c", "1"] let pipe = Pipe() task.standardOutput = pipe do { try task.run() task.waitUntilExit() let data = pipe.fileHandleForReading.readDataToEndOfFile() if let output = String(data: data, encoding: .utf8) { // Parse iostat output let lines = output.components(separatedBy: "\n") if lines.count > 2 { let values = lines[2].split(separator: " ").compactMap { Double($0) } if values.count >= 3 { // KB/t, tps, MB/s readBytes = UInt64(values.last ?? 0 * 1024 * 1024) } } } } catch { // Fallback to simulated values } return (readBytes, writeBytes) } private func calculateDiskActivity() -> Double { let currentBytes = getDiskBytes() let readDiff = currentBytes.read > previousDiskBytes.read ? currentBytes.read - previousDiskBytes.read : 0 let writeDiff = currentBytes.write > previousDiskBytes.write ? currentBytes.write - previousDiskBytes.write : 0 previousDiskBytes = currentBytes // Normalize to percentage (assuming 100MB/s as max) let totalBytes = Double(readDiff + writeDiff) let maxBytesPerInterval = 100.0 * 1024 * 1024 * updateInterval let percentage = (totalBytes / maxBytesPerInterval) * 100 return min(percentage, 100) } // MARK: - Network Monitoring private func getNetworkBytes() -> (in: UInt64, out: UInt64) { var ifaddr: UnsafeMutablePointer? var bytesIn: UInt64 = 0 var bytesOut: UInt64 = 0 guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else { return (0, 0) } defer { freeifaddrs(ifaddr) } var ptr = firstAddr while true { let interface = ptr.pointee // Check for data link layer if interface.ifa_addr.pointee.sa_family == UInt8(AF_LINK) { // Get network interface data if let data = interface.ifa_data { let networkData = data.assumingMemoryBound(to: if_data.self).pointee bytesIn += UInt64(networkData.ifi_ibytes) bytesOut += UInt64(networkData.ifi_obytes) } } guard let next = interface.ifa_next else { break } ptr = next } return (bytesIn, bytesOut) } private func calculateNetworkActivity() -> (percentage: Double, bytesIn: UInt64, bytesOut: UInt64) { let currentBytes = getNetworkBytes() let bytesInDiff = currentBytes.in > previousNetworkBytes.in ? currentBytes.in - previousNetworkBytes.in : 0 let bytesOutDiff = currentBytes.out > previousNetworkBytes.out ? currentBytes.out - previousNetworkBytes.out : 0 previousNetworkBytes = currentBytes // Calculate rate in bytes per second let totalBytesPerSecond = Double(bytesInDiff + bytesOutDiff) / updateInterval // Normalize to percentage (assuming 100 Mbps as reference) let maxBytesPerSecond = 100.0 * 1024 * 1024 / 8 // 100 Mbps in bytes let percentage = (totalBytesPerSecond / maxBytesPerSecond) * 100 return (min(percentage, 100), bytesInDiff, bytesOutDiff) } } // MARK: - Memory Formatter Extension extension SystemMonitor { /// Format bytes to human readable string static func formatBytes(_ bytes: UInt64) -> String { let formatter = ByteCountFormatter() formatter.countStyle = .memory return formatter.string(fromByteCount: Int64(bytes)) } }