// MemoryMonitor.swift — NE 进程内存用量周期打点 // // 职责: // 1. 周期调用 os_proc_available_memory()(iOS 13+)取 NE 进程剩余可分配内存。 // 2. 通过 task_info(MACH_TASK_BASIC_INFO) 取当前 resident size(兼容 iOS 12)。 // 3. 若 availableMemory < warningThreshold,输出 ⚠️ 警告日志。 // 4. 10 分钟持续运行打点,为 M3 内存上限验收提供数据。 // // 使用: // let monitor = MemoryMonitor() // monitor.start() // 开始打点(启动隧道后调用) // monitor.stop() // 停止打点(隧道停止后调用) // monitor.peakResidentBytes // 读取峰值 // // 参考阈值(NE 进程 jetsam 限制): // 旧设备(≤ A9):~15 MB residual limit // 新设备(≥ A12):~50 MB residual limit // 保守警告线:availableMemory < 5 MB 时认为高风险 import Darwin import Foundation import os.log // ── os_proc_available_memory 声明(iOS 13+ libSystem 符号,低版本回退到 mach)── // Swift 无法直接引用 os/proc.h 中的符号,用 @_silgen_name 桥接。 @_silgen_name("os_proc_available_memory") private func _os_proc_available_memory() -> UInt64 // MARK: – MemoryMonitor final class MemoryMonitor { // ── 配置 ──────────────────────────────────────────────────── /// 打点间隔(默认 10 秒,可在单测中缩短) let interval: TimeInterval /// 剩余内存警告阈值(默认 5 MB) let warningThreshold: UInt64 /// os.log 子系统 private let log: OSLog // ── 状态 ──────────────────────────────────────────────────── private var timer: DispatchSourceTimer? private let queue = DispatchQueue(label: "com.pangolin.memory-monitor", qos: .utility) private(set) var peakResidentBytes: UInt64 = 0 private(set) var minAvailableBytes: UInt64 = UInt64.max private(set) var sampleCount: Int = 0 init( interval: TimeInterval = 10, warningThreshold: UInt64 = 5 * 1024 * 1024, subsystem: String = "com.pangolin.pangolinVpn.PacketTunnel" ) { self.interval = interval self.warningThreshold = warningThreshold self.log = OSLog(subsystem: subsystem, category: "MemoryMonitor") } // MARK: – 控制 func start() { stop() let t = DispatchSource.makeTimerSource(queue: queue) t.schedule(deadline: .now(), repeating: interval, leeway: .seconds(1)) t.setEventHandler { [weak self] in self?.tick() } timer = t t.resume() os_log("MemoryMonitor: 开始打点,间隔=%.0fs", log: log, type: .info, interval) } func stop() { timer?.cancel() timer = nil os_log( "MemoryMonitor: 停止打点。总采样 %d 次 | 峰值 RSS=%.1f MB | 最小可用=%.1f MB", log: log, type: .info, sampleCount, Double(peakResidentBytes) / 1_048_576, minAvailableBytes == UInt64.max ? -1.0 : Double(minAvailableBytes) / 1_048_576 ) } // MARK: – 采样 private func tick() { let resident = residentBytes() let available = availableBytes() sampleCount += 1 if resident > peakResidentBytes { peakResidentBytes = resident } if available < minAvailableBytes { minAvailableBytes = available } let level: OSLogType = available < warningThreshold ? .error : .info os_log( "MemoryMonitor [#%d] RSS=%.1f MB | 可用=%.1f MB%{public}@", log: log, type: level, sampleCount, Double(resident) / 1_048_576, Double(available) / 1_048_576, available < warningThreshold ? " ⚠️ 接近上限,建议裁剪 build tags" : "" ) } // MARK: – 底层读取 /// 当前进程 resident size(RSS),iOS 12+ 兼容。 func residentBytes() -> UInt64 { var info = mach_task_basic_info() var count = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) let result: kern_return_t = withUnsafeMutablePointer(to: &info) { $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count) } } return result == KERN_SUCCESS ? UInt64(info.resident_size) : 0 } /// NE 进程剩余可分配内存。 /// iOS 13+:调用 os_proc_available_memory()。 /// iOS 12:退而估算 MAX_RSS - resident(不精确,仅供参考)。 func availableBytes() -> UInt64 { if #available(iOS 13.0, *) { return _os_proc_available_memory() } else { // iOS 12 回退:用 15 MB 保守上限 - 当前 RSS let conservativeLimit: UInt64 = 15 * 1024 * 1024 let resident = residentBytes() return resident < conservativeLimit ? conservativeLimit - resident : 0 } } /// 生成一行摘要字符串(供 handleAppMessage/getStats 返回)。 func summaryString() -> String { let rss = Double(residentBytes()) / 1_048_576 let avail = Double(availableBytes()) / 1_048_576 return String(format: "RSS=%.1fMB avail=%.1fMB peak=%.1fMB samples=%d", rss, avail, Double(peakResidentBytes) / 1_048_576, sampleCount) } }