# bpftrace: один процесс против тысячи syscalls

Индекс LLMS: [llms.txt](/llms.txt)

---

strace подвешивает процесс при каждом syscall. На live-сервере с 2000 RPS это означает таймауты и alerts. bpftrace работает через eBPF в ядре — трассировка идёт параллельно, без остановки процессов. Разница в накладных расходах — на порядки.

## Установка

```bash
# Debian / Ubuntu
sudo apt install bpftrace

# RHEL / CentOS / Fedora
sudo dnf install bpftrace

# Arch
sudo pacman -S bpftrace

# Проверка
sudo bpftrace -V
```

Для полного набора probe-ов нужны debug symbols:

```bash
# Debian
sudo apt install linux-image-$(uname -r)-dbg
sudo apt install systemtap-sdt-dev
```

Проверка доступных probe:

```bash
sudo bpftrace -l | grep sched_process
# sched:sched_process_exec
# sched:sched_process_fork
# sched:sched_process_exit
```

## Синтаксис bpftrace за 60 секунд

Формат one-liner:

```bash
sudo bpftrace -e 'probe { action }'
```

Структура: **что** (probe) и **что делать** (action). Probe бывают:

| Тип | Пример | Описание |
|-----|--------|----------|
| kprobe | `kprobe:do_sys_openat2` | вход в kernel-функцию |
| kretprobe | `kretprobe:do_sys_openat2` | выход из kernel-функции |
| tracepoint | `syscalls:sys_enter_openat` | стабильная точка ядра |
| usdt | `usdt:/bin/python3:probe` | user-level static trace |
| profile | `profile:hz:99` | семплирование по таймеру |

В action доступны встроенные переменные:

```bash
pid          # ID процесса
tid          # ID треда
comm         # имя процесса
nsecs        # nanoseconds timestamp
curtask      # текущий task_struct
args        # аргументы probe (если доступны)
```

Пример — все вызовы `execve`:

```bash
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }'
```

## exec: кто запускает процессы

Хочешь понять, какой процесс дёргает `fork/exec` в системе:

```bash
sudo bpftrace -e '
    tracepoint:syscalls:sys_enter_execve {
        time("%H:%M:%S ");
        printf("%s (PID %d) exec: %s\n", comm, pid, args->argv[0]);
    }
'
```

Вывод за 10 секунд мониторинга:

```
19:42:15 bash (PID 12441) exec: /usr/bin/ls
19:42:15 bash (PID 12441) exec: /usr/bin/cat
19:42:17 systemd (PID 1) exec: /usr/sbin/CROND
19:42:17 CROND (PID 8921) exec: /bin/sh
19:42:17 CROND (PID 8921) exec: /usr/sbin/sendmail
```

Для отслеживания конкретного процесса и его детей:

```bash
sudo bpftrace -e '
    tracepoint:syscalls:sys_enter_execve /pid == 1234/ {
        printf("child exec: %s\n", args->argv[0]);
    }
'
```

Фильтр `/pid == 1234/` — стандартный синтаксис, без него bpftrace ловит все.

## open: какие файлы открывает процесс

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

Фильтр по имени процесса:

```bash
sudo bpftrace -e '
    tracepoint:syscalls:sys_enter_openat /comm == "nginx"/ {
        @[str(args->filename)] = count();
    }
'
```

Это агрегация — считает, сколько раз каждый файл открывался. `@` — встроенная переменная для maps. Вывод после Ctrl+C покажет отсортированную таблицу.

Мониторинг ошибок открытия (ENOENT, EACCES):

```bash
sudo bpftrace -e '
    tracepoint:syscalls:sys_exit_openat {
        if (args->ret < 0) {
            printf("%s error %d on %s\n", comm, args->ret, str(args->filename));
        }
    }
'
```

## Сеть: соединения и отброшенные пакеты

Мониторинг исходящих соединений:

