dtrace/

DTrace — это такая штука, присутствующая во FreeBSD, NetBSD, MacOS, Solaris и Linux . DTrace предназначен для динамической трассировки ядра системы и приложений в реальном времени, главным образом с целью их профайлинга и отладки. Сегодня мы попробуем поработать с DTrace во FreeBSD. Кроме того, мы установим DTrace и в Ubuntu, хотя по поводу стабильности такой конфигурации и остаются вопросы.

Дополнение: Спустя пару лет после публикации этого поста поддержка DTrace была добавлена в Windows . В Linux теперь есть полноценный аналог DTrace под названием bpftrace.

Чем так примечателен именно DTrace?

DTrace является не единственным решением в своем роде. Из достойных аналогов можно назвать, например, SystemTap . По сравнению с аналогами, DTrace обладает следующими примечательными свойствами:

  • Во многих системах DTrace есть из коробки и просто работает, не нужно ничего устанавливать;
  • DTrace не тормозит по 10 секунд перед запуском трассировки;
  • Технология появилась давно и является вполне зрелой, ее не страшно использовать прямо на проде;
  • Имеется подробнейшая документация;
  • В первом приближении функционал выглядит более удобно и богато, чем у аналогов. Стабильные и документированные пробы есть прямо на все события в системе — в стиле TCP-соединение установлено, TCP-соединение закрыто, и такого рода вещи. А не так, что нужно знать, за какой процедурой в коде этой версии ядра нужно следить;

Приведенные далее примеры были проверены на FreeBSD версии 10.3, но по идее должны без изменений работать и на других версиях системы.

Примеры трассировки ядра системы

Как уже отмечалось, установка не требуется, так как DTrace является частью ядра FreeBSD начиная с версии 9.2 и по умолчанию включен в GENERIC. Если вы используете старое ядро, или новое, но собранное с кастомными параметрами, здесь описывается, как включить поддержку DTrace. Описание процесса сборки ядра FreeBSD вы найдете в заметке Собираем ядро и мир FreeBSD из исходников .

Загружаем все необходимые модули:

sudo kldload dtraceall

Смотрим список доступных пробов (probe, еще можно перевести как «зонд» или даже «щуп»):

sudo dtrace -l | less
sudo dtrace -l | grep entry | wc -l

Как видите, пробов существует великое множество. У меня в системе их доступно более 28 тысяч!

В качестве примера попробуем логировать создание всех новых процессов:

sudo dtrace -n ‘proc:::exec-success { trace(curpsinfo->pr_psargs); }’

Пример вывода:

dtrace: description ‘proc:::exec-success ‘ matched 1 probe
dtrace: buffer size lowered to 2m
CPU     ID            FUNCTION:NAME
0  51735        none:exec-success   /usr/sbin/sshd -R
0  51735        none:exec-success   -csh
0  51735        none:exec-success   /usr/games/fortune freebsd-tips
^C

Другой пример — трейсим все, что как-то связано с TCP:

sudo dtrace -n ‘tcp::: { @[probename] = count(); }’

Вывод будет выглядеть как-то так:

dtrace: description ‘tcp::: ‘ matched 8 probes
dtrace: buffer size lowered to 2m
dtrace: aggregation size lowered to 2m
^C

connect-established                                               1
connect-request                                                   1
state-change                                                      5
send                                                             62
receive                                                         107

Еще вас может заинтересовать утилита dtruss:

sudo dtruss ‘ls -la’

Эта утилита трейсит используемое программой системные вызовы. То есть, делает то же самое, что и truss (или strace в Linux), только через DTrace.

Если нужно потрейсить определенные системные вызовы, совершаемые конкретным процессом, можно сделать это при помощи такого скрипта:

#!/usr/sbin/dtrace -s

syscall:freebsd:poll:entry /execname == «postgres»/
{
printf(«fds = %p, nfds = %d, timeout = %d», arg0, arg1, arg2);
}

syscall:freebsd:poll:return /execname == «postgres»/
{
printf(«result = %p», arg1)
}

Как видите, DTrace позволяет нам залезть своими грязными лапками практически в любое место системы, будь то системные вызовы, сетевой стек, или что-то еще (см полный список доступных пробов). Довольно круто, не так ли?

Пример трассировки приложения

DTrace позволяет трейсить не только ядро операционной системы, но и пользовательские приложения. В качестве примера приложения, снабженного пробами для DTrace, рассмотрим мой любимый PostgreSQL . Чтобы активировать пробы, придется собрать PostgreSQL из исходников с соответствующими флагами. Вопрос сборки PostgreSQL подробнейшим образом рассмотрен в заметке PostgreSQL: сборка из исходников и настройка под Linux . Далее предполагается, что процесс этот вам прекрасно знаком.

Нам понадобится пакет libelf:

sudo pkg install libelf

Непосредственно сборка PostgreSQL (я лично проверял на версии 9.6, но по идее должно работать и на других):

mkdir ~ / postgresql-install

CFLAGS = «-O0 -g» LDFLAGS = «-lelf»
. / configure —enable-cassert —enable-debug —enable-dtrace
—prefix = / home / afiskon / postgresql-install &&
echo ‘#undef HAVE_SETPROCTITLE’ >> . / src / include / pg_config.h &&
gmake clean && gmake -j2 -s

Для быстрой локальной установки и конфигурации PostgreSQL можно воспользоваться скриптами из этого репозитория на GitHub .

