Установка и простые примеры использования SystemTap
24 августа 2016
Некоторое время назад мы с вами научились пользоваться DTrace. SystemTap представляет собой нечто похожее, но сильно заточенное под Linux и с рядом важных отличий. Во-первых, SystemTap не требует ручного добавления пробов в код ядра или приложений. Он работает и так, правда, требуя взамен знания исходников. Во-вторых, скрипты SystemTap транслируются в язык C и загружаются в ядро в виде модулей. За это приходится платить временем компиляции скриптов. Зато скриптовый язык SystemTap намного мощнее, чем у DTrace.
Установка SystemTap из бинарных пакетов
Описанные ниже действия были проверены мной на Ubuntu Linux версий 14.04 и 16.04. Для других версий Ubuntu и других дистрибутивов Linux отличия, по идее, должны быть небольшими.
Первым делом говорим:
Далее следуем инструкциям из /usr/share/doc/systemtap/README.Debian. В моем случае потребовалось выполнить:
Создаем /etc/apt/sources.list.d/ddebs.list (замените слово trusty на название вашей версии Ubuntu, см /etc/lsb-release):
' universe multiverse\ndeb http://ddebs.ubuntu.com trusty-updates '\
'main restricted universe multiverse\ndeb http://ddebs.ubuntu.com '\
'trusty-security main restricted universe multiverse\ndeb http://'\
'ddebs.ubuntu.com trusty-proposed main restricted universe '\
'multiverse" > /etc/apt/sources.list.d/ddebs.list'
Говорим:
Для ключей, на которые получили ругань в стиле:
signatures couldn't be verified because the public key is not
available: NO_PUBKEY C8CAB6595FDFF622
… говорим:
--recv-keys C8CAB6595FDFF622
Повторяем update, теперь все должно получиться.
Теперь очень внимательно смотрим версию следующего пакета:
Сравниваем с выводом:
Важно! Пакеты должны быть вот прямо одной версии. Например, linux-image-4.2.0-27-generic
версии 4.2.0-27.32~14.04.1
и linux-image-4.2.0-27-generic-dbgsym
версии 4.2.0-27.32
(без тильды и прочего) не будут работать вместе и вы будете получать очень странные ошибки.
Если версии совпадают, тогда ставим:
Обратите внимание, что размер пакета составляет (!) 460 Мб или около того, и это в сжатом виде. После установки у вашего диска станет на 3.3 Гб меньше свободного места. Если у вас не очень быстрый интернет, запаситесь терпением!
Компиляция SystemTap из исходников
Описанным выше образом вы получите далеко не самую новую версию SystemTap. В Ubuntu 14.04 будет установлен SystemTap версии 2.3, выпущенный в 2013 году. Даже при выполнении простейший скриптов он падает с очень странными ошибками. На Ubuntu 16.04 будет установлен SystemTap 2.9, датированный 2015-м годом. Им в принципе можно пользоваться. Однако пример с построением флеймграфов, который мы рассмотрим далее, у меня в нем не заработал, так как проб просто не компилировался.
Постойте, но ведь мы же с вами программисты! Давайте соберем новейший (во всяком случае, на момент написания этих строк) SystemTap 3.0 прямо из исходников:
sudo apt-get install m4 g++ gettext libz-dev
wget https://sourceware.org/systemtap/ftp/releases/systemtap-3.0.tar.gz
wget 'https://fedorahosted.org/releases/e/l/elfutils/'\
'0.159/elfutils-0.159.tar.bz2'
tar -xvzf systemtap-3.0.tar.gz
tar -xvjf elfutils-0.159.tar.bz2
cd systemtap-3.0
./configure --with-elfutils=../elfutils-0.159
make -j4
sudo make install
Заметьте, что здесь используется не самый новый elfutils-0.159. Дело в том, что с последним elfutils-0.165 SystemTap не собирается. В рассылке по этому поводу сказали, что в системе старый glibc. Поэтому я использовал менее новый elfutils версии 0.159, прямо как написано у SystemTap в README.
Использование SystemTap
Рассмотрим некоторые примечательные свойства языка SystemTap:
- Как уже отмечалось, код транслируется в С, компилируется в модуль ядра и загружается;
- Точки с запятой в конце выражения не являются обязательными, и вообще на самом деле означают пустое выражение;
- Можно использовать локальные переменные, просто присвоив чему-нибудь значение;
- Вывод типов (строковый, числовой) производится автоматически на этапе компиляции;
- Также есть тип «агрегаты» (aggregates), запись в которые производятся специальным оператором
<<<
, и для доступа к которым есть функции@min
,@max
,@avg
,@count
,@avg
, а также функции для вывода —@hist_log
и@hist_linear
. Агрегаты не требуют тяжелых блокировок, так как данные хранятся отдельно на каждом ядре процессора, и собираются воедино только при необходимости; - Еще SystemTap поддерживает ассоциативные массивы, реализованные, как хэш-таблицы, чей максимальный размер фиксирован и задается при старте скрипта. Ассоциативные массивы могут быть только глобальными;
- Поддерживаются управляющие конструкции
if-then-else
,while
,for
,foreach
,break
,continue
; - Можно объявлять свои функции, при этом аргументы передаются по значению;
- Также можно писать библиотеки (так называемые tapsets), складывая свои .stp файлы в /usr/share/systemtap/tapset, или используя аргумент
-I path
; - Глобальные переменные, используемые в пробе, автоматически блокируются должным образом при входе в проб;
- Вся память выдиляется один раз при инициализации скрипта и потому утечки памяти невозможны;
- Если хэндлер выполняется слишком долго или уходит слишком глубоко в рекурсию, это обнаруживается и выполнение скрипта прекращается;
- Вы можете смело делить на ноль и разыменовывать null — ваш скрипт от этого грохнется, но ядру ничего не будет;
- Продвинутые пользователи могут использовать в скриптах код на Си;
Пример выполнения однострочника:
Теперь рассмотрим скрипт посложнее. Этот скрипт трейсит системные вызовы open и close:
global close_count
probe begin
{
printf("Begin\n")
open_count = 0
close_count = 0
}
probe syscall.open
{
printf("%s(%d) open (%s)\n", execname(), pid(), argstr)
open_count++
}
probe syscall.open.return
{
printf("open returns: %s\n", $$return)
}
probe syscall.close
{
printf("%s(%d) close (%s)\n", execname(), pid(), $$parms)
close_count++
}
probe syscall.close.return
{
printf("close returns: %s\n", $$return)
}
probe timer.ms(5000)
{
exit()
}
probe end
{
printf("End, open_count = %d, close_count = %d\n",
open_count, close_count)
}
Запуск скрипта:
В соседнем терминале выполните cat .vimrc
и посмотрите, что будет выведено.
Посмотреть список всех доступных пробов можно так:
stap -l 'syscall.*'
Тоже самое, только с выводом списка доступных локальных переменных для каждого проба:
Пример трассировки конкретной ядерной процедуры:
probe begin
{
printf("Start\n")
}
probe kernel.function("ip_rcv")
{
printf("ip_rcv(%s)\n",$$parms)
}
probe timer.ms(5000)
{
exit()
}
probe end
{
printf("End\n")
}
А так трейсятся все процедуры из заданного .c файла:
probe kernel.function("*@net/ipv4/ip_input.c")
{
printf("ip_input.c, time=%u funcion = %s\n",
gettimeofday_s(), probefunc())
}
probe timer.ms(10000)
{
exit()
}
Пример трассировки обычных приложений (см исходники к заметке Не унылый пост о списках и деревьях поиска в языке C):
probe begin
{
printf("Start\n")
}
probe process("/x/y/htable_example").statement("*@htable_example.c:44")
{
printf("run_test(%s)\n",$$parms)
}
probe timer.ms(5000)
{
exit()
}
probe end
{
printf("End\n")
}
Для красивого расставления отступов в выводе используйте thread_indent:
probe kernel.function("*@net/socket.c").call {
printf ("%s -> %s\n", thread_indent(1), ppfunc())
}
probe kernel.function("*@net/socket.c").return {
printf ("%s <- %s\n", thread_indent(-1), ppfunc())
}
Другие полезняшки:
tid() |
Идентификатор текущей нитки |
pid() |
Идентификатор процесса (task group) текущей нитки |
uid() |
ID пользователя |
execname() |
Имя текущего процесса |
cpu() |
Номер текущего CPU |
gettimeofday_s() |
Сколько секунд прошло с начала эпохи |
get_cycles() |
Снапшот хардварного счетчика циклов |
pp() |
Строка, описывающая сработавший проб (probe point) |
ppfunc() |
Имя функции, в которую был помещен сработавший проб |
$$vars |
Список всех локальных переменных в текущем скоупе |
print_backtrace() |
Вывести ядерный стектрейс |
print_ubacktrace() |
Вывести пользовательский стектрейс |
Еще из полезных сведений о скриптовом языке SystemTap стоит отметить, что преобразование числа в строку осуществляется при помощи функции sprint
, а конкатенация строк — при помощи оператора «точка».
Приобретенных знаний вам должно более чем хватить на первое время, а подробности в случае необходимости вы найдете в манах (они, кстати, у SystemTap классные!) и в документации на официальном сайте.
Построение флеймграфов при помощи SystemTap
Напоследок рассмотрим задачу профилирования пользовательского приложения с построением красивых флеймграфов. Принцип тот же, что и у DTrace — собрать стектрейсы, по ним пострить флеймграф:
'probe timer.hz(4999) {print_ubacktrace();printf("\t1\n")}' \
-d /path/to/executable --ldd -x PID > bt.log
git clone https://github.com/brendangregg/FlameGraph
perl ./FlameGraph/stackcollapse-stap.pl bt.log | \
grep -v 'no user backtrace at' > bt_collapsed.log
perl ./FlameGraph/flamegraph.pl bt_collapsed.log > fg.svg
Ничего сложного. Но, как мне кажется, в Linux для решения этой задачи удобнее использовать perf.
Заключение
Как видите, боли при использовании SystemTap несколько больше, нежели при использовании DTrace. С другой стороны, это сильно разные решения.
SystemTap — скорее инструмент для разработчиков, предполагающей знание исходного кода анализируемого приложения. В свою очередь, DTrace является больше инструментом администратора и предполагает наличия готовых пробов. Впрочем, как уже ранее отмечалось, DTrace может успешно использоваться и программистами, как для отладки, так и для профайлинга.
Ссылки по теме:
- Официальный сайт SystemTap с классными туториалами в PDF;
- Больше примеров использования SystemTap — раз и два;
- Systems Performance: Enterprise and the Cloud, крутейшая книга;
А используете ли вы SystemTap и если да, то для решения каких задач?
Дополнение: Вас также может заинтересовать статьи Трассировка и профайлинг в Linux с помощью bcc/eBPF, Скандальная правда об отладке ядерного кода в Linux, и далее по ссылкам.
Метки: Linux, Оптимизация, Отладка.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.