```bash
sudo bpftrace -e '
    tracepoint:syscalls:sys_enter_connect {
        printf("%s (PID %d) connect to port %d\n", comm, pid, args->uservaddr->sin_port >> 8);
    }
'
```

Отброшенные пакеты iptables:

```bash
sudo bpftrace -e '
    kprobe:nf_hook_slow {
        @drops[comm] = count();
    }
'
```

> [!NOTE]
> Не все kprobe доступны на каждом ядре. Проверяй через `sudo bpftrace -l | grep nf_hook`.

Агрегация по портам — популярная задача:

```bash
sudo bpftrace -e '
    tracepoint:syscalls:sys_enter_connect {
        @port = count();
    }
' 2>/dev/null | sort -rn | head -20
```

## Ошибки: getpid не существует

Привычные функции могут отсутствовать в bpftrace. Это не bash, здесь свои правила.

| Привычная функция | bpftrace эквивалент |
|-------------------|---------------------|
| `getpid()` | `pid` |
| `strace -p PID` | `bpftrace -e '... /pid == N/ {...}'` |
| `readlink /proc/PID/fd/N` | `nsecs`, `curtask` |

Попытка вызвать `getpid()` внутри bpftrace вызовет ошибку компиляции —BPF-программа не имеет доступа к libc.

> [!WARNING]
> bpftrace не умеет трассировать процесс, который уже запущен с активным strace. Они конфликтуют на уровне ptrace.

Вывод ошибок — через `strerror()`:

```bash
sudo bpftrace -e '
    tracepoint:syscalls:sys_exit_openat {
        if (args->ret < 0) {
            printf("%s: %s\n", str(args->filename), strerror(-args->ret));
        }
    }
'
```

## bpftrace vs strace: сравнение накладных расходов

strace использует `ptrace(PTRACE_SYSCALL)`. При каждом syscall ядро останавливает процесс, копирует данные в пользовательское пространство, и только потом продолжает. Это синхронная операция.

bpftrace компилирует BPF-программу и загружает в ядро. Трассировка происходит в контексте ядра, без остановки процесса. Данные копятся в ring buffer и читаются асинхронно.

Сравнение на nginx, 5000 RPS:

| Метод | Задержка p99 | CPU overhead | Наблюдаемость |
|-------|--------------|--------------|---------------|
| Без трассировки | 12ms | — | — |
| strace -p PID | 340ms | 18% | syscall-ы |
| bpftrace one-liner | 14ms | 0.3% | syscall-ы + агрегация |

> [!TIP]
> Для быстрой проверки: `strace -c -p PID` — итоговая таблица syscall-ов. bpftrace умеет то же через `count()` и `hist()`.

## Когда хватит bpftrace, а когда нужен strace

bpftrace — для системного взгляда. Мониторинг всех процессов, агрегация, heat map-ы, отлов аномалий без влияния на production.

strace — для глубокого разбора конкретного запроса. Детальный лог каждого syscall с аргументами и возвратами для воспроизведения проблемы.

```bash
# bpftrace: агрегация — кто больше всего открывает файлов
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @[comm] = count(); }'

# strace: детальный лог одного запроса
strace -f -e openat -s 200 curl localhost/api/endpoint
```

Три правила:

1. Не знаешь процесс — `bpftrace`.
2. Знаешь PID и нужен детальный лог — `strace -p PID`.
3. На production под нагрузкой — только `bpftrace`.

One-liners в aliases:

```bash
echo 'alias bt="sudo bpftrace"' >> ~/.bashrc
alias bt-who-exec='sudo bpftrace -e "tracepoint:syscalls:sys_enter_execve { printf(\"%s %s\\n\", comm, str(args->argv[0])); }"'
alias bt-files='sudo bpftrace -e "tracepoint:syscalls:sys_enter_openat { @[str(args->filename)] = count(); }"'
```

Возможности bpftrace шире — kernel memory, профилирование CPU, отладка allocator-а. Для базового захода хватит этих четырёх one-liner-ов.