При компиляции сыпется довольно много варнингов. Насколько я смог выяснить, так и должно быть, этому багу уже лет десять . Еще я обнаружил, что Clang версий 3.7 и 3.8 почему-то не могут скомпилировать проект с такими флагами, выдавая ошибку undefined reference to `bort' . Clang версии 3.4, идущий в системе по умолчанию, собирает все без проблем. Разработчики Clang говорят, что проблема не на их стороне .

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

less . / src / backend / utils / probes.d
less . / src / backend / utils / probes.h

В общем случае список пробов для конкретного процесса можно посмотреть так:

sudo dtrace -l | grep 79470

… или, если нам известны названия пробов:

sudo dtrace -l -m postgres
sudo dtrace -l -P postgresql79470

Пример трассировки локов в PostgreSQL:

sudo dtrace -n ‘:postgres:LWLockAcquire: { printf(«name = %s, id = %d’
‘, mode = %d», copyinstr(arg0), arg1, arg2) } :postgres:LWLockRelease’
‘: { printf(«name = %s», copyinstr(arg0)) }’ -p 79670

В общем и целом, принцип тот же, что и в случае с ядром.

Профилирование с использованием DTrace

Как выяснилось, DTrace также легко справляется с профилированием кода:

sudo dtrace
-n ‘profile-4999 /execname == «postgres»/ { @[ustack(1)] = count() }’

sudo dtrace
-n ‘profile-4999 /pid == 1234/ { @[ustack()] = count() }’
-o out.dtrace

Идея заключается в том, чтобы снимать стектрейсы приложения с заданной частотой, в данном случае 4999 Гц, а затем смотреть, какие стектрейсы были сняты чаще других. Все гениальное — просто.

Если генерируемый вывод кажется вам не очень читаемым, скажите:

git clone https: // github.com / brendangregg / FlameGraph
perl . / FlameGraph / stackcollapse.pl out.dtrace > out_folded.dtrace
perl . / FlameGraph / flamegraph.pl . / out_folded.dtrace > fg.svg

В результате по собранным стектрейсам будут построены красивые флеймграфы, ничем не уступающие тем, что нам доводилось строить при помощи perf в заметке Профилирование кода на C/C++ в Linux и FreeBSD .

Использование DTraceToolkit

С моей стороны было бы большим упущением не рассказать про DTraceToolkit:

sudo pkg install dtrace-toolkit

Это такая коллекция из нескольких сотен полезных скриптов на базе DTrace:

pkg info -l dtrace-toolkit | egrep ‘.d$’

Рассмотрим некоторые из них.

Скрипт hotkernel позволяет определить, в каких процедурах ядро проводит больше всего времени:

$ sudo hotkernel

FUNCTION                                                COUNT   PCNT
zfs.ko`arc_read_done                                        1   0.0%
kernel`hpet_get_timecount                                   1   0.0%
kernel`in_lltable_lookup                                    1   0.0%
kernel`ata_end_transaction                                  1   0.0%
zfs.ko`zio_buf_free                                         1   0.0%
kernel`DELAY                                                1   0.0%
kernel`ata_pci_dmastart                                     1   0.0%
zfs.ko`txg_all_lists_empty                                  1   0.0%
kernel`ata_generic_command                                  2   0.1%
kernel`ata_tf_write                                         2   0.1%
kernel`spinlock_exit                                        2   0.1%
dtrace.ko`dtrace_dynvar_clean                               3   0.1%
kernel`ata_pci_dmastop                                      4   0.1%
kernel`acpi_cpu_c1                                       2984  99.3%

Скрипт opensnoop позволяет следить, кто какие файлы открывает:

$ sudo opensnoop

UID    PID COMM          FD PATH
1001   9147 id             3 /etc/nsswitch.conf
1001   9147 id             3 /etc/pwd.db
1001   9147 id             3 /etc/group
1001   9147 id             3 /etc/group
1001   9147 id             3 /etc/group

Скрипт procsystime позволяет определить, сколько времени процессы проводят в каких системных вызовах:

$ sudo procsystime

Elapsed Times for all processes,

SYSCALL          TIME (ns)
sigaction               2827
mmap               3400
sigreturn               4137
getpid               5032
__sysctl               9839
madvise              16798
ioctl              24210
sigprocmask              56221
read             115654
write             658697
select          998609958
_umtx_op          998813579

Скрипт shellsnoop следит за тем, кто какие команды выполняет в шелле (по ширине вывод обрезан мной):

$ sudo shellsnoop

PID  PPID      CMD DIR  TEXT
9175   799       id   W  uid=1001(afiskon) gid=1001(afiskon) groups…
9176   799      pwd   W  /home/afiskon
9177   799    uname   W  FreeBSD  10.3-RELEASE-p4 FreeBSD 10.3-RELE…

Детально рассмотреть все скрипты со всеми доступными флагами, увы, не представляется возможным. На эту тему можно вести целый отдельный блог!

Установка DTrace в Ubuntu Linux

Существует проект по портированию DTrace на ядро Linux под названием dtrace4linux.

Устанавливается dtrace4linux таким образом (я тестил на Ubuntu 16.04):

git clone https: // github.com / dtrace4linux / linux.git dtrace
sudo . / tools / get-deps.pl
make all
sudo make install
sudo make load

Проверяем:

sudo dtrace -n ‘tcp::: { @[probename] = count(); }’
sudo dtrace -n ‘syscall:::entry { @[probefunc] = count(); }’

Вроде, работает. Впрочем, если судить по ишьюсам на GitHub , использовать dtrace4linux следует с некоторой осторожностью.

Заключение

Надеюсь, мне удалось убедить вас в том, что DTrace — крутейшая штука, заслуживающая самого пристального внимания с вашей стороны. В качестве источников дополнительной информации можно рекомендовать:

А пользуетесь ли вы DTrace и если да, то какие его возможности находите наиболее ценными?

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

EnglishRussianUkrainian