Некоторое время назад мы с вами научились пользоваться 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 multiversendeb http://ddebs.ubuntu.com trusty-updates ‘
‘main restricted universe multiversendeb http://ddebs.ubuntu.com ‘
‘trusty-security main restricted universe multiversendeb 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(«Beginn»)
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: %sn», $$return)
}
probe syscall.close
{
printf(«%s(%d) close (%s)n», execname(), pid(), $$parms)
close_count++
}
probe syscall.close.return
{
printf(«close returns: %sn», $$return)
}
probe timer.ms(5000)
{
exit()
}
probe end
{
printf(«End, open_count = %d, close_count = %dn»,
open_count, close_count)
}
Запуск скрипта:
В соседнем терминале выполните cat .vimrc
и посмотрите, что будет выведено.
Посмотреть список всех доступных пробов можно так:
stap -l ‘syscall.*’
Тоже самое, только с выводом списка доступных локальных переменных для каждого проба:
Пример трассировки конкретной ядерной процедуры:
probe begin
{
printf(«Startn»)
}
probe kernel.function(«ip_rcv»)
{
printf(«ip_rcv(%s)n»,$$parms)
}
probe timer.ms(5000)
{
exit()
}
probe end
{
printf(«Endn»)
}
А так трейсятся все процедуры из заданного .c файла:
probe kernel.function(«*@net/ipv4/ip_input.c»)
{
printf(«ip_input.c, time=%u funcion = %sn»,
gettimeofday_s(), probefunc())
}
probe timer.ms(10000)
{
exit()
}
Пример трассировки обычных приложений (см исходники к заметке Не унылый пост о списках и деревьях поиска в языке C ):
probe begin
{
printf(«Startn»)
}
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(«Endn»)
}
Для красивого расставления отступов в выводе используйте thread_indent:
probe kernel.function(«*@net/socket.c»).call {
printf («%s -> %sn», thread_indent(1), ppfunc())
}
probe kernel.function(«*@net/socket.c»).return {
printf («%s <- %sn», 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(«t1n»)}’
-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 , и далее по ссылкам.