axiom-metrickit-ref について
MetricKit で実利用環境のパフォーマンス指標と診断情報を収集する iOS 開発者向けの API リファレンスです。ペイロード解析、コールスタックのシンボリケーション、バックグラウンド終了理由などを扱います。
主な機能
- 01MetricKit サブスクライバーの設定
- 02パフォーマンスペイロードの解析
- 03クラッシュとハングの診断
- 04コールスタックのシンボリケーション
- 05バックグラウンド終了理由の分析
- 06ペイロードのエクスポートと送信例
活用シーン
- 01MetricKit データ購読の導入
- 02CPU・メモリ・起動指標の確認
- 03クラッシュ、ハング、Jetsam の調査
- 04診断情報とクラッシュレポーターの統合
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-metrickit-refインストール後、Claude Code で/axiom-metrickit-refを入力して読み込まれたか確認します。
詳細
MetricKit API リファレンス
MetricKit を使用してフィールドのパフォーマンスメトリクスと診断データを収集するための、完全な API リファレンスです。
概要
MetricKit は、分析情報の共有をオプトインしたユーザーから、集計されたオンデバイスのパフォーマンスデータと診断データを提供します。データは毎日(開発時はオンデマンド)配信されます。
このリファレンスを使用する場面
次の場合にこのリファレンスを使用してください。
- アプリで MetricKit サブスクライバーを設定する
- MXMetricPayload または MXDiagnosticPayload を解析する
- MXCallStackTree のクラッシュデータをシンボリケーションする
- バックグラウンド終了の理由(jetsam、watchdog)を理解する
- MetricKit を既存のクラッシュレポーターと統合する
ハング診断のワークフローについては、axiom-hang-diagnostics を参照してください。
Instruments を使用した一般的なプロファイリングについては、axiom-performance-profiling を参照してください。
jetsam を含むメモリデバッグについては、axiom-memory-debugging を参照してください。
よくある注意点
- 24 時間の遅延 — MetricKit のデータは 1 日に 1 回届き、リアルタイムのデバッグには使えません
- コールスタックにはシンボリケーションが必要 — MXCallStackTree のフレームはシンボリケーションされていないため、dSYM を保管してください
- オプトインのみ — 「App デベロッパと共有」を有効にしたユーザーのみがデータを提供します
- 個別ではなく集計データ — ユーザーごとのトレースではなく、件数と平均値が取得されます
- シミュレーターでは動作しません — MetricKit は実機でのみデータを収集します
iOS バージョンのサポート:
| 機能 | iOS バージョン |
|---|---|
| 基本メトリクス(バッテリー、CPU、メモリ) | iOS 13+ |
| 診断ペイロード | iOS 14+ |
| ハング診断 | iOS 14+ |
| 起動診断 | iOS 16+ |
| 開発時の即時配信 | iOS 15+ |
パート 1:セットアップ
基本的な統合
import MetricKit
class AppMetricsSubscriber: NSObject, MXMetricManagerSubscriber {
override init() {
super.init()
MXMetricManager.shared.add(self)
}
deinit {
MXMetricManager.shared.remove(self)
}
// MARK: - MXMetricManagerSubscriber
func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
processMetrics(payload)
}
}
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
processDiagnostics(payload)
}
}
}
登録するタイミング
アプリのライフサイクルの早い段階でサブスクライバーを登録します。
@main
struct MyApp: App {
@StateObject private var metricsSubscriber = AppMetricsSubscriber()
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
または AppDelegate で登録します。
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
metricsSubscriber = AppMetricsSubscriber()
return true
}
開発時のテスト
iOS 15+ では、デバッグメニューから即時配信をトリガーできます。
Xcode > Debug > Simulate MetricKit Payloads
または、プログラムで実行します(デバッグビルドのみ)。
#if DEBUG
// Payloads delivered immediately in development
// No special code needed - just run and wait
#endif
パート 2:MXMetricPayload
MXMetricPayload には、過去 24 時間の集計済みパフォーマンスメトリクスが含まれます。
ペイロードの構造
func processMetrics(_ payload: MXMetricPayload) {
// Time range for this payload
let start = payload.timeStampBegin
let end = payload.timeStampEnd
// App version that generated this data
let version = payload.metaData?.applicationBuildVersion
// Access specific metric categories
if let cpuMetrics = payload.cpuMetrics {
processCPU(cpuMetrics)
}
if let memoryMetrics = payload.memoryMetrics {
processMemory(memoryMetrics)
}
if let launchMetrics = payload.applicationLaunchMetrics {
processLaunches(launchMetrics)
}
// ... other categories
}
CPU メトリクス(MXCPUMetric)
func processCPU(_ metrics: MXCPUMetric) {
// Cumulative CPU time
let cpuTime = metrics.cumulativeCPUTime // Measurement<UnitDuration>
// iOS 14+: CPU instruction count
if #available(iOS 14.0, *) {
let instructions = metrics.cumulativeCPUInstructions // Measurement<Unit>
}
}
メモリメトリクス(MXMemoryMetric)
func processMemory(_ metrics: MXMemoryMetric) {
// Peak memory usage
let peakMemory = metrics.peakMemoryUsage // Measurement<UnitInformationStorage>
// Average suspended memory
let avgSuspended = metrics.averageSuspendedMemory // MXAverage<UnitInformationStorage>
}
起動メトリクス(MXAppLaunchMetric)
func processLaunches(_ metrics: MXAppLaunchMetric) {
// First draw (cold launch) histogram
let firstDrawHistogram = metrics.histogrammedTimeToFirstDraw
// Resume time histogram
let resumeHistogram = metrics.histogrammedApplicationResumeTime
// Optimized time to first draw (iOS 15.2+)
if #available(iOS 15.2, *) {
let optimizedLaunch = metrics.histogrammedOptimizedTimeToFirstDraw
}
// Parse histogram buckets
for bucket in firstDrawHistogram.bucketEnumerator {
if let bucket = bucket as? MXHistogramBucket<UnitDuration> {
let start = bucket.bucketStart // e.g., 0ms
let end = bucket.bucketEnd // e.g., 100ms
let count = bucket.bucketCount // Number of launches in this range
}
}
}
アプリケーション終了メトリクス(MXAppExitMetric) — iOS 14+
@available(iOS 14.0, *)
func processExits(_ metrics: MXAppExitMetric) {
let fg = metrics.foregroundExitData
let bg = metrics.backgroundExitData
// Foreground (onscreen) exits
let fgNormal = fg.cumulativeNormalAppExitCount
let fgWatchdog = fg.cumulativeAppWatchdogExitCount
let fgMemoryLimit = fg.cumulativeMemoryResourceLimitExitCount
let fgMemoryPressure = fg.cumulativeMemoryPressureExitCount
let fgBadAccess = fg.cumulativeBadAccessExitCount
let fgIllegalInstruction = fg.cumulativeIllegalInstructionExitCount
let fgAbnormal = fg.cumulativeAbnormalExitCount
// Background exits
let bgSuspended = bg.cumulativeSuspendedWithLockedFileExitCount
let bgTaskTimeout = bg.cumulativeBackgroundTaskAssertionTimeoutExitCount
let bgCPULimit = bg.cumulativeCPUResourceLimitExitCount
}
スクロールの引っかかりメトリクス(MXAnimationMetric) — iOS 14+
@available(iOS 14.0, *)
func processHitches(_ metrics: MXAnimationMetric) {
// Scroll hitch rate (hitches per scroll)
let scrollHitchRate = metrics.scrollHitchTimeRatio // Double (0.0 - 1.0)
}
ディスク I/O メトリクス(MXDiskIOMetric)
func processDiskIO(_ metrics: MXDiskIOMetric) {
let logicalWrites = metrics.cumulativeLogicalWrites // Measurement<UnitInformationStorage>
}
ネットワーク転送メトリクス(MXNetworkTransferMetric)
func processNetwork(_ metrics: MXNetworkTransferMetric) {
let cellUpload = metrics.cumulativeCellularUpload
let cellDownload = metrics.cumulativeCellularDownload
let wifiUpload = metrics.cumulativeWifiUpload
let wifiDownload = metrics.cumulativeWifiDownload
}
Signpost メトリクス(MXSignpostMetric)
Signpost を使用してカスタム操作を追跡します。
// In your code: emit signposts
import os.signpost
let log = MXMetricManager.makeLogHandle(category: "ImageProcessing")
func processImage(_ image: UIImage) {
mxSignpost(.begin, log: log, name: "ProcessImage")
// ... do work ...
mxSignpost(.end, log: log, name: "ProcessImage")
}
// In metrics subscriber: read signpost data
func processSignposts(_ metrics: MXSignpostMetric) {
let name = metrics.signpostName
let category = metrics.signpostCategory
// Histogram of durations
let histogram = metrics.signpostIntervalData.histogrammedSignpostDurations
// Total count
let count = metrics.totalCount
}
ペイロードを JSON としてエクスポートする
func exportPayload(_ payload: MXMetricPayload) {
// JSON representation for upload to analytics
let jsonData = payload.jsonRepresentation()
// Or as Dictionary
if let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] {
uploadToAnalytics(json)
}
}
パート 3:MXDiagnosticPayload — iOS 14+
MXDiagnosticPayload には、クラッシュ、ハング、ディスク書き込み例外、CPU 例外の診断レポートが含まれます。
ペイロードの構造
@available(iOS 14.0, *)
func processDiagnostics(_ payload: MXDiagnosticPayload) {
// Crash diagnostics
if let crashes = payload.crashDiagnostics {
for crash in crashes {
processCrash(crash)
}
}
// Hang diagnostics
if let hangs = payload.hangDiagnostics {
for hang in hangs {
processHang(hang)
}
}
// Disk write exceptions
if let diskWrites = payload.diskWriteExceptionDiagnostics {
for diskWrite in diskWrites {
processDiskWriteException(diskWrite)
}
}
// CPU exceptions
if let cpuExceptions = payload.cpuExceptionDiagnostics {
for cpuException in cpuExceptions {
processCPUException(cpuException)
}
}
}
MXCrashDiagnostic
@available(iOS 14.0, *)
func processCrash(_ diagnostic: MXCrashDiagnostic) {
// Call stack tree (needs symbolication)
let callStackTree = diagnostic.callStackTree
// Crash metadata
let signal = diagnostic.signal // e.g., SIGSEGV
let exceptionType = diagnostic.exceptionType // e.g., EXC_BAD_ACCESS
let exceptionCode = diagnostic.exceptionCode
let terminationReason = diagnostic.terminationReason
// Virtual memory info
let virtualMemoryRegionInfo = diagnostic.virtualMemoryRegionInfo
// Unique identifier for grouping similar crashes
// (not available - use call stack signature)
}
MXHangDiagnostic
@available(iOS 14.0, *)
func processHang(_ diagnostic: MXHangDiagnostic) {
// How long the hang lasted
let duration = diagnostic.hangDuration // Measurement<UnitDuration>
// Call stack when hang occurred
let callStackTree = diagnostic.callStackTree
}
MXDiskWriteExceptionDiagnostic
@available(iOS 14.0, *)
func processDiskWriteException(_ diagnostic: MXDiskWriteExceptionDiagnostic) {
// Total bytes written that triggered exception
let totalWrites = diagnostic.totalWritesCaused // Measurement<UnitInformationStorage>
// Call stack of writes
let callStackTree = diagnostic.callStackTree
}
MXCPUExceptionDiagnostic
@available(iOS 14.0, *)
func processCPUException(_ diagnostic: MXCPUExceptionDiagnostic) {
// Total CPU time that triggered exception
let totalCPUTime = diagnostic.totalCPUTime // Measurement<UnitDuration>
// Total sampled time
let totalSampledTime = diagnostic.totalSampledTime
// Call stack of CPU-intensive code
let callStackTree = diagnostic.callStackTree
}
パート 4:MXCallStackTree
MXCallStackTree には、診断データのスタックフレームが含まれます。フレームはシンボリケーションされていないため、dSYM を使用してシンボリケーションする必要があります。
構造
@available(iOS 14.0, *)
func parseCallStackTree(_ tree: MXCallStackTree) {
// JSON representation
let jsonData = tree.jsonRepresentation()
// Parse the JSON
guard let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
let callStacks = json["callStacks"] as? [[String: Any]] else {
return
}
for callStack in callStacks {
guard let threadAttributed = callStack["threadAttributed"] as? Bool,
let frames = callStack["callStackRootFrames"] as? [[String: Any]] else {
continue
}
// threadAttributed = true means this thread caused the issue
if threadAttributed {
parseFrames(frames)
}
}
}
func parseFrames(_ frames: [[String: Any]]) {
for frame in frames {
// Binary image UUID (match to dSYM)
let binaryUUID = frame["binaryUUID"] as? String
// Address offset within binary
let offsetIntoBinaryTextSegment = frame["offsetIntoBinaryTextSegment"] as? Int
// Binary name (e.g., "MyApp", "UIKitCore")
let binaryName = frame["binaryName"] as? String
// Address (for symbolication)
let address = frame["address"] as? Int
// Sample count (how many times this frame appeared)
let sampleCount = frame["sampleCount"] as? Int
// Sub-frames (tree structure)
let subFrames = frame["subFrames"] as? [[String: Any]]
}
}
JSON 構造の例
{
"callStacks": [
{
"threadAttributed": true,
"callStackRootFrames": [
{
"binaryUUID": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
"offsetIntoBinaryTextSegment": 123456,
"binaryName": "MyApp",
"address": 4384712345,
"sampleCount": 10,
"subFrames": [
{
"binaryUUID": "F1E2D3C4-B5A6-7890-1234-567890ABCDEF",
"offsetIntoBinaryTextSegment": 78901,
"binaryName": "UIKitCore",
"address": 7234567890,
"sampleCount": 10
}
]
}
]
}
]
}
シンボリケーション
MetricKit のコールスタックはシンボリケーションされていません。シンボリケーションするには、次の手順に従います。
- App Store 向けの各ビルドの dSYM ファイルを保管します
binaryUUIDの UUID を dSYM と照合します- atos を使用してシンボリケーションします。
# Find dSYM for binary UUID
mdfind "com_apple_xcode_dsym_uuids == A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
# Symbolicate address
atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x100000000 0x105234567
または、シンボリケーションに対応したクラッシュレポートサービス(Crashlytics、Sentry など)を使用します。
パート 5:MXBackgroundExitData
アプリがバックグラウンドで終了された理由を追跡します。
@available(iOS 14.0, *)
func analyzeBackgroundExits(_ data: MXBackgroundExitData) {
// Normal exits (user closed, system reclaimed)
let normal = data.cumulativeNormalAppExitCount
// Memory issues
let memoryLimit = data.cumulativeMemoryResourceLimitExitCount // Exceeded memory limit
let memoryPressure = data.cumulativeMemoryPressureExitCount // Jetsam
// Crashes
let badAccess = data.cumulativeBadAccessExitCount // SIGSEGV
let illegalInstruction = data.cumulativeIllegalInstructionExitCount // SIGILL
let abnormal = data.cumulativeAbnormalExitCount // Other crashes
// System terminations
let watchdog = data.cumulativeAppWatchdogExitCount // Timeout during transition
let taskTimeout = data.cumulativeBackgroundTaskAssertionTimeoutExitCount // Background task timeout
let cpuLimit = data.cumulativeCPUResourceLimitExitCount // Exceeded CPU quota
let lockedFile = data.cumulativeSuspendedWithLockedFileExitCount // File lock held
}
終了タイプの解釈
| 終了タイプ | 意味 | 対応 |
|---|---|---|
normalAppExitCount | 正常終了 | なし(想定どおり) |
memoryResourceLimitExitCount | メモリを使いすぎました | フットプリントを削減します |
memoryPressureExitCount | Jetsam(システムによるメモリ回収) | バックグラウンドのメモリ使用量を50MB未満に削減します |
badAccessExitCount | SIGSEGV クラッシュ | null ポインターや無効なメモリを確認します |
illegalInstructionExitCount | SIGILL クラッシュ | 無効な関数ポインターを確認します |
abnormalExitCount | その他のクラッシュ | クラッシュ診断情報を確認します |
appWatchdogExitCount | 状態遷移中に応答しなくなりました | 起動時やバックグラウンドでの処理を減らします |
backgroundTaskAssertionTimeoutExitCount | バックグラウンドタスクを終了しませんでした | endBackgroundTask を適切に呼び出します |
cpuResourceLimitExitCount | バックグラウンドで CPU を使いすぎました | BGProcessingTask に移行します |
suspendedWithLockedFileExitCount | ファイルをロックしたままサスペンドしました | サスペンド前にロックを解除します |
パート 6:統合パターン
アナリティクスサービスへのアップロード
class MetricsUploader {
func upload(_ payload: MXMetricPayload) {
let jsonData = payload.jsonRepresentation()
var request = URLRequest(url: analyticsEndpoint)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
URLSession.shared.dataTask(with: request) { _, response, error in
if let error = error {
// Queue for retry
self.queueForRetry(jsonData)
}
}.resume()
}
}
クラッシュレポーターとの連携
class HybridCrashReporter: MXMetricManagerSubscriber {
let crashlytics: Crashlytics // or Sentry, etc.
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
// MetricKit captures crashes that traditional reporters might miss
// (e.g., watchdog kills, memory pressure exits)
if let crashes = payload.crashDiagnostics {
for crash in crashes {
crashlytics.recordException(
name: crash.exceptionType?.description ?? "Unknown",
reason: crash.terminationReason ?? "MetricKit crash",
callStack: parseCallStack(crash.callStackTree)
)
}
}
}
}
}
リグレッションのアラート
class MetricsMonitor: MXMetricManagerSubscriber {
let thresholds = MetricThresholds(
launchTime: 2.0, // seconds
hangRate: 0.01, // 1% of sessions
memoryPeak: 200 // MB
)
func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
checkThresholds(payload)
}
}
private func checkThresholds(_ payload: MXMetricPayload) {
// Check launch time
if let launches = payload.applicationLaunchMetrics {
let p50 = calculateP50(launches.histogrammedTimeToFirstDraw)
if p50 > thresholds.launchTime {
sendAlert("Launch time regression: \(p50)s > \(thresholds.launchTime)s")
}
}
// Check memory
if let memory = payload.memoryMetrics {
let peakMB = memory.peakMemoryUsage.converted(to: .megabytes).value
if peakMB > Double(thresholds.memoryPeak) {
sendAlert("Memory peak regression: \(peakMB)MB > \(thresholds.memoryPeak)MB")
}
}
}
}
パート 7:ベストプラクティス
推奨事項
- サブスクライバーを早期に登録する —
application(_:didFinishLaunchingWithOptions:)または App の初期化時に登録します - dSYM ファイルを保持する — コールスタックのシンボリケーションに必要です
- ペイロードをサーバーにアップロードする — ローカルで処理すると、アンインストール時にデータが失われます
- アラートを設定する — ユーザーから報告される前にリグレッションを検出します
- シミュレートしたペイロードでテストする — iOS 15 以降では Xcode の「Debug」メニューを使用します
禁止事項
- MetricKit だけに頼らない — 24 時間の遅延があり、ユーザーによるオプトインが必要です
- バックグラウンドでの終了を無視しない — Jetsam やタスクのタイムアウトは UX に影響します
- シンボリケーションを省略しない — 生のアドレスは使用できません
- メインスレッドで処理しない — ペイロードの処理には大きな負荷がかかることがあります
プライバシーに関する考慮事項
- MetricKit のデータは集計され、匿名化されています
- データは、アナリティクスの共有にオプトインしたユーザーからのみ取得されます
- 個人を特定できる情報は含まれません
- サーバーへのアップロードは安全です
パート 8:MetricKit と Xcode Organizer の比較
| 機能 | MetricKit | Xcode Organizer |
|---|---|---|
| データソース | アプリを実行しているデバイス | App Store Connect の集計データ |
| 配信 | サブスクライバーに毎日配信 | Xcode でオンデマンド |
| カスタマイズ | 生データへのフルアクセス | 事前定義されたビュー |
| シンボリケーション | 自分でシンボリケーションする必要があります | シンボリケーション済み |
| 履歴データ | サブスクライバーが有効な期間のみ | 直近16バージョン |
| コードが必要 | はい | いいえ |
両方を使いましょう:概要をすばやく把握するには Organizer、カスタムアナリティクスやアラートには MetricKit を使用します。
リソース
WWDC:2019-417、2020-10081、2021-10087
ドキュメント:/metrickit、/metrickit/mxmetricmanager、/metrickit/mxdiagnosticpayload
スキル:axiom-hang-diagnostics、axiom-performance-profiling、axiom-testflight-triage