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 termination with MetricKit
Search
nekowen
April 18, 2024
Programming
1
200
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
SwiftUI/Jetpack Composeを採用してよかったこと悪かったこと
nekowen
2
1.3k
iOS13向けに位置情報周りを対応しようとしたら苦労した話
nekowen
1
420
Other Decks in Programming
See All in Programming
Folding Cheat Sheet #4
philipschwarz
PRO
0
110
Amazon SQSコンシューマー疎結合への旅 - 出張! #DevelopersIO IT技術ブログの中の人が語る勉強会 #3
quiver
0
370
Ruby on Fails - effective error handling with Rails conventions
talyssonoc
0
250
TSKaigi 2024 - 新サービス Progate Path の演習で TypeScript を採用して見えた教材観点からの利点と課題
makotoshimazu
1
180
RailsConf 2024: Riffing on Rails: sketch your way to better designed code
kaspth
0
190
Jetpack Composeとデザインシステム
rmakiyama
0
180
Apache Hive 4 on Treasure Data
ryukobayashi
1
470
TypeScript 関数型スタイルでバックエンド開発のリアル
naoya
48
15k
Exploring the Implementation of “t.Run”, “t.Parallel”, and “t.Cleanup”
akarin
1
150
CQRS meets modern Java
simas
PRO
2
320
Productivity is Messing Around and Having Fun
hollycummins
1
160
Goのエラースタックトレースの歴史と今後
sonatard
10
2k
Featured
See All Featured
Done Done
chrislema
178
15k
Rails Girls Zürich Keynote
gr2m
91
13k
Building Better People: How to give real-time feedback that sticks.
wjessup
356
18k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
245
20k
Rebuilding a faster, lazier Slack
samanthasiow
74
8.3k
Into the Great Unknown - MozCon
thekraken
15
1.1k
Producing Creativity
orderedlist
PRO
338
39k
How To Stay Up To Date on Web Technology
chriscoyier
782
250k
What the flash - Photography Introduction
edds
64
11k
Build The Right Thing And Hit Your Dates
maggiecrowley
25
2k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
358
22k
Scaling GitHub
holman
457
140k
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 ご清聴ありがとうございました