В этом уроке разберём, как через perf найти ту самую функцию-пожирателя. Сначала живой профиль через perf top, потом запись со стеками вызовов (perf record и perf report), разберёмся с символами и debuginfo (почему вместо имён функций часто вылезают голые адреса), затронем актуальный на 2026 год eBPF-инструментарий и подведём к флеймграфам, которым посвящён отдельный урок 38.
Как работает perf и почему он почти ничего не стоит
Сразу о главном страхе новичка: "а не положу ли я нагруженный прод, запустив профайлер?" Нет. perf использует сэмплирование (sampling). Он не следит за каждой инструкцией - это было бы дорого. Вместо этого он много раз в секунду дёргает процессор через аппаратные счётчики (PMU) или программный таймер и спрашивает: "А что ты выполняешь прямо в этот момент? На какой функции стоит указатель инструкций?" Записал адрес и стек - отпустил. И так тысячи раз в секунду на каждом ядре.
Аналогия: чтобы понять, чем человек занят весь день, не нужна камера, пишущая 24 часа. Достаточно фотографировать его раз в минуту. Если на 80% снимков он за компьютером - вывод очевиден. perf делает ровно это с функциями. Чем чаще функция попадает в "кадр", тем больше процессорного времени она ест. Накладные расходы такого подхода на разумной частоте - доли процента, поэтому perf можно гонять и на боевых серверах.
perf - это cpu профайлер linux, который видит всё насквозь: и код твоего приложения в user space, и то, что происходит внутри ядра (системные вызовы, работа с сетью, файловой системой). Это важно: если приложение "тонет в ядре", обычные инструменты вроде top покажут только высокий %sy в общей строке, а perf покажет конкретную функцию ядра, которая ест время.
Ставится из пакета: на Debian/Ubuntu это linux-tools-$(uname -r) плюс linux-tools-generic, на RHEL/Fedora и в отечественных Astra Linux / RED OS - пакет perf. Версия perf привязана к ядру, ставь под свою (на 2026 актуально ядро 6.x).

