Як виміряти продуктивність застосунка за допомогою MetricKit

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Привіт! Мене звати Олексій Андрющенко, я iOS-розробник в продуктовій студії Uptech. На одному з проектів в Uptech ми впровадили інструмент MetricKit. Це був новий та цікавий досвід, і в цій статті я хочу поділитися такими інсайтами:

  • Що таке MetricKit і як його налаштувати.
  • Типи метрик в MetricKit.
  • Як за допомогою MetricKit моніторити роботу iOS-застосунка (приклади орієнтовних запитів).

Стаття буде цікава iOS-розробникам, які хочуть вимірювати продуктивність додатків.

Що ми маємо на увазі під «продуктивністю iOS-застосунка»

Виміряти продуктивність застосунку під час розробки просто. За допомогою Xcode можна побачити кількість оперативної пам’яті, що використовується, та завантаженість процесора.

Втім, коли застосунок потрапляє до рук реальних користувачів, все стає складніше. В реальних умовах користування застосунком може з’явитися безліч сюрпризів, що впливатимуть на продуктивність та користувацький досвід.

Існує багато інструментів для перевірки продуктивності iOS-застосунків. Втім, MetricKit вигідно відрізняється від інших тим, що дозволяє збирати та аналізувати продуктивність застосунку в реальному середовищі. Саме цим MetricKit заслуговує на увагу розробників.

Не варто думати, що витратити день на те, щоб зекономити користувачеві 300 мілісекунд — це вбити час. У реальності, якщо ви маєте 100 тисяч користувачів, то ви економите 30 000 секунд для них, а це загалом 8 годин 20 хвилин.

Взагалі метрик багато, але найважливішими є:

  • прийнятний час на запуск;
  • використання пам’яті пристрою;
  • частота операцій запису на диск;
  • споживання батареї;
  • частота кадрів в секунду (FPS);
  • використання мобільних даних.

Крім того, хороша продуктивність означає, що кожен компонент працює бездоганно. Плавний інтерфейс та швидке виконання запитів покращують взаємодію з користувачами та залучають їх до використання програми. Важливо вимірювати продуктивність застосунка та постійно його покращувати, тоді користувачі будуть задоволені й повертатимуться до роботи з ним. MetricKit — це чудовий інструмент для відстеження продуктивності додатків iOS.

Що таке MetricKit

MetricKit — це фреймворк, що допомагає збирати та аналізувати звіти для кожного девайсу щодо діагностики помилок та збоїв, а також показників живлення та продуктивності. Це потужний інструмент, який забезпечує реальний доступ до даних про продуктивність з усіх пристроїв користувачів, які використовують програму. Також цей інструмент може допомогти знайти тенденції та закономірності регресії продуктивності.

Як налаштувати MetricKit

Почати використовувати MetricKit у своєму проекті дуже легко. Для початку потрібно всього три кроки:

Крок 1. Імпортуйте фреймворк MetricKit у свій код.

Крок 2. Створіть єдиний екземпляр класу, який є точкою входу до взаємодії з фреймворком, назвемо його MetricManager.

Реалізуйте наданий протокол MXMetricManagerSubscriber, і все, ви готові отримати показники з фреймворку.

Приклад корисного навантаження:

Приклад отриманих даних:

