Files
pangolin/client/ios/PacketTunnel/MemoryMonitor.swift
T
wangjia 7a1715bd09 merge: iOS 端 PoC M3 + entitlement 申请 [tsk_F5r4Kt9m_wQU]
解决合并冲突(原任务 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>
2026-06-13 20:14:32 +08:00

141 lines
5.6 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 sizeRSSiOS 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)
}
}