MetricKit 详解
一、概览
MetricKit 从 iOS 13 系统开始引入。借助 MetricKit,您可以接收系统捕获的诊断报告、电源和性能指标。
1.1 基本特性
- 报告频率:已注册的应用每天最多只能接收一次包含前 24 小时数据的报告
- 即时分发:iOS 15 以上和 macOS 12 以上系统会立即分发诊断报告
- 系统级监控:MetricKit 由系统自动收集数据,无需手动埋点
1.2 使用场景
MetricKit 主要用于:
– 监控应用的性能指标
– 分析应用崩溃和异常退出
– 追踪电池消耗情况
– 检测应用响应性问题
– 监控磁盘 I/O 性能
二、指标分类
MetricKit 提供了四大类指标:
2.1 Battery Metric(电池指标)
Battery Metric 用于监控应用的电池消耗情况,包括:
- 信号:信号强度对电池的影响
- CPU:CPU 使用情况
- GPU:GPU 使用情况
- 位置:定位服务的使用情况
- 网络:网络请求对电池的影响
- CPU 异常诊断:CPU 异常使用导致的电池消耗
2.2 Performance Metric(性能指标)
Performance Metric 用于监控应用的性能表现,包括应用退出统计、运行时间、内存使用、启动诊断和崩溃诊断。
2.2.1 应用退出统计
| 分类 | 名称 | 描述 | 前台 | 后台 |
|---|---|---|---|---|
| 正常退出 | cumulativeNormalAppExitCount |
正常退出 | ✅ | ✅ |
| abort | cumulativeAbnormalExitCount |
abort 退出 | ✅ | ✅ |
| crash | cumulativeBadAccessExitCount |
内存访问违规退出 | ✅ | ✅ |
| crash | cumulativeIllegalInstructionExitCount |
无效指令退出 | ✅ | ✅ |
| 系统终止 | cumulativeAppWatchdogExitCount |
watchdog 强杀 | ✅ | ✅ |
| 系统终止 | cumulativeCPUResourceLimitExitCount |
超过 CPU 资源使用限制 | ❌ | ✅ |
| 系统终止 | cumulativeMemoryResourceLimitExitCount |
超过内存资源限制 | ✅ | ✅ |
| 系统终止 | cumulativeMemoryPressureExitCount |
内存压力退出 | ❌ | ✅ |
| 系统终止 | cumulativeSuspendedWithLockedFileExitCount |
持有文件锁并且处于挂起状态 | ❌ | ✅ |
| 超时 | cumulativeBackgroundTaskAssertionTimeoutExitCount |
后台任务超时 | ❌ | ✅ |
说明:
– ✅ 表示该指标在该状态下可用
– ❌ 表示该指标在该状态下不可用
2.2.2 其他性能指标
- 运行时间:应用运行时长统计
- 内存:内存使用情况统计
- 启动诊断:应用启动性能分析
- Crash 诊断:崩溃堆栈和诊断信息
2.3 Responsive Metric(响应性指标)
Responsive Metric 用于监控应用的响应性能,包括:
- 启动:应用启动响应时间
- 响应:用户交互响应时间
- 动画:动画流畅度指标
- 卡顿诊断:卡顿堆栈和诊断信息
2.4 Disk Access Metric(磁盘访问指标)
Disk Access Metric 用于监控应用的磁盘 I/O 性能,包括:
- 磁盘 I/O:磁盘读写操作统计
- 磁盘写诊断:磁盘写入性能分析
三、MetricKit 上报逻辑
3.1 堆栈上报方式
MetricKit 支持两种堆栈上报方式:
- 树形堆栈展开为列表堆栈进行上报
- 将树形结构的堆栈信息展开为线性列表
-
便于后端解析和分析
-
原始堆栈直接写文件
- 保留完整的树形结构信息
- 用于详细分析和调试
3.2 优化策略
堆栈上报优化:
– Crash 堆栈:使用列表堆栈进行上报,便于快速定位问题
– 其他堆栈:使用树形结构进行上报,保留完整的调用关系
补充上报:
– 动画指标:补充动画相关的性能数据
– 启动诊断:补充应用启动过程的详细诊断信息
四、使用示例
4.1 基本使用
#import <MetricKit/MetricKit.h>
@interface AppDelegate () <MXMetricManagerSubscriber>
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 订阅 MetricKit 报告
[[MXMetricManager sharedManager] addSubscriber:self];
return YES;
}
#pragma mark - MXMetricManagerSubscriber
- (void)didReceiveMetricPayloads:(NSArray<MXMetricPayload *> *)payloads {
for (MXMetricPayload *payload in payloads) {
// 处理性能指标
[self handlePerformanceMetrics:payload.performanceMetrics];
// 处理电池指标
[self handleBatteryMetrics:payload.batteryMetrics];
// 处理响应性指标
[self handleResponsivenessMetrics:payload.responsivenessMetrics];
// 处理磁盘访问指标
[self handleDiskIOMetrics:payload.diskIOMetrics];
}
}
- (void)handlePerformanceMetrics:(MXPerformanceMetrics *)metrics {
// 处理性能指标
MXAppExitMetrics *exitMetrics = metrics.applicationExitMetrics;
NSLog(@"Normal exits: %lu", exitMetrics.cumulativeNormalAppExitCount);
NSLog(@"Abnormal exits: %lu", exitMetrics.cumulativeAbnormalExitCount);
}
- (void)handleBatteryMetrics:(MXBatteryMetrics *)metrics {
// 处理电池指标
NSLog(@"CPU usage: %@", metrics.cpuMetrics);
}
- (void)handleResponsivenessMetrics:(MXResponsivenessMetrics *)metrics {
// 处理响应性指标
NSLog(@"Hang count: %lu", metrics.hangCount);
}
- (void)handleDiskIOMetrics:(MXDiskIOMetrics *)metrics {
// 处理磁盘 I/O 指标
NSLog(@"Cumulative logical writes: %llu", metrics.cumulativeLogicalWrites);
}
@end
4.2 诊断报告处理
- (void)didReceiveDiagnosticPayloads:(NSArray<MXDiagnosticPayload *> *)payloads {
for (MXDiagnosticPayload *payload in payloads) {
// 处理崩溃报告
for (MXCrashDiagnostic *crashDiagnostic in payload.crashDiagnostics) {
NSLog(@"Crash reason: %@", crashDiagnostic.crashReason);
NSLog(@"Exception type: %@", crashDiagnostic.exceptionType);
NSLog(@"Exception code: %@", crashDiagnostic.exceptionCode);
}
// 处理 CPU 异常报告
for (MXCPUExceptionDiagnostic *cpuException in payload.cpuExceptionDiagnostics) {
NSLog(@"CPU exception: %@", cpuException);
}
// 处理磁盘写入异常报告
for (MXDiskWriteExceptionDiagnostic *diskException in payload.diskWriteExceptionDiagnostics) {
NSLog(@"Disk write exception: %@", diskException);
}
// 处理挂起报告
for (MXHangDiagnostic *hangDiagnostic in payload.hangDiagnostics) {
NSLog(@"Hang duration: %f", hangDiagnostic.hangDuration);
NSLog(@"Hang call stack: %@", hangDiagnostic.callStackTree);
}
// 处理启动报告
for (MXLaunchDiagnostic *launchDiagnostic in payload.launchDiagnostics) {
NSLog(@"Launch duration: %f", launchDiagnostic.launchDuration);
}
}
}
五、注意事项
5.1 系统要求
- 最低版本:iOS 13.0+ / macOS 12.0+
- 即时分发:iOS 15.0+ / macOS 12.0+ 支持即时分发报告
- 报告频率:每天最多接收一次报告
5.2 数据隐私
- MetricKit 收集的数据仅包含性能指标,不包含用户隐私信息
- 所有数据都在设备本地处理,不会上传到 Apple 服务器
- 开发者需要自行实现数据上报逻辑
5.3 性能影响
- MetricKit 由系统自动管理,对应用性能影响极小
- 建议在后台线程处理报告数据,避免阻塞主线程
- 合理设置数据上报频率,避免过度上报
六、最佳实践
- 及时处理报告:在收到报告后及时处理,避免数据堆积
- 数据聚合:对相同类型的指标进行聚合分析
- 异常告警:设置阈值,对异常指标进行告警
- 数据可视化:将指标数据可视化展示,便于分析
- 持续监控:建立持续监控机制,及时发现性能问题
