Хорошая новость: для этого ничего ставить не надо. В каждом современном ядре с systemd уже встроен трассировщик - ftrace. Это не отдельная программа, а интерфейс прямо в ядре, которым ты управляешь через обычные файлы. В этом уроке разберём ftrace linux руками, потом наденем сверху удобную обёртку trace-cmd, а в конце честно сравним ftrace с perf и eBPF, чтобы ты понимал, какой инструмент брать в 2026 году.
Что такое ftrace и где он живёт
ftrace (function tracer) - это механизм внутри самого ядра. Когда ядро собрано с нужными опциями (CONFIG_FUNCTION_TRACER, CONFIG_FUNCTION_GRAPH_TRACER, CONFIG_DYNAMIC_FTRACE - в дистрибутивных ядрах это включено), компилятор вставляет в начало почти каждой функции ядра вызов-крючок (через -pg / fentry). Благодаря динамическому ftrace по умолчанию эти крючки заменены на nop-инструкции и стоят ровно ноль. Когда ты включаешь трассировку, ftrace на лету патчит нужные крючки в реальный вызов, пишет события в кольцевой буфер в памяти (отдельный буфер на каждый CPU), а ты потом этот буфер читаешь.
Управление - через специальную файловую систему tracefs. На современных ядрах (2026) она монтируется сюда автоматически при загрузке:
Код: Выделить всё
/sys/kernel/tracingКод: Выделить всё
sudo mount -t tracefs nodev /sys/kernel/tracingКод: Выделить всё
sudo ls /sys/kernel/tracing
available_tracers current_tracer trace
set_ftrace_filter available_events trace_pipe
set_graph_function tracing_on events
buffer_size_kb options ...- available_tracers - какие режимы (трейсеры) доступны.
- current_tracer - какой режим включён сейчас. Запись в него меняет режим И очищает буфер. По умолчанию там nop (трассировка выключена).
- set_ftrace_filter - белый список функций, которые трассируем (фильтр для function/function_graph).
- set_graph_function - для function_graph: трассировать только эти функции и всё, что они вызывают внутри (а не плоский фильтр).
- trace - снимок буфера, читаешь как обычный файл (чтение не опустошает буфер).
- trace_pipe - то же, но потоком в реальном времени (чтение опустошает буфер).
- tracing_on - 1 включить запись, 0 поставить на паузу (режим при этом сохраняется).
- buffer_size_kb - размер кольцевого буфера на CPU. По умолчанию мало, под большую запись увеличивают.

