7a1715bd09
解决合并冲突(原任务 tsk_nsobbj_rJdy0 分支 maestro/tsk_nsobbj_rJdy0):
两分支修改文件集合无重叠,手动 apply iOS PoC commit (e129f09)。
变更内容:
- client/ios/PacketTunnel/PacketTunnelProvider.swift:完整 M3 实现
(startTunnel/stopTunnel/handleAppMessage/LibboxPlatformInterface)
- client/ios/PacketTunnel/MemoryMonitor.swift:新增 NE 进程内存打点
- client/ios/PacketTunnel/PacketTunnel.entitlements:NE + App Group 权限
- client/ios/Runner/Runner.entitlements:主 App NE + App Group 权限
- client/ios/Runner/VpnManager.swift:红线词修复 + App Group 缓存 + NEVPNStatus 订阅
- client/ios/Runner/Info.plist:NSVPNUsageDescription 红线词修复
- client/ios/PacketTunnel/Info.plist:CFBundleDisplayName 红线词修复
- client/ios/Runner.xcodeproj/project.pbxproj:
CODE_SIGN_ENTITLEMENTS 添加到全部 build config;MemoryMonitor 加入 Sources
- app/kernel/build-ios.sh:M3 内存裁剪决策记录
- doc/ne-entitlement-申请指引.md:新增 NE entitlement 申请完整指引
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
141 lines
5.6 KiB
Swift
141 lines
5.6 KiB
Swift
// 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<mach_task_basic_info>.size / MemoryLayout<integer_t>.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)
|
||
}
|
||
}
|