mcpField
M
metrickit

metrickit

github.com/dpearson2699开发1.2k3.2k 次安装13 周前更新
代码仓库

关于 metrickit

面向开发者的 MetricKit 生产遥测指南,涵盖现代报告序列、持久化接收、诊断、自定义指标及 iOS 26 兼容路径。

主要功能

  1. 01指标与诊断报告序列
  2. 02持久化报告并延后上传
  3. 03崩溃、卡顿与资源诊断
  4. 04自定义 signpost 区间指标
  5. 05扩展启动任务测量
  6. 06iOS 26 兼容方案

适用场景

  1. 01接收生产环境性能报告
  2. 02在分析或上传前保存报告
  3. 03排查崩溃、卡顿和资源问题
  4. 04测量应用启动关键任务

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill metrickit

安装后在 Claude Code 中输入/metrickit确认已加载。

附带文件evals/evals.jsonreferences/metrickit-patterns.md

详细介绍

MetricKit

使用 MetricKit 获取开销低的生产环境遥测数据,以补充本地 Instruments 和 Xcode Organizer 分析。在 iOS 和 iPadOS 27 上,优先使用 Swift 优先的 MetricManager 报告序列。仅在明确的 iOS 26 兼容分支中保留 MXMetricManager。

Beta 敏感: 以下 iOS/iPadOS 27 接口基于 Apple 当前的 Beta 文档。由于此环境中没有 Xcode 27,因此尚未在本地通过编译验证。发布前请重新检查链接的 Apple 文档,并使用正式版 Xcode 27 SDK 编译。

实现持久化数据摄取、详细报告分析或 iOS 26 兼容路径时,请加载 MetricKit 扩展与兼容模式。

目录

MetricManager 设置

应用启动时,创建并保留一个长生命周期的 MetricManager。为 metricReports 和 diagnosticReports 各启动一个消费者任务,且每个只能启动一个。

这两个属性都提供不抛错的 AsyncSequence 值:

  • metricReports: some AsyncSequence<MetricReport, Never>
  • diagnosticReports: some AsyncSequence<DiagnosticReport, Never>

Apple 指出,同一序列的并发消费者可能收到不确定的子集。只有在单个消费者收到并持久化存储报告后,才进行分发;延迟订阅可能导致报告遗漏。

import MetricKit

@available(iOS 27.0, *)
final class MetricsService {
    private let manager = MetricManager()
    private var metricTask: Task<Void, Never>?
    private var diagnosticTask: Task<Void, Never>?

    func start(
        persistMetric: @escaping @Sendable (MetricReport) async -> Void,
        persistDiagnostic: @escaping @Sendable (DiagnosticReport) async -> Void
    ) {
        guard metricTask == nil, diagnosticTask == nil else { return }
        let manager = manager

        metricTask = Task {
            for await report in manager.metricReports {
                await persistMetric(report)
            }
        }

        diagnosticTask = Task {
            for await report in manager.diagnosticReports {
                await persistDiagnostic(report)
            }
        }
    }

    deinit {
        metricTask?.cancel()
        diagnosticTask?.cancel()
    }
}

持久化闭包需根据应用实际情况实现。请使用下文的先持久化工作流,而不是丢弃报告、仅记录日志或直接逐条上传。如果需要按状态划分的指标,请使用文档所述的 init(enabledStateReportingDomains:) 初始化方法和必需的域来构造管理器。

接收指标报告

MetricReport 遵循 Codable 和 Sendable。它通过以下内容描述一个时间间隔:

  • timeRange: DateInterval
  • 可选的 environment 元数据
  • 用于全天及更短时间间隔测量的 intervalEntries
  • 与应用状态相关联的测量数据 stateEntries

指标报告通常按日送达。提取单项结果之前,请先持久化完整报告。

进行每日分析时,请读取文档所述的 fullDayEntry,并对其 MetricResult 值进行 switch 处理:

let entry = report.intervalEntries.fullDayEntry

for result in entry.values {
    switch result {
    case .hangTime(let metric):
        analyzeHangTime(metric)
    case .peakMemory(let metric):
        analyzePeakMemory(metric)
    case .timeToFirstDraw(let metric):
        analyzeLaunch(metric)
    case .signpostInterval(let metric):
        analyzeSignpost(metric)
    @unknown default:
        preserveUnknownMetric(result)
    }
}