perf top - живой профиль: кто грузит cpu linux прямо сейчас
Когда нагрузка идёт ВОТ СЕЙЧАС, не нужно ничего записывать. Команда perf top - это как top, только не по процессам, а по функциям. Живой, обновляющийся список самых прожорливых функций в системе.
Код: Выделить всё
sudo perf topКод: Выделить всё
sudo perf top -F 99Код: Выделить всё
Samples: 41K of event 'cpu-clock', 99 Hz, Event count (approx.): 9876543210
Overhead Shared Object Symbol
23.41% myapp [.] json_parse_value
11.07% [kernel] [k] copy_user_enhanced_fast_string
8.92% libc.so.6 [.] __memcpy_avx_unaligned
4.10% myapp [.] hash_lookup- Overhead - доля сэмплов, пришедшихся на эту функцию. Грубо - сколько процентов времени CPU там провёл. 23% на одной функции это много, есть на что смотреть. Порог внимания на практике - примерно от 5%: ниже обычно шум, выше - кандидат на разбор.
- Shared Object - откуда функция. myapp - твой бинарник. libc.so.6 - системная библиотека C. А [kernel] - это код ядра.
- Symbol - имя функции. Метка [.] значит user space (твой код или библиотека), а [k] - kernel space (ядро).
Полезные клавиши прямо в perf top: стрелками встаёшь на функцию, Enter и потом Annotate - увидишь ассемблер функции с разбивкой нагрузки по конкретным инструкциям (видно, на какой строке кода затык). Клавиша E разворачивает дерево вызовов, если запущено со стеками. Выход - q.
perf record и perf report - запись со стеками вызовов
perf top отвечает "какая функция жжёт CPU". Но часто этого мало. Допустим, виновата memcpy. И что? Её зовут из ста мест. Нужен контекст: КТО её вызывает. Для этого нужна запись со стеками вызовов (call graph). Тут на сцену выходит perf record.
Ключевой флаг - -g, он включает запись стека вызовов на каждом сэмпле. То есть perf фиксирует не только текущую функцию, но и всю цепочку: кто кого позвал.
Код: Выделить всё
sudo perf record -F 99 -g -p 12345 -- sleep 30Теперь разбираем запись:
Код: Выделить всё
sudo perf report- Self - сколько времени потрачено в самой этой функции (её собственный код, без вызванных).
- Children - сколько времени потрачено в ней И во всех функциях, которые она вызвала, суммарно.
Символы и debuginfo: почему видишь адреса вместо имён
Очень частая боль новичка. Открываешь perf report, а там вместо имён функций - такое:
Код: Выделить всё
18.22% myapp [.] 0x000000000004a1f0
9.05% myapp [.] 0x0000000000051b3cЧто делать:
- Для своего приложения - собирай с флагом -g (отладочная информация) и не делай strip для профилируемой сборки. Для интерпретируемых/JIT-языков (Java, Node.js, .NET) нужны perf-map-агенты, чтобы JIT-адреса превращались в имена.
- Для системных библиотек и ядра - доставь debuginfo. На RHEL/Fedora: sudo dnf debuginfo-install glibc. На Debian/Ubuntu подключают репозиторий ddebs и ставят пакеты вида libc6-dbg. В Astra Linux / RED OS ищи пакеты с суффиксом -dbgsym или -debuginfo в штатных репозиториях.
- Символы ядра обычно доступны через /proc/kallsyms (нужен sudo, иначе адреса занулены настройкой kernel.kptr_restrict).
Если же код собран с -fomit-frame-pointer, стек получится обрезанным или фальшивым. Тогда переключаются на DWARF-разворачивание:
Код: Выделить всё
sudo perf record -F 99 --call-graph dwarf -p 12345 -- sleep 30Где сейчас уместен eBPF, а где perf (актуально на 2026)
perf - не единственный и не всегда лучший инструмент. К 2026 году eBPF дозрел и во многих задачах оттеснил классику.
- profile из bcc - eBPF-профайлер, который агрегирует стеки прямо в ядре и отдаёт уже свёрнутые счётчики, минуя огромный perf.data. Для быстрого on-CPU снимка часто удобнее: profile -F 99 -p 12345 30. Под капотом то же сэмплирование, но дешевле по диску.
- bpftrace - однострочники для точечной трассировки. Заменил многие сценарии strace там, где важен минимальный overhead. Пример снимка стеков: bpftrace -e 'profile:hz:99 /pid==12345/ { @[ustack] = count(); }'.
- Continuous profiling - Parca и Grafana Pyroscope (проекты слились) собирают eBPF-профили всего парка машин непрерывно и хранят историю. Это уже не "запустил руками раз в день", а постоянный поток профилей, по которому видно регрессии после релиза. Для прода 2026 - стандарт де-факто.
Типичные грабли и заблуждения
- "Запущу perf и убью сервер". Нет. Сэмплирование на 99 Гц - копейки. Бойся не overhead, а размера perf.data при --call-graph dwarf на длинной записи (следи за местом на диске).
- "Высокий Self у main - вот и виновник". Перепутаны Self и Children. У контейнерных функций (main, циклы событий) Self почти всегда мал. Ищи большой Self, а не Children.
- "Адреса вместо имён - perf сломан". perf в порядке, не хватает символов/debuginfo. Лечится пакетами, не переустановкой perf.
- "Стек обрывается на двух кадрах". Это обрезанные fp-стеки из пакета без фрейм-пойнтеров. На свежих Ubuntu/Fedora это уже редкость, но для старых сборок спасает --call-graph dwarf.
- Профилирование меньше пары секунд даёт слишком мало сэмплов, статистика врёт. Дай записи хотя бы 10-30 секунд под нагрузкой.
- Забыл sudo или упёрся в paranoid. Доступ к событиям регулирует /proc/sys/kernel/perf_event_paranoid. Уровни: 2 - запрещено профилирование ядра непривилегированным, 1 - запрещены CPU-события, 0 - разрешены tracepoint'ы, -1 - можно почти всё. Апстрим-дефолт 2, многие дистрибутивы ставят 1. Если perf ругается на права: sudo sysctl kernel.perf_event_paranoid=1 (временно, до перезагрузки).
- Создай нагрузку в отдельном терминале: запусти yes > /dev/null или поставь stress-ng --cpu 1.
- Запусти sudo perf top -F 99. Найди в верхушке свою нагрузку. Посмотри на колонку Overhead и метки [.] / [k].
- Возьми PID нагрузки (через pidof или top) и сними запись: sudo perf record -F 99 -g -p ПИД -- sleep 15.
- Разбери: sudo perf report. Найди функцию с самым большим Self. Разверни дерево через Enter и +, посмотри, кто её вызвал.
- Сравни с eBPF (если есть bcc): sudo profile -F 99 -p ПИД 15 - получишь свёрнутые стеки без огромного perf.data.
- Бонус к уроку про флеймграфы: sudo perf record -F 99 -ag -- sleep 30, затем perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > cpu.svg (скрипты из репозитория FlameGraph Брендана Грегга). Открой SVG в браузере - и весь профиль виден одной картинкой.
- Почему perf можно запускать на боевом сервере и чем сэмплирование отличается от трассировки каждой инструкции?
- Чем отличаются колонки Self и Children в perf report и в какой из них искать настоящего пожирателя CPU?
- Ты видишь в отчёте шестнадцатеричные адреса вместо имён функций. В чём причина и как это починить для своего приложения и для системных библиотек?
- Когда стоит переключиться с разворачивания стека по frame pointer на --call-graph dwarf, какой ценой и почему на свежих Ubuntu/Fedora это теперь нужно реже?
perf - это профайлер, который через сэмплирование почти бесплатно показывает, какие функции жгут CPU, и в приложении, и в ядре. perf top - живой профиль для "горит прямо сейчас". perf record -g плюс perf report - запись со стеками, чтобы понять не только ЧТО, но и КТО это вызвал. Голые адреса вместо имён лечатся символами и debuginfo, а не переустановкой. Смотри на Self, чтобы найти виновника, и на Children, чтобы понять путь. На 2026 фрейм-пойнтеры в дистрибутивах вернулись, а для постоянного наблюдения за CPU всё чаще берут eBPF (profile из bcc, bpftrace, continuous profiling через Parca/Pyroscope). А когда профиль большой и хочется увидеть всю картину разом - превращаем его во флеймграф, об этом в уроке 38.