Upgrade to Pro — share decks privately, control downloads, hide ads and more …

健康第一!MetricKitで始めるアプリの健康診断 / App Health Checku...

nekowen
August 22, 2024

健康第一!MetricKitで始めるアプリの健康診断 / App Health Checkups Starting with MetricKit

iOSDC Japan 2024 Day0 Track B 18:10 -

nekowen

August 22, 2024
Tweet

More Decks by nekowen

Other Decks in Programming

Transcript

  1. ΞϓϦύϑΥʔϚϯεͱ͸ ओʹύϑΥʔϚϯεࢦඪͱͳΔͷ͸͜ͷͭ w Ԡ౴ੑ w ΞϓϦͷىಈ࣌ؒ w ϝϞϦͷ࢖༻ྔ w όοςϦʔޮ཰

    ࢀߟ4VSWJWBMHVJEFUPBQQMJDBUJPOQFSGPSNBODF IUUQTEFWFMPQFSBQQMFDPNKQWJEFPTQMBZXXED
  2. 9DPEF0SHBOJ[FS .FUSJD,JU "1* 'JSFCBTF1FSGPSNBODF .POJUPSJOH ಋೖͷखؒ ಋೖෆཁ .FUSJD,JUͷJNQPSU ؆୯ͳ࣮૷ 4%,ͷ௥Ճ

    ॳظԽ ΧελϚΠζ ͳ͠ 3FHSFTTJPOTͷΈ௨஌Մ ؂ࢹαʔϏε΍"1*ʹґଘ ࢦඪ͓ΑͼΞϥʔτͷઃఆ͕Մೳ "1*෼ੳج൫ͷ४උ ෆཁ ඞཁ ෆཁ 5FTU'MJHIUͰ ར༻Մೳ͔ Ͱ͖ͳ͍ Ͱ͖Δ Ͱ͖Δ ऩूͰ͖Δ৘ใ ΞϓϦͷىಈ࣌ؒ Ԡ౴ੑ ϝϞϦ΍όοςϦʔޮ཰ ͳͲ ΞϓϦͷىಈ࣌ؒ Ԡ౴ੑ ϝϞϦ΍όοςϦʔޮ཰ ΧελϜΠϕϯτ ͳͲ ΞϓϦͷىಈ࣌ؒ ը໘ͷϨϯμϦϯάύϑΥʔϚϯε )5514௨৴ͷϦΫΤετσʔλ ΧελϜΠϕϯτ
  3. ϝτϦΫεΛड͚औΔ class AppDelegate: NSObject, UIApplicationDelegate, MXMetricManagerSubscriber { func application(_ application:

    UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { MXMetricManager.shared.add(self) return true } func didReceive(_ payloads: [MXMetricPayload]) { } func didReceive(_ payloads: [MXDiagnosticPayload]) { } }
  4. ϝτϦΫεΛड͚औΔ class AppDelegate: NSObject, UIApplicationDelegate, MXMetricManagerSubscriber { func application(_ application:

    UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { MXMetricManager.shared.add(self) return true } func didReceive(_ payloads: [MXMetricPayload]) { } func didReceive(_ payloads: [MXDiagnosticPayload]) { } } .9.FUSJD.BOBHFS4VCTDSJCFSʹ४ڌ͢Δ
  5. ϝτϦΫεΛड͚औΔ class AppDelegate: NSObject, UIApplicationDelegate, MXMetricManagerSubscriber { func application(_ application:

    UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { MXMetricManager.shared.add(self) return true } func didReceive(_ payloads: [MXMetricPayload]) { } func didReceive(_ payloads: [MXDiagnosticPayload]) { } } EJE3FDFJWFϝιουΛఆٛ
  6. ϝτϦΫεΛड͚औΔ class AppDelegate: NSObject, UIApplicationDelegate, MXMetricManagerSubscriber { func application(_ application:

    UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { MXMetricManager.shared.add(self) return true } func didReceive(_ payloads: [MXMetricPayload]) { } func didReceive(_ payloads: [MXDiagnosticPayload]) { } } ϝτϦΫε͕४උͰ͖ͨͱ͖ʹݺ͹ΕΔ
  7. ϝτϦΫεΛड͚औΔ class AppDelegate: NSObject, UIApplicationDelegate, MXMetricManagerSubscriber { func application(_ application:

    UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { MXMetricManager.shared.add(self) return true } func didReceive(_ payloads: [MXMetricPayload]) { } func didReceive(_ payloads: [MXDiagnosticPayload]) { } } ਍அσʔλ͕४උͰ͖ͨͱ͖ʹݺ͹ΕΔ
  8. ϝτϦΫεΛड͚औΔ class AppDelegate: NSObject, UIApplicationDelegate, MXMetricManagerSubscriber { func application(_ application:

    UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { MXMetricManager.shared.add(self) return true } func didReceive(_ payloads: [MXMetricPayload]) { payloads.forEach { printMetricPayload($0) } } func didReceive(_ payloads: [MXDiagnosticPayload]) { payloads.forEach { printDiagnosticPayload($0) } } } .9.FUSJD.BOBHFSBEE @ ΛݺͿ
  9. ൃੜස౓      ىಈ࣌ؒ NT  

     { "applicationLaunchMetrics" : { "histogrammedTimeToFirstDrawKey" : { "histogramNumBuckets" : 3, "histogramValue" : { "0" : { "bucketCount" : 5, "bucketStart" : "80 ms", "bucketEnd" : "89 ms" }, "1" : { "bucketCount" : 2, "bucketStart" : "150 ms", "bucketEnd" : "159 ms" }, "2" : { "bucketCount" : 1, "bucketStart" : "6990 ms", "bucketEnd" : "6999 ms" } } } } } ࠾औͨ͠σʔλ άϥϑԽͨ͠΋ͷ
  10. { "applicationLaunchMetrics" : { "histogrammedTimeToFirstDrawKey" : { "histogramNumBuckets" : 3,

    "histogramValue" : { "0" : { "bucketCount" : 5, "bucketStart" : "80 ms", "bucketEnd" : "89 ms" }, "1" : { "bucketCount" : 2, "bucketStart" : "150 ms", "bucketEnd" : "159 ms" }, "2" : { "bucketCount" : 1, "bucketStart" : "6990 ms", "bucketEnd" : "6999 ms" } } } } } ࠾औͨ͠σʔλ άϥϑԽͨ͠΋ͷ ൃੜස౓      ىಈ࣌ؒ NT   
  11. { "applicationLaunchMetrics" : { "histogrammedTimeToFirstDrawKey" : { "histogramNumBuckets" : 3,

    "histogramValue" : { "0" : { "bucketCount" : 5, "bucketStart" : "80 ms", "bucketEnd" : "89 ms" }, "1" : { "bucketCount" : 2, "bucketStart" : "150 ms", "bucketEnd" : "159 ms" }, "2" : { "bucketCount" : 1, "bucketStart" : "6990 ms", "bucketEnd" : "6999 ms" } } } } } ࠾औͨ͠σʔλ άϥϑԽͨ͠΋ͷ ൃੜස౓      ىಈ࣌ؒ NT   
  12. Ҿ༻3FEVDJOHUFSNJOBUJPOTJOZPVSBQQ IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOYDPEFSFEVDFUFSNJOBUJPOTJOZPVSBQQ6OEFSTUBOEUFSNJOBUJPOSFBTPOT ཧ༝ ݪҼ Ϋϥογϡ ηάϝϯςʔγϣϯҧ൓ɾෆਖ਼ͳ໋ྩɾ BTTFSU͓ΑͼΩϟον͞Ε͍ͯͳ͍ྫ֎͕ൃੜͨ͠ 8BUDIEPHʹΑΔ΋ͷ ىಈ͓ΑͼόοΫάϥ΢ϯυɾϑΥΞάϥ΢ϯυ ͷભҠॲཧʹ͕͔͔࣌ؒΓ͗ͨ͢

    $16࢖༻੍ݶ $16ෛՙ͕ߴ͍ঢ়ଶ͕ଓ͍ͨ ϝϞϦ࢖༻੍ݶ ϝϞϦ࢖༻ྔ͕γεςϜͷ੍ݶ஋Λ௒͑ͨ ϝϞϦෆ଍ +FUTBN γεςϜ͕ΑΓଟ͘ͷϝϞϦྖҬΛඞཁͱ͢ΔͨΊ όοΫάϥ΢ϯυͷΞϓϦΛऴྃͨ͠ #BDLHSPVOE5BTL ͷλΠϜΞ΢τ #BDLHSPVOE5BTLʹׂΓ౰ͯΒΕ͍ͯΔ࣮ߦ࣌ؒΛ௒͑ͨ ϑΝΠϧϩοΫ "QQ(SPVQTͷڞ༗ϑΝΠϧͷϩοΫΛอ࣋͠ଓ͚ͨ
  13. ཧ༝ ϓϩύςΟ Ϋϥογϡ DVNVMBUJWF#BE"DDFTT&YJU$PVOU DVNVMBUJWF*MMFHBM*OTUSVDUJPO&YJU$PVOU 8BUDIEPHʹΑΔ΋ͷ DVNVMBUJWF"QQ8BUDIEPH&YJU$PVOU $16࢖༻੍ݶ DVNVMBUJWF$163FTPVSDF-JNJU&YJU$PVOU ϝϞϦ࢖༻੍ݶ

    DVNVMBUJWF.FNPSZ3FTPVSDF-JNJU&YJU$PVOU ϝϞϦෆ଍ +FUTBN DVNVMBUJWF.FNPSZ1SFTTVSF&YJU$PVOU #BDLHSPVOE5BTL ͷλΠϜΞ΢τ DVNVMBUJWF#BDLHSPVOE5BTL"TTFSUJPO5JNFPVU&YJU$PVOU ϑΝΠϧϩοΫ DVNVMBUJWF4VTQFOEFE8JUI-PDLFE'JMF&YJU$PVOU
  14. ཧ༝ ϓϩύςΟ Ϋϥογϡ DVNVMBUJWF#BE"DDFTT&YJU$PVOU DVNVMBUJWF*MMFHBM*OTUSVDUJPO&YJU$PVOU 8BUDIEPHʹΑΔ΋ͷ DVNVMBUJWF"QQ8BUDIEPH&YJU$PVOU $16࢖༻੍ݶ DVNVMBUJWF$163FTPVSDF-JNJU&YJU$PVOU ϝϞϦ࢖༻੍ݶ

    DVNVMBUJWF.FNPSZ3FTPVSDF-JNJU&YJU$PVOU ϝϞϦෆ଍ +FUTBN DVNVMBUJWF.FNPSZ1SFTTVSF&YJU$PVOU #BDLHSPVOE5BTL ͷλΠϜΞ΢τ DVNVMBUJWF#BDLHSPVOE5BTL"TTFSUJPO5JNFPVU&YJU$PVOU ϑΝΠϧϩοΫ DVNVMBUJWF4VTQFOEFE8JUI-PDLFE'JMF&YJU$PVOU
  15.     ϋϯά࣌ؒ NT ʙ   

       ࠾औͨ͠σʔλ άϥϑԽͨ͠΋ͷ { "applicationResponsivenessMetrics" : { "histogrammedAppHangTime" : { "histogramNumBuckets" : 44, "histogramValue" : { "3" : { "bucketCount" : 2, "bucketStart" : "310 ms", "bucketEnd" : "319 ms" }, "12" : { "bucketCount" : 1, "bucketStart" : "470 ms", "bucketEnd" : "479 ms" }, "21" : { "bucketCount" : 1, "bucketStart" : "900 ms", "bucketEnd" : "909 ms" }, ... } } } }
  16.     ϋϯά࣌ؒ NT ʙ   

       ࠾औͨ͠σʔλ άϥϑԽͨ͠΋ͷ { "applicationResponsivenessMetrics" : { "histogrammedAppHangTime" : { "histogramNumBuckets" : 44, "histogramValue" : { "3" : { "bucketCount" : 2, "bucketStart" : "310 ms", "bucketEnd" : "319 ms" }, "12" : { "bucketCount" : 1, "bucketStart" : "470 ms", "bucketEnd" : "479 ms" }, "21" : { "bucketCount" : 1, "bucketStart" : "900 ms", "bucketEnd" : "909 ms" }, ... } } } }
  17. ࠶ܝ ཧ༝ ݪҼ Ϋϥογϡ ηάϝϯςʔγϣϯҧ൓ɾෆਖ਼ͳ໋ྩɾ BTTFSU͓ΑͼΩϟον͞Ε͍ͯͳ͍ྫ֎͕ൃੜͨ͠ 8BUDIEPHʹΑΔ΋ͷ ىಈ͓ΑͼόοΫάϥ΢ϯυɾϑΥΞάϥ΢ϯυ ͷભҠॲཧʹ͕͔͔࣌ؒΓ͗ͨ͢ $16࢖༻੍ݶ

    $16ෛՙ͕ߴ͍ঢ়ଶ͕ଓ͍ͨ ϝϞϦ࢖༻੍ݶ ϝϞϦ࢖༻ྔ͕γεςϜͷ੍ݶ஋Λ௒͑ͨ ϝϞϦෆ଍ +FUTBN γεςϜ͕ΑΓଟ͘ͷϝϞϦྖҬΛඞཁͱ͢ΔͨΊ όοΫάϥ΢ϯυͷΞϓϦΛऴྃͨ͠ #BDLHSPVOE5BTL ͷλΠϜΞ΢τ #BDLHSPVOE5BTLʹׂΓ౰ͯΒΕ͍ͯΔ࣮ߦ࣌ؒΛ௒͑ͨ ϑΝΠϧϩοΫ "QQ(SPVQTͷڞ༗ϑΝΠϧͷϩοΫΛอ࣋͠ଓ͚ͨ
  18. ࠓճͷݪҼ ཧ༝ ݪҼ Ϋϥογϡ ηάϝϯςʔγϣϯҧ൓ɾෆਖ਼ͳ໋ྩɾ BTTFSU͓ΑͼΩϟον͞Ε͍ͯͳ͍ྫ֎͕ൃੜͨ͠ 8BUDIEPHʹΑΔ΋ͷ ىಈ͓ΑͼόοΫάϥ΢ϯυɾϑΥΞάϥ΢ϯυ ͷભҠॲཧʹ͕͔͔࣌ؒΓ͗ͨ͢ $16࢖༻੍ݶ

    $16ෛՙ͕ߴ͍ঢ়ଶ͕ଓ͍ͨ ϝϞϦ࢖༻੍ݶ ϝϞϦ࢖༻ྔ͕γεςϜͷ੍ݶ஋Λ௒͑ͨ ϝϞϦෆ଍ +FUTBN γεςϜ͕ΑΓଟ͘ͷϝϞϦྖҬΛඞཁͱ͢ΔͨΊ όοΫάϥ΢ϯυͷΞϓϦΛऴྃͨ͠ #BDLHSPVOE5BTL ͷλΠϜΞ΢τ #BDLHSPVOE5BTLʹׂΓ౰ͯΒΕ͍ͯΔ࣮ߦ࣌ؒΛ௒͑ͨ ϑΝΠϧϩοΫ "QQ(SPVQTͷڞ༗ϑΝΠϧͷϩοΫΛอ࣋͠ଓ͚ͨ ࠓճͷݪҼ
  19. ཧ༝ ϓϩύςΟ Ϋϥογϡ DVNVMBUJWF#BE"DDFTT&YJU$PVOU DVNVMBUJWF*MMFHBM*OTUSVDUJPO&YJU$PVOU 8BUDIEPHʹΑΔ΋ͷ DVNVMBUJWF"QQ8BUDIEPH&YJU$PVOU $16࢖༻੍ݶ DVNVMBUJWF$163FTPVSDF-JNJU&YJU$PVOU ϝϞϦ࢖༻੍ݶ

    DVNVMBUJWF.FNPSZ3FTPVSDF-JNJU&YJU$PVOU ϝϞϦෆ଍ +FUTBN DVNVMBUJWF.FNPSZ1SFTTVSF&YJU$PVOU #BDLHSPVOE5BTL ͷλΠϜΞ΢τ DVNVMBUJWF#BDLHSPVOE5BTL"TTFSUJPO5JNFPVU&YJU$PVOU ϑΝΠϧϩοΫ DVNVMBUJWF4VTQFOEFE8JUI-PDLFE'JMF&YJU$PVOU
  20. public enum BackgroundPerformanceError: Error { case memoryLimit(Int, Double?, Double?) public

    var code: Int { switch self { case .memoryLimit: return -1001 } } public var message: String { switch self { case .memoryLimit: "ϝϞϦͷ੍ݶΛ௒͑ͨͨΊΞϓϦ͕ऴྃ͠·ͨ͠" } } public var userInfo: [String: Any] { var userInfo = ["message": message] switch self { case let .memoryLimit(count, averageSuspendedMemory, peakMemoryUsage): userInfo["cumulative-count"] = "\(count)" if let averageSuspendedMemory { userInfo["average-suspended-memory"] = "\(averageSuspendedMemory)" } if let peakMemoryUsage { userInfo["peak-memory-usage"] = "\(peakMemoryUsage)" } } return userInfo } }
  21. public enum BackgroundPerformanceError: Error { case memoryLimit(Int, Double?, Double?) public

    var code: Int { switch self { case .memoryLimit: return -1001 } } public var message: String { switch self { case .memoryLimit: "ϝϞϦͷ੍ݶΛ௒͑ͨͨΊΞϓϦ͕ऴྃ͠·ͨ͠" } } public var userInfo: [String: Any] { var userInfo = ["message": message] switch self { case let .memoryLimit(count, averageSuspendedMemory, peakMemoryUsage): userInfo["cumulative-count"] = "\(count)" if let averageSuspendedMemory { userInfo["average-suspended-memory"] = "\(averageSuspendedMemory)" } if let peakMemoryUsage { userInfo["peak-memory-usage"] = "\(peakMemoryUsage)" } } return userInfo } }
  22. public enum BackgroundPerformanceError: Error { case memoryLimit(Int, Double?, Double?) public

    var code: Int { switch self { case .memoryLimit: return -1001 } } public var message: String { switch self { case .memoryLimit: "ϝϞϦͷ੍ݶΛ௒͑ͨͨΊΞϓϦ͕ऴྃ͠·ͨ͠" } } public var userInfo: [String: Any] { var userInfo = ["message": message] switch self { case let .memoryLimit(count, averageSuspendedMemory, peakMemoryUsage): userInfo["cumulative-count"] = "\(count)" if let averageSuspendedMemory { userInfo["average-suspended-memory"] = "\(averageSuspendedMemory)" } if let peakMemoryUsage { userInfo["peak-memory-usage"] = "\(peakMemoryUsage)" } } return userInfo } }
  23. 1BZMPBE͔Β&SSPSΛ࡞੒͢Δ public func didReceive(_ payloads: [MXMetricPayload]) { payloads.forEach { //

    ApplicationExitMetrics ؚ͕·Ε͍ͯͳ͚Ε͹εΩοϓ guard let applicationExitMetrics = $0.applicationExitMetrics else { return } let foregroundExitData = applicationExitMetrics.foregroundExitData let backgroundExitData = applicationExitMetrics.backgroundExitData … } }
  24. var errors: [PerformanceError] = [] // ϝϞϦ࢖༻ྔΛKilobytesʹม׵ let averageSuspendedMemory =

    $0.memoryMetrics?.averageSuspendedMemory.averageMeasurement .converted(to: .kilobytes).value let peakMemoryUsage = $0.memoryMetrics?.peakMemoryUsage.converted(to: .kilobytes).value // ϑΥΞάϥ΢ϯυͰϝϞϦ੍ݶʹΑΔऴྃճ਺͕1Ҏ্͋Ε͹ه࿥͢Δ if foregroundExitData.cumulativeMemoryResourceLimitExitCount > 0 { // ͲΕ͘Β͍ͷϝϞϦΛ࢖༻͍͔ͯͨ͠΋ՄೳͰ͋Ε͹ه࿥ errors.append(.foreground(.memoryLimit( foregroundExitData.cumulativeMemoryResourceLimitExitCount, averageSuspendedMemory, peakMemoryUsage ))) } // όοΫάϥ΢ϯυͰϝϞϦ੍ݶʹΑΔऴྃճ਺͕1Ҏ্͋Ε͹ه࿥͢Δ if backgroundExitData.cumulativeMemoryResourceLimitExitCount > 0 { // ͲΕ͘Β͍ͷϝϞϦΛ࢖༻͍͔ͯͨ͠΋ՄೳͰ͋Ε͹ه࿥ errors.append(.background(.memoryLimit( backgroundExitData.cumulativeMemoryResourceLimitExitCount, averageSuspendedMemory, peakMemoryUsage ))) }