使用 @unknown default,以免 Beta 版或未来的结果导致数据摄取流水线变得脆弱。即使当前应用无法识别某个结果,也要保留报告的原始编码数据。

接收诊断报告

DiagnosticReport 遵循 Codable 和 Sendable。它包含一个 timeRange、必需的 environment 元数据,以及一个 DiagnosticResult。

诊断是独立的事件型报告,旨在由 MetricKit 生成后立即交付给提示词。不要假设每次崩溃、卡顿或资源事件都会生成报告;仍需考虑系统采样和资格条件。

持久化存储后,明确路由结果:

switch report.result {
case .crash(let diagnostic):
    analyzeCrash(diagnostic)
case .hang(let diagnostic):
    analyzeHang(diagnostic)
case .cpuException(let diagnostic):
    analyzeCPUException(diagnostic)
case .diskWriteException(let diagnostic):
    analyzeDiskWrites(diagnostic)
case .appLaunch(let diagnostic):
    analyzeLaunch(diagnostic)
case .memoryException(let diagnostic):
    analyzeMemory(diagnostic)
@unknown default:
    preserveUnknownDiagnostic(report)
}

iOS/iPadOS 27 的诊断类型包括 CrashDiagnostic、HangDiagnostic、CPUExceptionDiagnostic、DiskWriteExceptionDiagnostic、AppLaunchDiagnostic 和 MemoryExceptionDiagnostic。内存异常类型是 iOS 27 新增的。

有用的字段包括:

诊断类型重要字段
CrashDiagnosticcallStackTree、异常类型/代码/原因、信号、虚拟内存区域、终止类别/原因
HangDiagnosticcallStackTree、hangDuration
CPUExceptionDiagnosticcallStackTree、totalCPUTime、totalSampledTime
DiskWriteExceptionDiagnosticcallStackTree、totalBytesWritten
AppLaunchDiagnosticcallStackTree、launchDuration
MemoryExceptionDiagnosticcallStackTree

关键指标结果

选择一组精简的遥测词汇,以匹配调查需求,而不是导出所有结果。优先关注相关类别:响应能力与终止;运行时、CPU、内存、网络和存储;或启动、显示/GPU 和自定义时间间隔。

按应用版本和环境元数据聚合,比较分布而非单个值,并将回归问题与版本发布相关联。避免把每日聚合数据当作某个用户操作的精确轨迹。

在将确切的 MetricResult case 映射到解析器或仪表板时,请加载关键指标结果目录。

调用栈树

iOS 27 的 CallStackTree 取代了 MXCallStackTree。它遵循 Codable 和 Sendable,并提供:

  • 用于遍历帧的 forEachFrame
  • 用于按线程分析的 callStackThreads
  • 用于映像和二进制元数据的 binaryInfo

在展开或符号化之前,先存储完整的诊断报告。保留二进制标识符和偏移量,以便服务端符号化使用匹配的归档文件和 dSYM。

自定义 Signpost 指标

通过管理器创建 OS 日志,然后使用 mxSignpost 测量开始/结束时间:

let log = manager.logHandle(category: "ImagePipeline")

mxSignpost(.begin, log: log, name: "Decode")
await decodeImage()
mxSignpost(.end, log: log, name: "Decode")

MetricKit 将聚合结果公开为 MetricResult.signpostInterval。不要在 MetricReport 上查找 signpostMetrics 数组。

需要 MetricKit 资源测量时,请使用 mxSignpost。Apple 文档指出,使用 OSSignposter 和 MetricKit 日志句柄创建的时间间隔,不会填充 mxSignpost 提供的资源测量字段。

将高频本地追踪与用于 MetricKit 聚合的少量稳定生产时间间隔分开。

持久化导出与上传

将报告交付视为至少一次的摄取问题:

  1. 使用 JSONEncoder 对完整的 MetricReport 或 DiagnosticReport 进行编码。
  2. 将字节原子性地加入应用自有的持久化发件箱队列。
  3. 记录报告类型、架构版本、应用版本和稳定的去重键。
  4. 仅在成功入队后确认本地处理完成。
  5. 稍后通过重试、退避、批处理和保留限制进行上传。
  6. 仅在服务端接受后,将发件箱项标记为已上传。
