Основы трассировки с помощью bpftrace

8 августа 2022

Тут по работе возникла задачка с PostgreSQL. Нужно было определить, как часто при определенных условиях вызываются такие-то процедуры, и что они при этом возвращают. Трейсить предстояло совсем чуть-чуть, да и не в проде, поэтому я воспользовался LLDB. Несмотря на то, что это не инструмент трассировки, в моем случае с задачей он справился. И тут я вспомнил, что еще не так давно читал про bpftrace. Хотя, конечно же, успел напрочь все позабыть. Было решено проверить, насколько лучше или хуже bpftrace подошел бы для той же задачи.

В двух словах, bpftrace — это инструмент, аналогичный DTrace, только для Linux и реализованный поверх BPF. Считается стабильным, быстрым, и чем-то, что не страшно запускать в боевом окружении.

Эксперименты проводились на Ubuntu 20.04 LTS. Чтобы установить bpftrace, просто говорим:

sudo apt install bpftrace

Bpftrace позволяет трассировать всякое, не исключая системных вызовов:

# список всего что можно трассировать
sudo bpftrace -l | less

# список системных вызовов
sudo bpftrace -l 'tracepoint:syscalls:sys_enter_*'

Например, можно в реальном времени наблюдать, какие файлы открываются какими процессами:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_open,
  tracepoint:syscalls:sys_enter_openat {
    printf("%s %s\n", comm, str(args->filename));
  }'

Подробности можно найти в статье The bpftrace One-Liner Tutorial.

Поскольку каждый раз писать такие однострочники неудобно, bpftrace идет с рядом готовых утилит. Так для трассировки открытия файлов есть opensnoop:

$ sudo opensnoop.bt

PID    COMM               FD ERR PATH
[...]
4744   cat                 2   0 /etc/passwd

Однако я напомню, что мы хотели потрейсить PostgreSQL. Чтобы это работало, СУБД должна быть собрана с флагом --enable-debug, и желательно также с -O0.

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

# это для конкретного процесса
sudo bpftrace -p 20419 -l | grep 'uprobe:'

# а это - для конкретного исполняемого файла
sudo bpftrace -l 'uprobe:/home/eax/pginstall/bin/postgres:*'

Если мы хотим трейсить не аргументы функций, а возвращаемые значения, то вместо uprobe:* нужно использовать uretprobe:*. Заметьте, что последние отсутствуют в выводе bpftrace -l. Видимо, так сделано для экономии места, поскольку списки uprobe:* и uretprobe:* будут одинаковыми.

Допустим, я хочу знать, как часто вызывается heapam_tuple_update() и что при этом возвращается:

$ sudo bpftrace -e 'uretprobe:/home/eax/pginstall/bin/postgres:
  heapam_tuple_update {
    printf("%d %s %d\n", pid, comm, retval);
  }'

Attaching 1 probe...

20553 postgres 0
20829 postgres 3

Ну что ж. Вроде, работает. Но, как мне кажется, в общем случае при разработке все же проще воспользоваться либо отладочным выводом, либо GDB / LLDB.

Никто же не помнит наизусть, как в bpftrace вывести значения всех аргументов. При том, что они могут быть какими-то структурами с большой вложенностью. Также, я не хочу разбираться, что это значит, когда heapam_tuple_update() возвращает 0 или 3. Я хочу сразу видеть, что он возвращает enum TM_Result, и значения TM_Ok и TM_Updated соответственно.

Это не означает, что bpftrace — какой-то неправильный инструмент. Просто он изначально создавался для других задач. Это в первую очередь инструмент системного администратора для анализа производительности или, например, аудита безопасности. Программисту он тоже может пригодиться, но только когда он ищет проблемы на проде без возможности модифицировать код приложения, или в каких-то таких задачах.

В качестве источника дополнительной информации о bpftrace рекомендую документацию на GitHub’е. Также обратите внимание на книги Systems Performance: Enterprise and the Cloud, Second Edition и BPF Performance Tools за авторством Brendan Gregg.

Метки: , , .


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