Использование DTrace на примере FreeBSD и Linux

8 августа 2016

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.

Метки: , , , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.