Как стать автором
Обновить

Использование инструмента трассировки событий в Erlang

Время на прочтение 4 мин
Количество просмотров 2.2K
Автор оригинала: Jesper Louis Andersen
Вот одна из распространённых проблем многопоточных(concurrent) систем: события возникают постоянно в разных частях программы в разное время, и у вас нет возможности контролировать причину и время их возникновения. Чтобы отследить проблему, мы зачастую можем воспользоваться диаграммой последовательностей. Например, такой (спасибо Wikipedia):



Предназначение данной диаграммы — показать взаимодействие между различными параллельными компонентами системы. В данном примере Fred, Bob, Hank и Renee в ресторане. Каждый может легко нарисовать подобную диаграмму на бумаге. Проблема в том, что наброски на бумаге могут отличаться от того, что происходит во время выполнения вашей программы.

Правда было бы здорово, если бы вы могли строить похожие диаграммы на основе данных трассировки программы автоматически? Что ж, Erlang вам в этом поможет. Я воспользуюсь некоторым кодом, что бы проиллюстрировать пример, как это сделать.

Шаг 1. Строим функцию трассировки


Первый шаг — построить функцию трассировки. Моя выглядит следующим образом и расположена в модуле «utp».
report_event(DetailLevel, FromTo, Label, Contents) ->
   %% N.B External call
   ?MODULE:report_event(DetailLevel, FromTo, FromTo, Label, Contents).

report_event(_DetailLevel, _From, _To, _Label, _Contents) ->
   hopefully_traced.


Основная идея заключена во второй функции. Это функция-заглушка от пяти аргументов, которая сразу же отрабатывается. Возвращаемое значение «hopefully_traced» произвольно, но оно показывает что мы хотим вернуть из этой функции. Первая функция используется, когда мы отслеживаем исходящие и получаемые события одим и тем же компонентом.

Описание аргументов:
  • DetailLevel: число от 0 до 100. Оно обозначает уровень значимости данного события. Назначая основным событиям меньшие, а менее значимым — бóльшие числа, мы даём возможность трассировщику скрыть часть событий, таким образом добиваясь нужного уровеня гранулированности.
  • From: источник событий.
  • To: кому предназначено событие.
  • Label: нотационная метка сообщения.
  • Contents: то, что будет отображено при щелчке на событие. Может предоставлять более детальную информацию о событии, не загромождая диаграмму.

Шаг 2. Трассируем события в программе


Когда в программе происходит что-то важное, вызываем «utp:report_event/5» (подставляем сюда название функции трассировки, которую объявили в шаге 1). По умолчанию данная функция не будет делать ничего, но впоследствии мы можем ее отловить, используя возможности трассировки Erlang. Вот пример взаимодействия, описанного выше:
trace_test() ->
   Events = [{fred, bob, order_food},
             {bob, hank, order_food},
             {bob, fred, serve_wine},
             {hank, bob, pickup},
             {bob, fred, serve_feed},
             {fred, renee, pay}],
   [utp:report_event(50, F, T, L, [])
    || {F,T,L} <- Events].


Имейте ввиду, отслежено может быть все что угодно, путём вызова данной функции в различных местах вашего кода. Вместо запуска множества отладочных блоков, вы можете добавить вызов функции трассировки в любом месте программы. Также заметьте, что я немного ужал пример выше, используя генератор списков, т.к. мне не важен уровень детализации событий и их содержимое.

Шаг 3. Вызываем приложение «et»


Далее нам необходимо правильным способом вызвать Erlang приложение «et». Я написал модуль «utp_filter», который совершает этот вызов, несмотря на то, что пока не содержит каких-либо фильтрующих функций:
start(ExtraOptions) ->
   Options =
       [{event_order, event_ts},
        {scale, 2},
        {max_actors, 10},
        {detail_level, 90},
        {actors, [fred, bob, hank, renee]},
        {trace_pattern, {utp, max}},
        {trace_global, true},
        {title, "uTP tracer"} | ExtraOptions],
   et_viewer:start(Options).


Данный модуль инициализирует «et_viewer» таким образом, что он может быть использован с нашим примером. Порядок событий — «event_ts» означает, что мы добавляем временные метки тогда, когда события возникают, а не тогда, когда они получены. Атрибут «actors» устанавливает порядок появления акторов. Атрибут «trace_pattern» очень важен. Он позволяет отследить вызов «utp:report_event/5». Также можно указать много других опций, либо событие выберет их самостоятельно. РПросто укажите модуль, в котором расположена функция трассировки.

Шаг 4. Тестирование


Если мы вызовем «utp_filter:start([])», запустив «et viewer», описанный в шаге 3, мы сможем запустить тест трассировки из шага 2:



Это должно быть похоже на пример с Wikipedia, за исключением того, что получено путем трассировки нашей программы. В частности эта идея была использована мной для захвата и нахождения багов в вариантах TCP-стэка «uTP»:



(Несмотря на то, что диаграмма маленькая, она всё же отображает основную идею — этот пример не для того, чтобы показать диаграмму, а для того чтобы приобрести всемирную славу :). Для TCP-подобного протокола это очень мощный инструмент: вывод tcpdump(1) и strace(1), а также внутренне состояние протокола на одной диаграмме. Я нашел несколько багов при помощи данных инструментов просто просматривая взаимодействие программы.

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

Приложение(Application) — термин Erlang/OTP, более подробно
Теги:
Хабы:
+38
Комментарии 3
Комментарии Комментарии 3

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн