mcpField
A
axiom-metrickit-ref

axiom-metrickit-ref

github.com/charleswiltgen開発1.2k163 回インストール今日更新
リポジトリ

axiom-metrickit-ref について

MetricKit で実利用環境のパフォーマンス指標と診断情報を収集する iOS 開発者向けの API リファレンスです。ペイロード解析、コールスタックのシンボリケーション、バックグラウンド終了理由などを扱います。

主な機能

  1. 01MetricKit サブスクライバーの設定
  2. 02パフォーマンスペイロードの解析
  3. 03クラッシュとハングの診断
  4. 04コールスタックのシンボリケーション
  5. 05バックグラウンド終了理由の分析
  6. 06ペイロードのエクスポートと送信例

活用シーン

  1. 01MetricKit データ購読の導入
  2. 02CPU・メモリ・起動指標の確認
  3. 03クラッシュ、ハング、Jetsam の調査
  4. 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 を参照してください。

よくある注意点

  1. 24 時間の遅延 — MetricKit のデータは 1 日に 1 回届き、リアルタイムのデバッグには使えません
  2. コールスタックにはシンボリケーションが必要 — MXCallStackTree のフレームはシンボリケーションされていないため、dSYM を保管してください
  3. オプトインのみ — 「App デベロッパと共有」を有効にしたユーザーのみがデータを提供します
  4. 個別ではなく集計データ — ユーザーごとのトレースではなく、件数と平均値が取得されます
  5. シミュレーターでは動作しません — 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 のコールスタックはシンボリケーションされていません。シンボリケーションするには、次の手順に従います。

  1. App Store 向けの各ビルドの dSYM ファイルを保管します
  2. binaryUUID の UUID を dSYM と照合します
  3. 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メモリを使いすぎましたフットプリントを削減します
memoryPressureExitCountJetsam(システムによるメモリ回収)バックグラウンドのメモリ使用量を50MB未満に削減します
badAccessExitCountSIGSEGV クラッシュnull ポインターや無効なメモリを確認します
illegalInstructionExitCountSIGILL クラッシュ無効な関数ポインターを確認します
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 の比較

機能MetricKitXcode 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

コメント