Function и function_graph: kernel trace своими руками
Посмотрим, какие режимы есть:
Код: Выделить всё
sudo cat /sys/kernel/tracing/available_tracers
function_graph function blk hwlat wakeup_dl wakeup_rt wakeup nopВАЖНО про грабли с производительностью. Если включить function на всё ядро без фильтра, система зальёт буфер миллионами событий в секунду и нагруженный сервер может ощутимо просесть или подвиснуть. Поэтому железное правило: сначала ставим фильтр, потом включаем режим. Протрассируем работу с блочным слоем - функции, имена которых начинаются на blk:
Код: Выделить всё
cd /sys/kernel/tracing
sudo bash -c 'echo "blk*" > set_ftrace_filter'
sudo bash -c 'echo function_graph > current_tracer'
sudo bash -c 'echo 1 > tracing_on'
sleep 1
sudo bash -c 'echo 0 > tracing_on'
sudo head -20 traceКод: Выделить всё
CPU DURATION FUNCTION CALLS
2) | blk_mq_submit_bio() {
2) 1.230 us | blk_mq_get_tag();
2) + 18.450 us | blk_mq_dispatch_rq_list();
2) 0.890 us | blk_account_io_start();
2) ! 142.300 us | }- CPU - на каком ядре процессора это происходило (буфер per-CPU, поэтому колонка важна).
- DURATION - сколько функция выполнялась. us это микросекунды, ns - наносекунды.
- Скобки { и } - вход в функцию и выход. Отступ показывает вложенность вызовов, как дерево. У вложенных листовых функций (которые никого не зовут) длительность пишется сразу в строке.
- Символы-маркеры важны: + значит дольше 10 us, ! - дольше 100 us, # - дольше 1000 us (1 ms), * и @ - ещё дольше. Это ftrace сам подсвечивает подозрительно долгие вызовы. Увидел "!" или "#" - вот туда и копай.
События подсистем и обёртка trace-cmd
Кроме трассировки произвольных функций, у ftrace есть готовые события (tracepoints) - заранее расставленные разработчиками ядра стабильные точки в важных местах. Их тысячи, сгруппированы по подсистемам: sched (планировщик), block (диск), net (сеть), irq, syscalls, ext4/xfs, kmem. Посмотреть список:
Код: Выделить всё
sudo head /sys/kernel/tracing/available_eventsБазовый цикл - record, потом report. Записываем все события планировщика во время команды:
Код: Выделить всё
sudo trace-cmd record -e sched dd if=/dev/zero of=/tmp/t bs=1M count=50Код: Выделить всё
sudo trace-cmd report | head
dd-4821 [001] 9023.118: sched_wakeup: comm=kworker/1:1 pid=88 prio=120
dd-4821 [001] 9023.118: sched_switch: dd:4821 [120] R ==> kworker/1:1:88Чтобы трассировать функции через trace-cmd (а не events), есть плагины function и function_graph:
Код: Выделить всё
sudo trace-cmd record -p function_graph -l 'vfs_*' cat /etc/hostname
sudo trace-cmd reportКогда хочется не текст, а картинку - есть KernelShark, графический вьюер для trace.dat (актуальная линейка - KernelShark 2.x, умеет грузить и сливать несколько trace-файлов через концепцию data streams). Он рисует временную шкалу по CPU: видно, как процессы скачут между ядрами, где простои, где всплески прерываний, где кто кого вытеснил. Запускается просто:
Код: Выделить всё
kernelshark trace.dat- Включил function без фильтра. Самая частая ошибка новичка. Сначала set_ftrace_filter (или -l у trace-cmd), и только потом запись. Иначе оверхед просадит нагруженный сервер. На проде по возможности используй events вместо широкого function - они дешевле.
- Забыл сбросить трейсер. После экспериментов верни режим в покой: echo nop > current_tracer и echo > set_ftrace_filter. Самый надёжный способ обнулить всё разом - echo 0 > tracing_on и затем trace-cmd reset. Иначе ftrace продолжит молотить в фоне и съедать такты.
- Маленький буфер - потерянные события. Кольцевой буфер перезаписывает старое новым. Если в report/KernelShark пропуски или строка "LOST EVENTS", подними buffer_size_kb или у trace-cmd флаг -b (размер в KB). Это не глюк, это переполнение.
- Путаю ftrace и strace. strace - это про системные вызовы одного процесса с границы юзерспейса (и он тормозит цель в разы из-за ptrace). ftrace - про внутренности ядра целиком и почти без оверхеда при фильтре. Разные слои и разная цена.
- Думаю, что надо писать скрипты с нуля. Не надо. У Брендана Грегга есть набор perf-tools на чистом ftrace: funcgraph (дерево вызовов функции), funccount (счётчик вызовов), funcslower (функции медленнее порога), iolatency (гистограмма латентности диска), kprobe. Это готовые шелл-обёртки, ставятся почти без зависимостей и работают даже там, где eBPF недоступен (старое ядро, урезанная сборка, отсутствие BTF).
Чтобы не брать молоток на каждый винт, держи карту в голове:
- ftrace - отвечает на "какие функции ядра вызываются, в каком порядке и сколько длятся" дёшево и из коробки, без компиляторов и пакетов. Идеален для function_graph по конкретной подсистеме и для быстрого взгляда на tracepoints.
- perf - профилирование по сэмплам (perf record/report, флеймграфы) и аппаратные счётчики PMU (кэш-промахи, IPC). Берёшь, когда вопрос "где в целом горит CPU", а не "что делает одна функция".
- eBPF (bpftrace/bcc) - когда нужна своя логика, фильтрация и АГРЕГАЦИЯ прямо в ядре, чтобы не тащить мегабайты сырых событий в юзерспейс. На современных ядрах (2026) с BTF предпочитают пробы fentry/fexit (раньше назывались kfunc/kretfunc): они вешаются на функции ядра через eBPF-трамплины с почти нулевым оверхедом, типы аргументов берут из BTF (меньше ручного каста), и - в отличие от kretprobe - в fexit доступны и аргументы, и возвращаемое значение. Готовые bcc/bpftrace инструменты (biolatency, execsnoop, runqlat, tcpconnect, opensnoop) во многих расследованиях уже заменили связки на сыром ftrace и strace.
Мини-лаба: повтори прямо сейчас
- Смонтируй tracefs (если не смонтирована) и выведи available_tracers и первые строки available_events.
- Через set_ftrace_filter ограничь трассировку функциями vfs_*, включи function_graph на секунду, прочитай trace и найди вызов с маркером "!" или "#".
- Сделай sudo trace-cmd record -e sched sleep 2, затем trace-cmd report. Найди строки sched_switch и пойми, какой процесс кого вытеснил и в каком состоянии (R/S/D) он ушёл.
- Запиши то же самое и открой trace.dat в KernelShark - найди на шкале по CPU момент переключения контекста.
- Обязательно верни систему в покой: trace-cmd reset, либо вручную echo nop > current_tracer и echo > set_ftrace_filter.
- Чем function_graph отличается от function и какое поле в его выводе показывает латентность? Что означают маркеры +, ! и #?
- Почему перед включением трассировки всего ядра обязательно задавать set_ftrace_filter, и чем events безопаснее для прода?
- Что делают команды trace-cmd record и trace-cmd report, в каком файле оказывается результат и как его посмотреть картинкой?
- Когда ты возьмёшь ftrace, а когда уйдёшь к perf или bpftrace? В чём преимущество fexit над kretprobe?
ftrace - встроенный в ядро трассировщик, рулится файлами в /sys/kernel/tracing (tracefs, монтируется сама на 2026). function_graph рисует дерево вызовов с длительностью и сам подсвечивает долгие функции маркерами +, ! и #. События (sched, block, net) - готовые стабильные tracepoints в подсистемах, дешевле сырых функций. trace-cmd record/report - удобная обёртка (ветка v3.x), KernelShark - картинка по времени. Главное правило: сначала фильтр, потом запись, и не забудь trace-cmd reset. Когда логов и strace не хватает, чтобы понять поведение ядра, - kernel trace через ftrace это твой следующий шаг, а за более умной агрегацией иди в bpftrace с fentry/fexit.