Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
MetricKitで予期せぬ終了を検知する話 / Detect unexpected term...
Search
nekowen
April 18, 2024
Programming
1
1k
MetricKitで予期せぬ終了を検知する話 / Detect unexpected termination with MetricKit
Ebisu.mobile #5〜モバイルアプリの品質改善どうしてる?〜
https://hey.connpass.com/event/313395/
nekowen
April 18, 2024
Tweet
Share
More Decks by nekowen
See All by nekowen
アプリ起動時間を80%高速化した話 / Talk about 80% faster app startup time.
nekowen
0
160
健康第一!MetricKitで始めるアプリの健康診断 / App Health Checkups Starting with MetricKit
nekowen
5
5.8k
5分で理解するAccessorySetupKit / Understanding AccessorySetupKit in 5 minutes
nekowen
0
400
SwiftUI/Jetpack Composeを採用してよかったこと悪かったこと
nekowen
2
1.5k
iOS13向けに位置情報周りを対応しようとしたら苦労した話
nekowen
1
490
Other Decks in Programming
See All in Programming
Understanding Kotlin Multiplatform
l2hyunwoo
0
110
可変性を制する設計: 構造と振る舞いから考える概念モデリングとその実装
a_suenami
2
480
なぜあなたのオブザーバビリティ導入は頓挫するのか
ryota_hnk
1
370
Caude codeで爆速開発
codelynx
0
100
SQLアンチパターン第2版 データベースプログラミングで陥りがちな失敗とその対策 / Intro to SQL Antipatterns 2nd
twada
PRO
27
8k
プロダクトという一杯を作る - プロダクトチームが味の責任を持つまでの煮込み奮闘記
hiliteeternal
0
120
What's new in AppKit on macOS 26
1024jp
0
170
Claude Code で Astro blog を Pages から Workers へ移行してみた
codehex
0
150
リバースエンジニアリング新時代へ! GhidraとClaude DesktopをMCPで繋ぐ/findy202507
tkmru
4
1.2k
抽象化という思考のツール - 理解と活用 - / Abstraction-as-a-Tool-for-Thinking
shin1x1
1
580
効率的な開発手段として VRTを活用する
ishkawa
1
180
Android 16KBページサイズ対応をはじめからていねいに
mine2424
0
650
Featured
See All Featured
How to train your dragon (web standard)
notwaldorf
96
6.1k
Build The Right Thing And Hit Your Dates
maggiecrowley
37
2.8k
KATA
mclloyd
30
14k
GraphQLとの向き合い方2022年版
quramy
49
14k
YesSQL, Process and Tooling at Scale
rocio
173
14k
Build your cross-platform service in a week with App Engine
jlugia
231
18k
How STYLIGHT went responsive
nonsquared
100
5.6k
The Art of Programming - Codeland 2020
erikaheidi
54
13k
A Tale of Four Properties
chriscoyier
160
23k
The World Runs on Bad Software
bkeepers
PRO
70
11k
It's Worth the Effort
3n
185
28k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.4k
Transcript
STORES 株式会社 @nekowen Ebisu.mobile #5〜モバイルアプリの品質改善どうしてる?〜 2024年4月19日(金) 12:00 - 13:00 MetricKitで予期せぬ終了を
検知する話
自己紹介 Ryoya Kobitsu / @nekowen STORESレジ・予約開発グループ WWDC24に参加します(ドキドキ) X: @n3k0w3n GitHub:
@nekowen 2
3 ある日の出来事…
ある日の出来事… 4 QAチームから 「1日に数回アプリが突然クラッシュする」 という報告をもらう
5 何が起きていたか
何が起きていたか 6 - Crashlytics, TestFlightにレポートが上がっていなかった🤔 - システムによって強制終了された可能性 - 問題が起きた端末でJetsamイベントが記録されていた -
メモリの使いすぎでアプリが強制終了されていた - → 特定の条件でメモリを消費し続けるバグが見つかった
何が起きていたか 7 - watchdogに引っかかった - 無効なコード署名 - CPUへ負荷をかけオーバー ヒートした -
大量にメモリを使用して Jetsamイベントを引き起こ した 引用 https://developer.apple.com/documentation/xcode/acquiring-crash-reports-and-d iagnostic-logs#Locate-crash-reports-and-memory-logs-on-the-device
何が起きていたか 8 - watchdogに引っかかった - 無効なコード署名 - CPUへ負荷をかけオーバー ヒートした -
大量にメモリを使用して Jetsamイベントを引き起こ した 引用 https://developer.apple.com/documentation/xcode/acquiring-crash-reports-and-d iagnostic-logs#Locate-crash-reports-and-memory-logs-on-the-device
何が起きていたか 9 - Crashlytics, TestFlightにレポートが上がっていなかった🤔 - システムによって強制終了された可能性 - 問題が起きた端末でJetsamイベントが記録されていた -
メモリの使いすぎでアプリが強制終了されていた - → 特定の条件でメモリを消費し続けるバグが見つかった
何が起きていたか 10 - Crashlytics, TestFlightにレポートが上がっていなかった🤔 - システムによって強制終了された可能性 - 問題が起きた端末でJetsamイベントが記録されていた -
メモリの使いすぎでアプリが強制終了されていた - → 特定の条件でメモリを消費し続けるバグが見つかった🐛
何が起きていたか 11 - Crashlytics, TestFlightにレポートが上がっていなかった🤔 - システムによって強制終了された可能性 - 問題が起きた端末でJetsamイベントが記録されていた -
メモリの使いすぎでアプリが強制終了されていた - → 特定の条件でメモリを消費し続けるバグが見つかった 何らかの方法で検知できないか?🤔
12 MetricKitとは何か
MetricKitとは何か 13 - システムがキャプチャしたアプリの メトリクスや診断レポートをデバイ ス上で受け取れるフレームワーク - Xcode Organizerより詳細なレポー トが得られる
- iOS13から提供されている 引用 https://developer.apple.com/videos/play/wwdc 2020/10081
MetricKitとは何か 14 エネルギー効率 パフォーマンス 応答性 ディスクアクセス クラッシュレポート 応答性 ディスクアクセス メトリクス
診断データ
MetricKitとは何か - 全体で得られる情報 15 エネルギー効率 パフォーマンス 応答性 ディスクアクセス クラッシュレポート 応答性
ディスクアクセス メトリクス 診断データ アプリの起動時間 CPU使用率 メモリ使用量 位置情報取得に使った時間 ネットワーク通信量etc… クラッシュ全般 アプリの起動時間 アプリのハングetc…
MetricKitとは何か - 配信されるタイミング 16 エネルギー効率 パフォーマンス 応答性 ディスクアクセス クラッシュレポート 応答性
ディスクアクセス メトリクス 診断データ iOS15 / macOS12以降の場合 レポートがあれば即時配信される 1日に最大1回アプリに対し てレポートが配信される
MetricKitとは何か - メトリクス/パフォーマンスで得られる情報 17 エネルギー効率 パフォーマンス 応答性 ディスクアクセス クラッシュレポート 応答性
ディスクアクセス メトリクス 診断データ アプリ起動にかかった時間 アクティブ時間 アプリ終了回数・メモリ使用量
MetricKitとは何か - メトリクス/パフォーマンスで得られる情報 18 エネルギー効率 パフォーマンス 応答性 ディスクアクセス クラッシュレポート 応答性
ディスクアクセス メトリクス 診断データ アプリ起動にかかった時間 アクティブ時間 アプリ終了回数・メモリ使用量 回数の変化を追っていけば 異常が起きていることがわかりそう👀
19 MXAppExitMetricから アプリの終了回数を知る
MXAppExitMetricからアプリの終了回数を知る 20 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount
MXAppExitMetricからアプリの終了回数を知る 21 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount 正常・異常終了の累積数
MXAppExitMetricからアプリの終了回数を知る 22 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount システムによって 強制終了された累積数
MXAppExitMetricからアプリの終了回数を知る 23 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount クラッシュの累積数
MXAppExitMetricからアプリの終了回数を知る 24 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount BackgroundTaskのタイムアウトによって 終了させられた累積数
MXAppExitMetricからアプリの終了回数を知る 25 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount メモリに関するシステム終了の 累積カウント ※分析したい数値
26 実装してみよう
実装してみよう 27 class AppMetrics: NSObject { let shared = MXMetricManager.shared
func startReceiveReport() { shared.add(self) } func pauseReceiveReport() { shared.remove(self) } }
実装してみよう 28 class AppMetrics: NSObject { let shared = MXMetricManager.shared
func startReceiveReport() { shared.add(self) } func pauseReceiveReport() { shared.remove(self) } } MXMetricManagerの共有インスタンスに 通知先のオブジェクトをaddする
実装してみよう 29 class AppMetrics: NSObject { let shared = MXMetricManager.shared
func startReceiveReport() { shared.add(self) } func pauseReceiveReport() { shared.remove(self) } } 通知を止めたいときは removeメソッドを呼ぶ
実装してみよう 30 extension AppMetrics: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload])
{ payloads.forEach { if let applicationExitMetrics = $0.applicationExitMetrics { // バックグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryResourceLimitExitCount) logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryPressureExitCount) // フォアグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.foregroundExitData.cumulativeMemoryResourceLimitExitCount) } // まるっとPayloadをサーバーに送りたい場合 sendPayloadToServer($0.jsonRepresentation()) } } func didReceive(_ payloads: [MXDiagnosticPayload]) { payloads.forEach { sendPayloadToServer($0.jsonRepresentation()) } } }
実装してみよう 31 extension AppMetrics: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload])
{ payloads.forEach { if let applicationExitMetrics = $0.applicationExitMetrics { // バックグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryResourceLimitExitCount) logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryPressureExitCount) // フォアグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.foregroundExitData.cumulativeMemoryResourceLimitExitCount) } // まるっとPayloadをサーバーに送りたい場合 sendPayloadToServer($0.jsonRepresentation()) } } func didReceive(_ payloads: [MXDiagnosticPayload]) { payloads.forEach { sendPayloadToServer($0.jsonRepresentation()) } } }
実装してみよう 32 extension AppMetrics: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload])
{ payloads.forEach { if let applicationExitMetrics = $0.applicationExitMetrics { // バックグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryResourceLimitExitCount) logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryPressureExitCount) // フォアグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.foregroundExitData.cumulativeMemoryResourceLimitExitCount) } // まるっとPayloadをサーバーに送りたいとき sendPayloadToServer($0.jsonRepresentation()) } } func didReceive(_ payloads: [MXDiagnosticPayload]) { payloads.forEach { sendPayloadToServer($0.jsonRepresentation()) } } } メトリクスを受け取るときは func didReceive([MXMetricPayload]) を定義する
実装してみよう 33 extension AppMetrics: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload])
{ payloads.forEach { if let applicationExitMetrics = $0.applicationExitMetrics { // バックグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryResourceLimitExitCount) logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryPressureExitCount) // フォアグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.foregroundExitData.cumulativeMemoryResourceLimitExitCount) } // まるっとPayloadをサーバーに送りたいとき sendPayloadToServer($0.jsonRepresentation()) } } func didReceive(_ payloads: [MXDiagnosticPayload]) { payloads.forEach { sendPayloadToServer($0.jsonRepresentation()) } } } 診断データを受け取るときは func didReceive([MXDiagnosticPayload]) を定義する
実装してみよう 34 extension AppMetrics: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload])
{ payloads.forEach { if let applicationExitMetrics = $0.applicationExitMetrics { // バックグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryResourceLimitExitCount) logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryPressureExitCount) // フォアグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.foregroundExitData.cumulativeMemoryResourceLimitExitCount) } // まるっとPayloadをサーバーに送りたいとき sendPayloadToServer($0.jsonRepresentation()) } } func didReceive(_ payloads: [MXDiagnosticPayload]) { payloads.forEach { sendPayloadToServer($0.jsonRepresentation()) } } } PayloadをJSONに変換するメソッドが用意されて いるので、まるっとサーバーに送る場合は使う
まとめ 35 - MetricKitを使うことでアプリの健康状態をレポートベースで 受け取れて独自に分析ができます - MXAppExitMetricを見ることで検知しづらいシステムによる 強制終了の詳細を数値で追える - 改善やっていきましょう!
36 ご清聴ありがとうございました