"appVersion": 1.4.10, 
"timeStampBegin": 2021-09-19 21:00:00 +0000, 
"timeStampEnd": 2021-09-20 20:59:00 +0000,

 "cellularConditionMetrics": {
    cellConditionTime =     {
        histogramNumBuckets = 3;
        histogramValue =         {
            0 =             {
                bucketCount = 20;
                bucketEnd = "1 bars";
                bucketStart = "1 bars";
            };
            1 =             {
                bucketCount = 30;
                bucketEnd = "2 bars";
                bucketStart = "2 bars";
            };
            2 =             {
                bucketCount = 50;
                bucketEnd = "3 bars";
                bucketStart = "3 bars";
            };
        };
    };
}, 
"applicationLaunchMetrics": {
    histogrammedResumeTime =     {
        histogramNumBuckets = 3;
        histogramValue =         {
            0 =             {
                bucketCount = 60;
                bucketEnd = "210 ms";
                bucketStart = "200 ms";
            };
            1 =             {
                bucketCount = 70;
                bucketEnd = "310 ms";
                bucketStart = "300 ms";
            };
            2 =             {
                bucketCount = 80;
                bucketEnd = "510 ms";
                bucketStart = "500 ms";
            };
        };
    };
    histogrammedTimeToFirstDrawKey =     {
        histogramNumBuckets = 3;
        histogramValue =         {
            0 =             {
                bucketCount = 50;
                bucketEnd = "1\U00a0010 ms";
                bucketStart = "1\U00a0000 ms";
            };
            1 =             {
                bucketCount = 60;
                bucketEnd = "2\U00a0010 ms";
                bucketStart = "2\U00a0000 ms";
            };
            2 =             {
                bucketCount = 30;
                bucketEnd = "3\U00a0010 ms";
                bucketStart = "3\U00a0000 ms";
            };
        };
    };
}, 
"diskIOMetrics”: {
    cumulativeLogicalWrites = "1\U00a0300 kB";
}, 
"networkTransferMetrics": {
    cumulativeCellularDownload = "80\U00a0000 kB";
    cumulativeCellularUpload = "70\U00a0000 kB";
    cumulativeWifiDownload = "60\U00a0000 kB";
    cumulativeWifiUpload = "50\U00a0000 kB";
}, 
"applicationExitMetrics": {
    backgroundExitData =     {
        cumulativeAbnormalExitCount = 1;
        cumulativeAppWatchdogExitCount = 1;
        cumulativeBackgroundFetchCompletionTimeoutExitCount = 1;
        cumulativeBackgroundTaskAssertionTimeoutExitCount = 1;
        cumulativeBackgroundURLSessionCompletionTimeoutExitCount = 1;
        cumulativeBadAccessExitCount = 1;
        cumulativeCPUResourceLimitExitCount = 1;
        cumulativeIllegalInstructionExitCount = 1;
        cumulativeMemoryPressureExitCount = 1;
        cumulativeMemoryResourceLimitExitCount = 1;
        cumulativeNormalAppExitCount = 1;
        cumulativeSuspendedWithLockedFileExitCount = 1;
    };
    foregroundExitData =     {
        cumulativeAbnormalExitCount = 1;
        cumulativeAppWatchdogExitCount = 1;
        cumulativeBadAccessExitCount = 1;
        cumulativeCPUResourceLimitExitCount = 1;
        cumulativeIllegalInstructionExitCount = 1;
        cumulativeMemoryResourceLimitExitCount = 1;
        cumulativeNormalAppExitCount = 1;
    };
}, 
"memoryMetrics": {
    averageSuspendedMemory =     {
        averageValue = "100\U00a0000 kB";
        sampleCount = 500;
        standardDeviation = 0;
    };
    peakMemoryUsage = "200\U00a0000 kB";
},
"displayMetrics": {
    averagePixelLuminance =     {
        averageValue = "50 apl";
        sampleCount = 500;
        standardDeviation = 0;
    };
}, 
"metaData": {
    appBuildVersion = 2;
    bundleIdentifier = "com.uptech";
    deviceType = "iPhone12,3";
    osVersion = "iPhone OS 15.0 (19A346)";
    platformArchitecture = arm64e;
    regionFormat = UA;
}, 
"animationMetrics": {
    scrollHitchTimeRatio = "1\U00a0000 ms per s";
}, 
"locationActivityMetrics": {
    cumulativeBestAccuracyForNavigationTime = "20 secs";
    cumulativeBestAccuracyTime = "30 secs";
    cumulativeHundredMetersAccuracyTime = "30 secs";
    cumulativeKilometerAccuracyTime = "20 secs";
    cumulativeNearestTenMetersAccuracyTime = "30 secs";
    cumulativeThreeKilometersAccuracyTime = "20 secs";
}, 
"applicationTimeMetrics": {
    cumulativeBackgroundAudioTime = "30 secs";
    cumulativeBackgroundLocationTime = "30 secs";
    cumulativeBackgroundTime = "40 secs";
    cumulativeForegroundTime = "700 secs";
},
```
"signpostMetrics":  {
{
    signpostCategory = TestSignpostCategory1;
    signpostIntervalData =     {
        histogrammedSignpostDurations =         {
            histogramNumBuckets = 3;
            histogramValue =             {
                0 =                 {
                    bucketCount = 50;
                    bucketEnd = "100 ms";
                    bucketStart = "0 ms";
                };
                1 =                 {
                    bucketCount = 60;
                    bucketEnd = "400 ms";
                    bucketStart = "100 ms";
                };
                2 =                 {
                    bucketCount = 30;
                    bucketEnd = "700 ms";
                    bucketStart = "400 ms";
                };
            };
        };
        signpostAverageMemory = "100\U00a0000 kB";
        signpostCumulativeCPUTime = "30\U00a0000 ms";
        signpostCumulativeHitchTimeRatio = "50 ms per s";
        signpostCumulativeLogicalWrites = "600 kB";
    };
    signpostName = TestSignpostName1;
    totalSignpostCount = 30;
},
{
    signpostCategory = TestSignpostCategory2;
    signpostIntervalData =     {
        histogrammedSignpostDurations =         {
            histogramNumBuckets = 3;
            histogramValue =             {
                0 =                 {
                    bucketCount = 60;
                    bucketEnd = "200 ms";
                    bucketStart = "0 ms";
                };
                1 =                 {
                    bucketCount = 70;
                    bucketEnd = "300 ms";
                    bucketStart = "201 ms";
                };
                2 =                 {
                    bucketCount = 80;
                    bucketEnd = "500 ms";
                    bucketStart = "301 ms";
                };
            };
        };
        signpostAverageMemory = "60\U00a0000 kB";
        signpostCumulativeCPUTime = "50\U00a0000 ms";
        signpostCumulativeLogicalWrites = "700 kB";
    };
    signpostName = TestSignpostName2;
    totalSignpostCount = 40;
}
}, 
"applicationResponsivenessMetrics": {
    histogrammedAppHangTime =     {
        histogramNumBuckets = 3;
        histogramValue =         {
            0 =             {
                bucketCount = 50;
                bucketEnd = "100 ms";
                bucketStart = "0 ms";
            };
            1 =             {
                bucketCount = 60;
                bucketEnd = "400 ms";
                bucketStart = "100 ms";
            };
            2 =             {
                bucketCount = 30;
                bucketEnd = "700 ms";
                bucketStart = "400 ms";
            };
        };
    };
}, 
"cpuMetrics": {
    cumulativeCPUInstructions = "100 kiloinstructions";
    cumulativeCPUTime = "100 secs";
}, 
"gpuMetrics": {
    cumulativeGPUTime = "20 secs";
}

Примітка: Використання MetricKit вимагає iOS 13 або новішої версії, тому, якщо ваш проєкт підтримує версії iOS нижче за iOS 13, вам потрібно додатково перевірити, чи підходить поточна версія iOS.

Типи метрик, які можна відслідковувати з MetricKit

MetricKit збирає багато різних показників, які допомагають вам зрозуміти продуктивність iOS-застосунка, наприклад: живлення батареї, продуктивність, швидкість реагування, доступ до диска та спеціальні показники.

Немає потреби використовувати всі показники для кожного iOS-застосунка. Наприклад, якщо ви не використовуєте локальну базу даних і рідко зберігаєте файли на телефоні, то показники, пов’язані з записом на диск, не настільки важливі.

Але я хотів би поговорити про показники, які зазвичай важливі для будь-якої програми для iOS:

  • Показник активності місцезнаходження. Він показує інформацію про те, як довго відслідковування місця було активоване для кожного типу точності. Це корисно, якщо у вашому застосунку використовується LocationManager, ви можете побачити, чи є надто високі цифри, і дослідити це.
  • Метрика передачі даних у мережі. Показує, скільки даних завантажено та вивантажено за допомогою мобільного інтернету та WiFi.
  • Показник закриття програми. Це дуже важливий показник, оскільки він може показати причини, з яких ваша програма існує для активного та фонового режимів. Причини виходу з програми під час активного режиму можуть включати: використання занадто багато пам’яті, надто сильне використання процесора, недійсний доступ до пам’яті тощо.
  • Показник запуску програми. Показує, скільки часу потрібно застосунку для запуску. Зменшення часу запуску покращує взаємодію з користувачами та зменшує ймовірність того, що система iOS припинить роботу програми.
  • Спеціальні показники.

Серед різних показників MetricKit є один, який заслуговує особливої уваги — Signpost. Це спеціальна метрика, що дозволяє розробникам вимірювати обчислення.

Метрика вказівників може бути корисною у багатьох випадках, наприклад, для вимірювання продуктивності мережевих запитів.

Багато iOS-застосунків взаємодіють з зовнішніми сервісами, і це дає гарну можливість відстежувати продуктивність. Наприклад, ви можете відстежувати кількість запитів і час запиту.

За допомогою цих метрик ви можете визначити, які запити використовуються найчастіше, а також що можна оптимізувати (у разі збільшення часу запиту).

Використання MetricKit для відстеження продуктивності: покроковий гайд

Нарешті, коли ми переконалися у важливості та типах показників, ми переходимо до процесу вимірювання продуктивності застосунків iOS. Я б особисто поділив його на три етапи:

Крок 1. По-перше, вам потрібно створити клас, який буде обробляти логіку за допомогою вказівників, як у прикладі нижче:

Крок 2. Інтерфейс цієї структури досить простий. Вам потрібно викликати `logger.beginLog (name:" example «)» перед запитом і «logger.endLog (name:» example "" після отримання відповіді.

Крок 3. Ви зареєстрували інформацію про запит, але вам потрібно отримати її назад. Для цього вам потрібно додати код до методу didReceive(_ payloads: [MXMetricPayload]):

Примітка: властивість signpostIntervalData змінної signpostMetric має складну структуру, тому доведеться використати додаткову логіку, щоб отримати необхідні дані (в основному тривалість запиту).

Висновок

Користувачі вирішуватимуть, чи варто використовувати ваш застосунок, виходячи з того, як він працює, а не з того, як він розроблений. Тому вимірювання продуктивності вашого додатка для iOS має вирішальне значення як для задоволеності користувачів, так і для ваших бізнес-задач.

За допомогою MetricKit ви можете відстежувати багато показників продуктивності вашого iOS-застосунка одночасно в три прості кроки. Це чудовий інструмент для того, щоб ваш застосунок iOS був орієнтованим на користувачів і працював продуктивно.

Додаткові ресурси

👍НравитсяПонравилось6
В избранноеВ избранном2
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Подписаться на комментарии