let data = try JSONEncoder().encode(report)
try await durableOutbox.enqueue(data, kind: .metric)

durableOutbox 是应用自有的抽象,并非 MetricKit API。不要在序列消费者中同步执行网络上传。

通过现代序列接收报告时,持久化本地存储是恢复机制;不要让成功摄取依赖于假定存在的现代回补 API。

扩展启动测量

对于超出首次绘制时间的工作,请在管理器上使用 trackLaunchTask(id:onTrackingError:_:):

await manager.trackLaunchTask(
    id: "bootstrap-data",
    onTrackingError: { error in
        recordLaunchTrackingError(error)
    }
) {
    await bootstrapApplication()
}

此 API 为 @MainActor,并提供同步和异步重载。任务闭包的结果和抛出的错误会传递给调用方;MetricManager.LaunchTaskError 会通过 onTrackingError 报告,且不会中断受跟踪的工作。结果会显示为 MetricResult.extendedLaunch。

使用稳定的 LaunchTaskID 值,并且只跟踪启动关键工作。

iOS 26 兼容性

对于仍支持部署到 iOS 26 的应用,请使用可用性边界:

  • iOS/iPadOS 27:使用 MetricManager 并消费两个异步报告序列。
  • iOS/iPadOS 26 及更早的受支持版本:使用 MXMetricManager.shared、MXMetricManagerSubscriber 和旧版载荷回调。

旧版 API 仍是唯一有文档说明支持 pastPayloads 和 pastDiagnosticPayloads 的分支。MXMetricManager 在 iOS 27 中已弃用,因此应通过可用性检查将其隔离,而不是把旧版载荷混入现代处理管线。

加载 iOS 26 兼容性,了解完整的订阅器、旧版 signpost、历史载荷和扩展启动模式。

Xcode Organizer

在构建自定义后端之前,使用 Xcode Organizer 检查 Apple 汇总的生产指标、卡顿、崩溃和回归。如果产品需要自定义关联、保留、告警或与现有可观测性系统集成,请直接接入 MetricKit 数据。

不要指望开发设备上的运行能够重现生产报告的人群范围、频率或汇总方式。

范围边界

任务改用
在本地重现问题或记录详细跟踪信息debugging-instruments
诊断保留对象或内存图路径ios-memgraph-analysis
调整 SwiftUI 失效、标识或滚动代码swiftui-performance
研究结构化的 EnergyKit 影响数据energykit
设计通用日志记录和 os_signpost 策略swift-logging

MetricKit 用于识别生产环境中的症状和趋势。请将实际代码修复交由负责相关子系统的技能处理。

常见错误

错误修正方式
在序列循环中直接上传先在本地编码并加入队列;稍后再异步上传。
认为每个诊断事件都会生成报告将诊断视为经采样、由系统生成的证据。
在 MetricManager 上查找 pastPayloads仅在 iOS 26 的 MXMetricManager 分支中保留旧版回填逻辑。
只解析已知的枚举情况保留原始报告并使用 @unknown default。
在 iOS 27 分支中使用 MXMetricManager.makeLogHandle使用 manager.logHandle(category:)。
在 iOS 27 分支中使用成对的启动扩展/结束调用使用 trackLaunchTask。
将 MetricKit 汇总数据视为本地跟踪信息使用 Instruments 和 signpost 重现问题。

审查清单

  • iOS 27 分支在启动时运行,保留一个 MetricManager,并为每个报告序列配置一个消费者。
  • 每份报告都会先编码并可靠地加入队列,然后再进行分析或上传。
  • 诊断和指标的 switch 都使用 @unknown default。
  • 未知的原始报告仍可恢复。
  • 保留符号化元数据和匹配的 dSYM。
  • 自定义区间使用 manager.logHandle(category:) 和 mxSignpost。
  • 扩展启动工作使用 trackLaunchTask。
  • iOS 26 的 MXMetricManager 路径通过可用性检查隔离。
  • 适用时,生产汇总数据会引导开展本地 Instruments 调查。
  • 已重新检查 iOS 27 beta API,并使用发布版 Xcode 27 SDK 编译。

参考资料

评论

支持 Markdown