systemtap/

Некоторое время назад мы с вами научились пользоваться DTrace . SystemTap представляет собой нечто похожее, но сильно заточенное под Linux и с рядом важных отличий. Во-первых, SystemTap не требует ручного добавления пробов в код ядра или приложений. Он работает и так, правда, требуя взамен знания исходников. Во-вторых, скрипты SystemTap транслируются в язык C и загружаются в ядро в виде модулей. За это приходится платить временем компиляции скриптов. Зато скриптовый язык SystemTap намного мощнее, чем у DTrace.

Установка SystemTap из бинарных пакетов

Описанные ниже действия были проверены мной на Ubuntu Linux версий 14.04 и 16.04. Для других версий Ubuntu и других дистрибутивов Linux отличия, по идее, должны быть небольшими.

Первым делом говорим:

sudo apt-get install systemtap gcc

Далее следуем инструкциям из /usr/share/doc/systemtap/README.Debian. В моем случае потребовалось выполнить:

sudo apt-get install linux-headers-$ ( uname -r )

Создаем /etc/apt/sources.list.d/ddebs.list (замените слово trusty на название вашей версии Ubuntu, см /etc/lsb-release):

sudo sh -c ‘echo «deb http://ddebs.ubuntu.com trusty main restricted’
‘ 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’

Говорим:

sudo apt-get update

Для ключей, на которые получили ругань в стиле:

W: GPG error: http://ddebs.ubuntu.com trusty Release: The following
signatures couldn’t be verified because the public key is not
available: NO_PUBKEY C8CAB6595FDFF622

… говорим:

sudo apt-key adv —keyserver keyserver.ubuntu.com
—recv-keys C8CAB6595FDFF622

Повторяем update, теперь все должно получиться.

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

sudo apt-cache madison linux-image-$ ( uname -r ) -dbgsym

Сравниваем с выводом:

dpkg -l | grep linux-image-$ ( uname -r )

Важно! Пакеты должны быть вот прямо одной версии. Например, 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 (без тильды и прочего) не будут работать вместе и вы будете получать очень странные ошибки .

Если версии совпадают, тогда ставим:

sudo apt-get install linux-image-$ ( uname -r ) -dbgsym

Обратите внимание, что размер пакета составляет (!) 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 remove systemtap
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 — ваш скрипт от этого грохнется, но ядру ничего не будет;
  • Продвинутые пользователи могут использовать в скриптах код на Си;

Пример выполнения однострочника:

sudo stap -e ‘probe begin { printf(«Hellon») exit() }’

Теперь рассмотрим скрипт посложнее. Этот скрипт трейсит системные вызовы open и close:

global open_count
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)
}

Запуск скрипта:

sudo stap open.stp

В соседнем терминале выполните cat .vimrc и посмотрите, что будет выведено.

Посмотреть список всех доступных пробов можно так:

stap -l ‘*’
stap -l ‘syscall.*’

Тоже самое, только с выводом списка доступных локальных переменных для каждого проба:

stap -L ‘syscall.*’

Пример трассировки конкретной ядерной процедуры:

#!/usr/bin/env stap

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 файла:

#!/usr/bin/env stap

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 ):

#!/usr/bin/env stap

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:

#!/usr/bin/env stap

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 — собрать стектрейсы, по ним пострить флеймграф:

sudo stap -e
‘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 и если да, то для решения каких задач?

Дополнение: Вас также может заинтересовать статьи Трассировка и профайлинг в Linux с помощью bcc/eBPF , Скандальная правда об отладке ядерного кода в Linux , и далее по ссылкам.

EnglishRussianUkrainian