Профилирование приложений в MacOS

Введение

Часто бывают ситуации когда надо понять насколько оптимально работает приложение или какие функции или системные вызовы оно использует и как это соотносится с ресурсами. Эта задача называется профилированием. Для профилирования в Linux есть несколько вариантов, самый распространенный из которых - утилита perf, но для пользователей MacOS она недоступна, так как напрямую связан с ядром Linux. К счастью, для их операционной системы есть другие способы профилирования, о которых я расскажу ниже.

Обзор способов профилирования на MacOS

Для профилирования в MacOS фреймворк DTrace. Но использоваться он может 2-мя способами:

  1. С помощью набора утилит для профилирования Instruments.app
  2. Консольная утилита dtrace.

Схематично верхнеуровневая архитектура выглядит так:

архитектура

Профилирование при помощи Instruments

Чтобы профилирвать приложение таким способом необходимо запусть Instruments и выбрать Time profiler:

instruments главное окно

Задать настрокйи профилирования (это тема для отдельной статьи и тут я их описывать не буду):

главное окно Instruments

После запуска записи появится список процессов, раскрыв который можно увидеть функции которые вызываются:

выбор процесса в Instruments

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

Чтобы отобразить информацю с помощью FlameGraph нужно сделать 2 дополнительных действия:

Сначала нужно выгрзуть стек вызовов из Instrument (выбрать процесс и сделать Edit->Deep Copy):

экспорт stacktrace Instruments

Потом вставить это в любой текстовый документ (в моем случае emacs.stack):


Weight	Self Weight	Symbol Names
13.00 ms  100,0 %	0 s	 Emacs-x86_64-10_14 (2465)
11.00 ms  84,6 %	0 s	  Main Thread  0x5c9b
11.00 ms   0,0 %	0 s	   start
11.00 ms   0,0 %	0 s	    main
11.00 ms   0,0 %	0 s	     Frecursive_edit
11.00 ms   0,0 %	0 s	      recursive_edit_1
11.00 ms   0,0 %	0 s	       command_loop
11.00 ms   0,0 %	0 s	        internal_catch
...

Дальше загружаем FlameGraph:

git clone https://github.com/brendangregg/FlameGraph.git

Далее нужно свернуть стек вызовов в линии:

./flamegraph-instruments.pl emacs.stack > emacs.stack_folded

Важно: чтобы команда отработала без ошибок необходимо применить патч #349

После “свертки” можно построить итоговый граф:

./flamegraph.pl emacs.stack_folded > emacs.svg

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

emacs flamegraph

Профилирование при помощи dtrace

Важно: для полноценной работы dtrace нужно добавить его в исключения [SIP(https://support.apple.com/ru-ru/102149). Для этого нужно сделать следующее

  1. При загрузке зажать cmd+r чтобы попасть в меню восстановления системы
  2. Открыть терминал
  3. Выполнить команду
csrutil enable --without dtrace
  1. Перезагрузиться

Этот способ гораздо сложнее предущего, так как тут надо изучать дополнительный язык сценариев.

Для получения стека вызовов для профилирования можно использовать “однострочник”:

dtrace -x ustackframes=100 -n 'profile-97 /pid == 12345 && arg1/ { @[ustack()] = count(); } tick-60s { exit(0); }' -o out.user_stacks

В этой команде устанавливается размер стека в 100 кадров (ustackframes=100). Функция ustack() профилирует функции в пространтве пользователя. Аргумет pid задает id процесса, который профилируется. tick-60s значит что через 60 сек будет выполнен выход и скрипта профилирования.

Можно написать отдельный скрипт (profiling.d):

profile-997
/pid == "json2mpack"/
{
  @[ustack(100)] = count();
}

tick-5s
{
  exit(0);
}

Как видно тут у функции есть первый параметр, он также отвечает за рамер стека и может передаваться в качестве агрумента.

Запустить его через dtrace можно так:

dtrace -s profiling.d -o out.user_stacks

В целом по отладке с помощью скриптов dtrace есть целые книги, с которыми нужно будет хотя бы мельком ознакомиться в отличии от предыдцщего способа. Но такая отладка имеет гораздо больше применения нежели способ с Instruments.

На выходе получится файл вида (out.user_stacks):

  json2mpack`DYLD-STUB$$fcntl
  json2mpack`main+0x2c
  dyld`start+0x796
    1

  libsystem_kernel.dylib`__exit+0xa
    1

Теперь можно построить flamegraph:

./stackcollapse.pl out.user_stacks | ./flamegraph.pl > app.svg

В итоге получаем SVG:

emacs flamegraph

Заключение

В статье я постарался описать базовые принципы профилирования приложений в MacOS, есть и другие способы, но они являются производными от тех, которые описаны выше. По теме профилирования приложений рекомендую блог Брендана Грега.

Так же есть еще утилита xctrace, котрая делает тоже самое что и Instuments, но из консоли. Например записать профилирование процесса можно через команду:

xctrace record --template 'Time Profiler' --attach <pid процесса>

Полезные ссылки

 
comments powered by Disqus