e129f094c2
### 新增文件 - client/ios/PacketTunnel/MemoryMonitor.swift 周期打点 NE 进程内存(os_proc_available_memory iOS13+ / mach_task_basic_info 兼容 iOS12);每 10s 采样一次;availableMemory < 5MB 时输出 ⚠️ 警告;提供 summaryString() 供 handleAppMessage/getMemory IPC 返回。 - client/ios/PacketTunnel/PacketTunnel.entitlements Extension 的 NE + App Group entitlement 文件;申请说明写入注释。 - client/ios/Runner/Runner.entitlements 主 App 的 NE + App Group entitlement 文件;申请说明写入注释。 - doc/ne-entitlement-申请指引.md 完整申请操作手册:Bundle ID 清单、Apple Developer Portal 步骤、 专项审批英文申请文案(已脱敏,无红线词)、审批 lead time 说明。 ### 修改文件 - client/ios/PacketTunnel/PacketTunnelProvider.swift 完整 M3 实现(替换原骨架 TODO): · startTunnel:解析 configJson(options/App Group 两路回退)→ buildNetworkSettings → setTunnelNetworkSettings → setupTunBridge → startLibbox → startPacketBridging → memoryMonitor.start() · stopTunnel:memoryMonitor.stop() → stopLibbox → closeTunBridge · handleAppMessage:JSON IPC 协议(getStatus/getMemory/selectOutbound) · LibboxPlatformInterface 扩展(#if canImport(Libbox)): openTun 返回 socketpair libbox 侧 fd,autoDetectInterfaceControl no-op, writeLog 转 os.log,useProcFS 返回 false · packetFlow 双向桥接(readFromPacketFlow/writeToPacketFlow 循环) · parseTunAddress/parseDNSServers 从 JSON 提取网络参数 - client/ios/Runner/VpnManager.swift · localizedDescription "穿山甲 Pangolin VPN" → "Pangolin 加速"(红线词修复) · 新增 appGroup 常量(group.com.pangolin.pangolinVpn) · start() 写入 App Group UserDefaults 缓存 configJson - client/ios/Runner/Info.plist NSVPNUsageDescription "穿山甲使用 VPN…" → "Pangolin 使用网络加速通道…"(红线词修复) - client/ios/PacketTunnel/Info.plist CFBundleDisplayName "穿山甲 Tunnel" → "Pangolin 加速通道"(红线词修复) - client/ios/Runner.xcodeproj/project.pbxproj · Runner + PacketTunnel 所有 build config 添加 CODE_SIGN_ENTITLEMENTS · 新增 MemoryMonitor.swift 到 PacketTunnel Sources 构建阶段 · 新增 Runner.entitlements / PacketTunnel.entitlements / MemoryMonitor.swift 文件引用及分组 - app/kernel/build-ios.sh 添加 M3 内存裁剪决策记录:gVisor→grpc→QUIC 优先裁减顺序及内存估算, 等待真机 10min 压测结果后更新最终结论 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)
|
||
}
|
||
}
|