Новая игрушка
Мы продолжаем знакомиться с новым материалом от Apple, представленным на WWDC. На этот раз рассмотрим MetricKit, это абсолютно новый фреймворк, который служит инструментом для мониторинга производительности приложений.
Все знают, что измерение производительности приложения во время разработки — это просто. Xcode показывает количество используемой оперативной памяти и загруженность процессора, вы можете подключиться с помощью Instruments к симулятору или тестируемому устройству и даже написать собственные инструменты (более подробно об этом смотрите наши статьи о пользовательских пакетах инструментов: часть 1/часть 2). Только лишь понимание важности настройки производительности не позволяет измерить практически все, что выполняет приложение. Но все стает более сложным, когда мы говорим о AppStore, если разрабатываемое приложение предназначается для реальных пользователей. Независимо от того, насколько тщательно вы тестируете свое приложение, в реальных условиях всегда появится куча сюрпризов, которые повлияют на производительность и пользовательский опыт. Конечно, существует множество инструментов для сбора различных параметров, но большинство из них ограничены iOS SDK, а также влиянием фактического мониторинга на поведение приложений.
В этом году Apple решила заполнить этот пробел и предоставила разработчикам инструмент, который помогает им собирать и анализировать показатели производительности приложений в реальной среде. Они анонсировали MetricKit (фреймворк, который предоставляет доступ к параметрам, которые предоставляет OS) отдельной вкладки в органайзере Xcode 11, где можно найти параметры приложений. Сделаем паузу в MetricKit, потому что отображение параметров в XCode будет работать только с приложениями, которые уже опубликованные в AppStore.
MXMetricManager
Архитектура фреймворка довольно проста и понятна. Центральную часть занимает класс MXMetricManager, представляющий собой одно-элементную структуру, которая предоставляет разработчику большой набор APIs фреймворка.
В общем, рабочий процесс состоит из 3-х основных этапов:
- Вы инициализируете MXMetricMnager и назначаете для него наблюдателя.
- При желании вы можете реализовать собственные метрики в своем приложении, используя API Signpost
- И, наконец, теперь мы имеем дело с полученными данными в методе didReceivePayloads, т.е. отправляем их в свой бэкэнд для дальнейшего анализа.
Параметры приходят в виде массива экземпляров MXMetricPayload. Полезная нагрузка инкапсулирует наборы метаданных и временные метки. Metric payload — это простая оболочка для подкласса MXMetric. Для каждого типа параметров она отдельная.
Типы метрик довольно хорошо документированы Apple, поэтому не будем останавливаться на этом слишком долго. Тем не менее, следует остановиться, чтобы заметить одну интересную вещь — MXMetric предоставляет открытый API для сериализации его в NSDictionary или JSON, что, с моей точки зрения, немного необычно.
Внутренние компоненты MetricKit.
Снаружи MetricKit выглядит довольно просто. Но всегда интересно увидеть, как все работает изнутри. Погружение во что-то более глубокое является всегда интригой, если перед вами стоит конкретная задача. Поэтому я решил, что хочу передать параметры с метками MetricKit, а затем заставить их предоставлять мне обновленные метрики в любое время. Конечно, вы можете использовать `Debug -> Simulate MetricKit Payloads` в Xcode, но id не позволяет отображать собственные данные. Правда, это не очень полезная команда, но она дает вам направление в ваших исследованиях, и это выглядит весьма забавно ;)
Чтобы начать выполнение задачи, нам, очевидно, необходим сам MetricKit. Можно подумать, что получить бинарный файл для фреймворка легко, потому что Xcode показывает его в списке фреймворков, как только вы добавите его через диалог «связать бинарный файл с библиотеками». Это весьма оптимистичная мысль. Потому что если вы откроете MetricKit.framework, вы увидите файл MetricKit.tbd. Его размер всего 4кб. Очевидно, это не то, что мы ищем.
Так что же на самом деле здесь происходит?
TBD расшифровывается как «text-based dylib stub» и фактически является файлом YAML с описанием dylib, экспортирующим символы и путем к двоичному файлу dylib. Связывание с файлами tbd уменьшает размер двоичного файла. Позже, во время выполнения, настоящий бинарный файл dylib будет загружен из OS по пути, указанному в файле tbd. Вот как выглядит файл, когда вы открываете его в Xcode:
Используя путь из файла tbd, можно легко получить двоичный файл MetricKit для дальнейшего исследования, но существует еще более простой метод.
Наш двоичный файл приложения содержит путь к каждой динамически связанной библиотеке в разделе заголовка Mach-O. Эту информацию легко получить с помощью инструмента, используя флаг -l.
Вот вывод для тестового проекта, который я создал:
→ otool -l ./Metrics | grep -i metrickit
name /System/Library/Frameworks/MetricKit.framework/MetricKit (offset 24)
Можно увидеть тот же путь, который мы видели ранее в файле tbd. Имея бинарный файл фреймворка, можно взглянуть на внутренние элементы. Для этого я обычно использую Hopper Disassemble. Он простой в использовании, но очень мощный инструмент для внимательного исследования двоичных файлов.
Как только откроется бинарный файл MetricKit — перейдем к вкладке ‘Proc.’ и развернем список ’Tags’. Здесь можно увидеть все экспортированные символы. Выбрав один из них (например, MXMetricManager), увидим все его методы и, выбрав метод, увидим его содержимое в правой части:
При просмотре списка методов MXMetricManager [ https://gist.github.com/deszip/88a258ae21d33dc75d7cbac9569c6ec1 ] весьма легко заметить метод _checkAndDeliverMetricReports. Похоже, это то, что необходимо вызвать, чтобы заставить MetricKit доставлять обновления подписчикам.
К сожалению, попытка вызвать его не привела к вызову абонента, что, вероятно, означает то, что данные параметры не будут доставлены. Рассматривая реализацию метода, заметим несколько интересных вещей: он выполняет перебор содержимого каталога /Library/Caches/MetricKit/Reports.
Затем он пытается разархивировать экземпляр MXMetricPayload для каждого элемента на диске. И, наконец, он перебирает зарегистрированных подписчиков и вызывает метод didReceive со списком данных.
Проблема, вероятно, в том, что в /Library/Caches/MetricKit/Reports нет данных, но известно то, что нам необходимы некоторые заархивированные экземпляры MXMetricPayload. Итак, давайте создадим их и поместим на диск перед вызовом ‘_checkAndDeliverMetricReports’. Опять же, план состоит в том, чтобы создать экземпляр MXMetricPayload, затем создать и добавить в него любой тип MXMetric, а затем заархивировать экземпляр данных на диске. После всего вызвать метод ‘_checkAndDeliverMetricReports’, это должно привести к вызову нашего подписчика с stub в качестве аргумента.
Просматривая документы Apple по payload и метрикам, вы можете заметить, что у них нет общедоступных инициализаторов, и большинство свойств доступны только для чтения. Итак, каким же образом возможно создать экземпляр класса?
Снова вернемся к Hopper, чтобы посмотреть список методов MXMetricPayload:
Здесь видно его инициализаторы и методы для присваивания параметров. Вызывать закрытые методы легко, с помощью класса NSInvocation и метода ‘performSelector’ из-за динамической природы Objective-C.
В качестве примера мы создадим метрики для CPU и добавим их в payload. Используя данную ссылку, можно найти полный фрагмент кода: [ https://gist.github.com/deszip/a0cf877b07cc2877129e0aaef2fed1e4 ].
И в завершение архивируем все что мы создали и записываем данные в каталог /Library/Caches/MetricKit/Reports.
Теперь пришло время вызвать метод '_checkAndDeliverMetricReports', что в итоге должно привести к вызову абонента. На этот раз в качестве аргумента передаем данные с stubbed payload в качестве аргумента.
Откуда берутся метрики
Получение отчетов довольно просто реализовать посредством MetricKit, но вам, вероятно, интересно узнать, как отчеты появляются в каталоге app/Library. Вот как.
Копая внутри двоичного файла MetricKit, я заметил этот метод: ' _createXPCConnection’. Проверка его реализации проясняет ситуацию — он строит NSXPCConnection для обслуживания с именем com.apple.metrickit.xpc и двумя интерфейсами MXXPCServer и MXXPCClient для клиентской и серверной сторон. Если посмотреть на описание протокола:
Заключение
MetricKit — это уникальный и незаменимый инструмент, для заботы о производительности своего приложения в реальных условиях в продакшене.
К сожалению, в настоящее время невозможно взглянуть на пользовательский интерфейс ‘Metric’ в Xcode, за исключением того, что было показано во время демонстрации на сессии WWDC.
Он может быть бесценным инструментом для перехода пользовательского опыта на новый уровень за счет устранения проблем с производительностью в вашем коде.
Один недостаток, который я вижу сейчас в данном инструменте — это отсутствие подробностей для каждого типа: только разделение — это версия приложения, и вы не можете видеть какие-либо метрики для определенной группы устройств/версий OS/регионов и т.д.
Но, естественно, всегда имеется возможность отправить данные себе для дальнейшей обработки вместе с важной информацией, которая вам необходима. Вы можете прикрепить ее к задачам в вашем трекере ошибок и многое другое. В AppSpector наша команда работает над расширением функциональности средств мониторинга производительности с помощью данных, полученных из MetricKit.
Оставаться в курсе событий!