Установка и простые примеры использования SystemTap

24 августа 2016

Некоторое время назад мы с вами научились пользоваться 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):

deb http://ddebs.ubuntu.com trusty main restricted universe multiverse
deb http://ddebs.ubuntu.com trusty-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com trusty-security main restricted universe multiverse
deb http://ddebs.ubuntu.com trusty-proposed main restricted universe multiverse

Говорим:

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("Hello\n") exit() }'

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

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

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

sudo stap open.stp

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

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

stap -l '*'
stap -l 'syscall.*'

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

stap -L 'syscall.*'

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

#!/usr/bin/env stap

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

#!/usr/bin/env stap

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

#!/usr/bin/env stap

probe begin
{
  printf("Start\n")
}

probe process("/xx/yy/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:

#!/usr/bin/env stap

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

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

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

Метки: , , .

Подпишись через RSS, E-Mail, Google+, Facebook, Vk или Twitter!

Понравился пост? Поделись с другими: