company_banner

Custom instruments: когда signpost недостаточно

    Instruments для Xcode компании Apple — это инструменты для анализа производительности iOS-приложения. Их используют для сбора и отображения данных, которые необходимы в отладке кода. В прошлом году Apple презентовала Custom Instruments. Это возможность расширить стандартный набор инструментов для профилирования приложений. Когда существующих инструментов недостаточно, вы сможете самостоятельно создать новые — они соберут, проанализируют и отобразят данные так, как вам потребуется.

    Прошел год, а новых публичных инструментов и информации по их созданию в сети почти нет. Так что мы решили исправить ситуацию и поделиться тем, как создавали собственный Custom Instrument, который определяет причину слабой изоляции unit-тестов. Он базируется на технологии signpost (мы писали о ней в предыдущей статье) и позволяет быстро и точно определять место возникновения мигания теста.



    Теоретический минимум


    Чтобы создать новый инструмент для Xcode, потребуется понимание двух теоретических блоков. Тем, кто хочет разобраться самостоятельно, сразу дадим нужные ссылки:


    Для остальных — ниже краткий конспект по необходимым темам.

    Сперва выберите File -> New -> Project -> категория macOS -> Instruments package. Созданный проект включает в себя файл с расширением .instrpkg, в котором декларативно в формате xml объявлен новый инструмент. Ознакомимся с элементами разметки:

    Что Атрибуты Описание
    Схемы данных
    interval-schema, point-schema и т.д.
    Описывает структуру данных в виде таблицы подобно sql-схемам. Схемы используются в других элементах разметки, чтобы определить тип данных на входе и выходе модели, например, при описании отображения (UI).
    Импорт схем данных
    import-schema
    Импорт готовых схем. Он позволяет использовать структуры данных, которые определены Apple.
    Модель инструмента
    modeler
    Связывает инструмент с файлом .clp, в котором определена логика инструмента, и объявляет ожидаемую схему данных на входе и выходе модели.
    Описание инструмента
    instrument
    Описывает модель данных и определяет, как события будут отображаться в UI. Модель данных описывается с помощью атрибутов create-table, create-parameter и тд. Графики инструмента определяются атрибутами graph, а таблица деталей — list, narrative и т.д.

    Если хотим дополнить логику нового инструмента, то создаем файл .clp с кодом на языке CLIPS. Базовые сущности языка:

    • «Fact» — это некое событие, зарегистрированное в системе с помощью команды assert;
    • «Rule» — это if-блок со специфичным синтаксисом, содержащий условие, при котором выполняется набор действий.

    Какие правила и в какой последовательности будут активированы, определяется самим CLIPS на основе входящих фактов, приоритетов правил и механизма разрешения конфликтов.

    Язык поддерживает создание типов данных на основе примитивов, использование арифметических, логических операций и функций. А также полноценное объектно-ориентированное программирование (ООП) с определением классов, посылкой сообщений, множественным наследованием.

    Рассмотрим базовый синтаксис языка, который позволит создавать логику для кастомных инструментов.

    1. Чтобы создать fact, используем конструкцию assert:

    CLIPS> (assert (duck))

    Таким образом, мы получим запись duck в таблице фактов, которую можно посмотреть с помощью команды facts:

    CLIPS> (facts)

    Для удаления факта используем команду retract: (retract duck)

    2. Чтобы создать rule, используем конструкцию defrule:

    CLIPS> (defrule duck) — создание правила с названием duck
    (animal-is duck)</i> — если animal-is duck присутствует в таблице фактов
    =>
    (assert (sound-is quack))) — то создается новый факт sound-is quack

    3. Для создания и использования переменных применяется следующий синтаксис (перед именем переменной идет обязательный знак "?"):

    ?<variable-name>

    4. Можно создавать новые типы данных с помощью:

    CLIPS>
    (deftemplate prospect
    (slot name (type STRING) (default ?DERIVE))
    (slot assets (type SYMBOL) (default rich))
    (slot age (type NUMBER) (default 80)))

    Так, мы определили структуру с названием prospect и тремя атрибутами name, assets и age соответствующего типа и значением по умолчанию.

    5. Арифметические и логические операции имеют префиксный синтаксис. То есть чтобы сложить 2 и 3, необходимо использовать следующую конструкцию:

    CLIPS> (+ 2 3)

    Либо чтобы сравнить две переменные x и y:

    CLIPS> (> ?x ?y)

    Практический пример


    В своем проекте мы используем библиотеку OCMock для создания объектов-заглушек. Однако возникают ситуации, когда мок живет дольше теста, для которого создавался, и влияет на изоляцию других тестов. В итоге это приводит к «миганию» (нестабильности) unit-тестов. Чтобы отследить время жизни тестов и моков, создадим собственный инструмент. Ниже приведен алгоритм действий.

    Шаг 1. Делаем разметку событий signpost


    Для обнаружения проблемных моков нужны две категории интервальных событий — время создания и уничтожения мока, время старта и завершения теста. Чтобы получить эти события, переходим в библиотеку OCMock и размечаем их с помощью signpost в методах init и stopMocking класса OCClassMockObject.





    Далее переходим в исследуемый проект, делаем разметку в unit-тестах, методах setUp и tearDown:



    Шаг 2. Создаем новый инструмент из шаблона Instrument Package




    Сначала определяем тип данных на входе. Для этого в файле .instrpkg импортируем схему signpost. Теперь события, созданные signpost, будут попадать в инструмент:



    Далее определяем тип данных на выходе. В этом примере будем выводить одномоментные события. У каждого события будет время и описание. Для этого объявляем схему:



    Шаг 3. Описываем логику инструмента


    Создаем отдельный файл с расширением .clp, в котором задаем правила с помощью языка CLIPS. Чтобы новый инструмент знал, в каком файле определена логика, добавляем блок modeler:



    В этом блоке с помощью атрибута production-system указываем относительный путь к файлу с логикой. В атрибутах output и required-input определяем схемы данных на входе и выходе соответственно.



    Шаг 4. Описываем специфику представления инструмента (UI)


    В файле .instrpkg остается описать сам инструмент, то есть отображение результатов. Создаем таблицу для данных в атрибуте create-table, используя ранее объявленную схему detected-mocks-narrative в атрибуте schema-ref. И настраиваем тип вывода информации — narrative (описательный):



    Шаг 5. Пишем код логики


    Перейдем к файлу .clp, в котором определена логика экспертной системы. Логика будет следующая: если время старта теста пересекается с интервалом жизни мока, то считаем, что этот мок «пришел» из другого теста — что нарушает изоляцию текущего unit-теста. Для того, чтобы в итоге создать событие с интересующей информацией, нужно проделать следующие шаги:

    1. Определяем структуры mock и unitTest с полями — время события, идентификатор события, название теста и класс мока.



    2. Определяем правила, которые создадут факты с типами mock и unitTest на основе входящих событий signpost:



    Читать эти правила можно следующим образом: если на входе мы получаем факт типа os- signpost с искомыми subsystem, category, name и event-type, то создаем новый факт с типом, что был определен выше (unitTest или mock), и наполняем значениями. Здесь важно помнить — CLIPS это регистрозависимый язык и значения subsystem, category, name и event- type должны совпадать с тем, что использовалось в коде исследуемого проекта.



    Значения переменных от событий signpost передаются следующим образом:



    3. Определяем правила, которые освобождают завершенные события (являются лишними, так как не влияют на результат).



    Шаг 6. Определяем правило, которое будет генерировать результаты


    Прочитать правило можно так.

    Если

    1) существует unitTest и mock;

    2) при этом начало теста наступает позже существующего мока;

    3) существует таблица для хранения результатов со схемой detected-mocks-narrative;

    то

    4) создаем новую запись;

    5) заполняем временем;

    6)… и описанием.



    В результате видим следующую картину при использовании нового инструмента:



    Исходный код custom instrument и пример проекта для использования инструмента можно посмотреть на GitHub.

    Отладка инструментов


    Для отладки кастомных инструментов используется debugger.



    Он позволяет

    1. Увидеть компилируемый код на основе описания в instrpkg.
    2. Увидеть подробную информацию о том, что происходит с инструментом во время выполнения.



    3. Вывести полный список и описание системных схем данных, которые можно использовать в качестве входных данных в новых инструментах.



    4. Выполнить произвольные команды в консоли. Например, вывести список правил командой «list-defrules» или фактов командой «facts»



    Настройка на CI сервере


    Можно запускать инструменты из командной строки — профилировать приложение во время выполнения unit- или UI-тестов на CI-сервере. Это позволит, к примеру, ловить memory leak как можно раньше. Для профилирования тестов в pipeline используем следующие команды:

    1. Запуск инструментов с атрибутами:

    xcrun instruments -t <template_name> -l <average_duration_ms> -w <device_udid>

    • где template_name — путь до шаблона с инструментами или название шаблона. Можно получить командой xcrun instruments -s;
    • average_duration_ms — время записи в миллисекундах, должно быть больше или равно времени выполнения тестов;
    • device_udid — идентификатор симулятора. Можно получить командой xcrun instruments -s. Должен совпадать с идентификатором симулятора, на котором будут выполняться тесты.

    2. Запуск тестов на этом же симуляторе командой:

    xcodebuild -workspace <path_to_workspace>-scheme <scheme_with_tests> -destination
    <device> test-without-building
    

    • где path_to_workspace — путь к рабочему пространству Xcode;
    • scheme_with_tests — схема с тестами;
    • device — идентификатор симулятора.

    В результате в рабочей директории будет создан отчет с расширением .trace, который можно открыть приложением Instruments или нажав правой кнопкой по файлу и выбрав Show Package Contents.

    Выводы


    Мы рассмотрели пример модернизации signpost до полноценного инструмента и рассказали, как автоматически применять его на «прогонах» CI-сервера, использовать в решении проблемы «мигающих» (нестабильных) тестов.

    По мере погружения в возможности custom instruments вы будете лучше понимать, в каких еще случаях можно применять инструменты. Например, нам они также помогают разобраться в проблемах многопоточности — где и когда использовать потокобезопасный доступ к данным.
    Создать новый инструмент оказалось достаточно просто. Но главное — потратив несколько дней на изучение механики и документации для его создания сегодня, вам удастся избежать нескольких бессонных ночей в попытках исправить баги.

    Источники



    Статью писали вместе с @regno — Антоном Власовым, iOS-разработчиком.
    Сбербанк
    117,00
    Компания
    Поделиться публикацией

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое