awesome-everything EN
↑ Обратно к восхождению

Наблюдаемость

Flame graph: читаем картинку, которая показывает, куда ушло время

Суть Profiler сэмплирует call stack 100 раз в секунду и строит flame graph — самый широкий frame на любом уровне — это функция, поедающая CPU, и её видно с первого взгляда.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 12 min

Trace говорит «1.2 секунды в inventory». Есть логи, метрики, дашборды — но ни один из них не скажет, какая именно функция внутри inventory съела время. Profiling отвечает за 60 секунд, без отладчика и без угадывания.

Что делает profiler

Profiler прерывает программу 100 раз в секунду и захватывает текущий call stack — цепочку функций от main до того, что выполняется прямо сейчас. За 30 секунд набирается 3000 снимков. Функция, чаще всего встречающаяся в снимках, — та, что больше всего ест CPU.

Flame graph визуализирует это: стеки отсортированы alphabetically по x-оси, ширина пропорциональна числу сэмплов, y-ось — глубина от точки входа внизу до leaf-функции наверху. Самый широкий frame на любом уровне — самый горячий путь.

Метафора со стадионом

Представь стадион со 100 000 людьми, занятых разным. Вертолёт пролетает сверху 100 раз в секунду и фотографирует, кто что делает. За минуту — 6000 фото. Посчитай, какая активность встречается чаще всего — туда уходит «CPU-время» толпы.

Flame graph — столбчатая диаграмма этих счётчиков, с вызывающими под вызываемыми. Широкие бары = популярные активности. Вертолёт — profiler; фото — стек-сэмплы; диаграмма — flame graph.

Чтение flame graph на практике

Bea on-call. Inventory-сервис, p99 = 1.5 с. Она открывает continuous-profile dashboard, фильтрует по trace-id, видит flame graph с одним массивным 1.1-секундным блоком: json.Marshal внутри serializeResponse. Фикс очевиден: кешировать маршаленный ответ или пре-кодировать при записи. Без profiling команда гадала бы — БД? Кеш? Сеть? С flame graph гадать не нужно.

ОсьЗначениеЧастая ошибка
Ширина (x)Число сэмплов — доля CPU-времениЧитают слева-направо как «порядок времени» — это НЕ ТАК
Позиция (x)Alphabetical-группировка по parent’уЛевый frame НЕ выполняется раньше правого
Высота (y)Глубина стека — main внизу, leaf наверхуБолее высокий стек = более глубокая вложенность, не медленнее

Как захватить CPU-профиль с помощью pprof

Node API — внезапный скачок p99. Tracing находит медленный span. Continuous-profile dashboard, отфильтрованный по trace-id, показывает flame graph с доминирующим regex compile в handler’е. Library upgrade ввёл O(n²) regex; фикс — precompile вне handler’а. Детекция — 60 секунд.

Для Go-сервисов pprof встроен:

// 1. Подключи pprof-handler'ы (регистрирует маршруты /debug/pprof/*)
import _ "net/http/pprof"

// Запусти debug-сервер
go func() {
  http.ListenAndServe("localhost:6060", nil)
}()

// 2. Захвати 30-секундный CPU-профиль под нагрузкой:
//    go tool pprof -http=:9090 \
//      http://localhost:6060/debug/pprof/profile?seconds=30
//
// 3. Flame graph view откроется на :9090.
//    Самый широкий leaf-frame наверху = горячий путь.

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

Викторина

Что означает ШИРИНА frame'а на flame graph?

Викторина

Continuous profiler работает в production при 2-5% CPU overhead. Почему не убивает производительность?

Расставь шаги по порядку

Расставь шаги CPU-профилирования медленной функции с pprof:

  1. 1 Определить подозрительную нагрузку (медленный span, высокий CPU, медленный endpoint)
  2. 2 Стартовать профилирование (pprof.StartCPUProfile или endpoint /debug/pprof/profile)
  3. 3 Прогнать подозрительную нагрузку 30 секунд под нагрузкой
  4. 4 Остановить профилирование и сохранить файл профиля
  5. 5 Открыть профиль во flame graph viewer (go tool pprof, speedscope, Pyroscope)
  6. 6 Найти самый широкий frame на leaf-уровне — это горячая функция
  7. 7 Подняться по parent'ам — кто вызывает горячий путь, затем применить фикс
Закончи аналогию

Заполни пропуск: вертикальная ось flame graph показывает call _______ — main внизу, функция на CPU наверху.

Вспомните перед уходом
  1. 01
    В одном абзаце: почему flame graph почти всегда быстрее, чем отладчик или print-statement'ы, для поиска медленной части программы?
  2. 02
    Какая самая распространённая ошибка при чтении flame graph и что на самом деле означает x-ось?
  3. 03
    Почему нужно профилировать под репрезентативной нагрузкой, а не на тихой системе?
Итог

Profiler прерывает программу ~100 раз в секунду, захватывает call stack и после многих сэмплов строит flame graph: ширина = доля CPU. Самый широкий frame на любом уровне — самый горячий путь, без угадывания. x-ось — alphabetical-группировка стеков, не время; читать её как время — самая распространённая ошибка новичка. Профилировать нужно под репрезентативной нагрузкой — пустая система показывает только idle loop. С continuous profiler’ом, работающим always-on при 2-5% overhead, flame graph любого инцидента уже сохранён к моменту, когда срабатывает пейджер.

Связанные уроки
встречается в167
Продолжить восхождение ↑Sampling vs instrumentation profiling: почему 99 Гц побеждает в production
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.