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

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

Типы профилей: CPU, память, off-CPU, mutex — какой когда брать

Суть CPU profiling видит только работающий код; off-CPU, block и mutex профили покрывают 96% запроса, проведённые в ожидании — для полной диагностики нужны все четыре.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 15 min

Span занимает 500 мс. Открываешь CPU-профиль — сервис использовал 20 мс CPU. Куда ушли остальные 480 мс? CPU profiling слеп к ожиданию. Если остановиться здесь — ответ не найти никогда.

CPU профили: что видят и что упускают

CPU profiling сэмплирует call stack, пока поток на CPU — выполняет инструкции. Запрос, проведший 20 мс в вычислениях и 480 мс ожидая DB-запроса, покажет 20 мс в CPU-профиле, а 480 мс останутся невидимыми.

Это важнейшее ограничение profiling: CPU-профили видят только функции, потребляющие процессор. Всё, чего ждёт программа — I/O, сеть, локи, планировщик — находится off-CPU и невидимо для CPU-profiler’а.

Memory и allocation профили

Heap profiler’ы сэмплируют аллокации, а не CPU. Heap profile в Go сэмплирует одну аллокацию на ~512 КиБ (Poisson) и записывает стек при каждом сэмпле. Результат — flame graph, где ширина это выделенные байты, а не CPU-время. Это находит memory-горячки: функция, выделяющая 100 МБ/с, выглядит широкой.

Обнаружение memory leak с heap profile:

  1. Взять heap профиль.
  2. Подождать 30-60 минут.
  3. Взять ещё один heap профиль.
  4. Сравнить (go tool pprof -base baseline.heap current.heap).
  5. Функции, чья аллокация выросла, — источник утечки.

Allocation профили захватывают короткоживущие аллокации, которые GC возвращает; heap профили — снимки живой памяти. JVM-эквиваленты: async-profiler с -e alloc, JFR allocation events. Python: tracemalloc, memray.

Off-CPU профили

Работа Brendan Gregg по off-CPU analysis (2013) определила пробел: CPU-профили пропускают всё, чего ждёт процесс. eBPF-реализации цепляются за события переключения контекста kernel-планировщика. Когда планировщик снимает поток с CPU (блокировка на I/O, sleep, ожидание лока), kernel захватывает стек потока. Этот стек — место, где началось ожидание. Когда поток возвращается, прошедшее время атрибутируется этому стеку.

Off-CPU flame graph показывает время ожидания точно так же, как CPU flame graph показывает время выполнения. Для I/O-bound сервиса off-CPU профиль — единственный, который хоть что-то объясняет: CPU-профиль просто говорит «сервис простаивал».

Block и mutex профили

Block profile (Go: runtime.SetBlockProfileRate): время ожидания на sync-примитивах — каналах, condition variable’ах, WaitGroup’ах. Более фокусированный, чем off-CPU, так как нацелен на language-level синхронизацию.

Mutex profile (runtime.SetMutexProfileFraction): конкуренция за локи конкретно. Сообщает, какой код держал лок, пока другие его ждали, — атрибутировано в момент unlock’а.

Тип профиляШирина означаетКогда брать
CPUПотреблённое CPU-времяВысокое CPU, медленный ответ
Heap / AllocationВыделенные байтыGC pressure, OOM, рост памяти
Off-CPUВремя ожидания (все причины)Медленный запрос, но низкий CPU
BlockВремя на sync-примитивахПодозреваемая конкуренция Go goroutine
MutexВремя конкуренции за локПодозреваемая высокая конкуренция за лок

Выбор профиля по соотношению CPU/wall-time

Диагностический shortcut: смотри CPU-время vs wall-clock-время медленного запроса.

  • CPU/wall ≈ 100%: вычислительное узкое место — CPU профиль.
  • CPU/wall < 30%: узкое место off-CPU — off-CPU/block профиль или trace span’ы.
  • Память растёт стабильно: heap/allocation профиль.
  • Потоки конкурируют за лок: mutex профиль.

Java-сервис с GC-thrashing — классический allocation-профиль случай. Симптом: высокая скорость аллокации при частых old-gen GC. Allocation flame graph покажет самый широкий frame как функцию, аллоцирующую с наибольшей скоростью — часто string concatenation в logging-коде без параметризованного форматирования.

Викторина

Запрос проводит 50 мс на CPU и 450 мс ожидая DB-запрос. Какой тип профиля покажет ожидание БД?

Викторина

Java-сервис OOM'ится на определённых endpoint'ах. CPU профиль выглядит нормально. Какой тип профиля взять?

Вспомните перед уходом
  1. 01
    Почему heap profiler в Go сэмплирует одну аллокацию на ~512 КиБ вместо записи каждой?
  2. 02
    Объясни, почему CPU flame graph недостаточен для диагностики I/O-bound сервиса.
  3. 03
    Опиши процедуру обнаружения memory leak с heap профилями.
Итог

Четыре типа профилей покрывают полный жизненный цикл запроса: CPU (что выполняется), heap или allocation (что аллоцируется), off-CPU (что ждёт I/O или планировщика), block или mutex (что ждёт локов). CPU profiling видит только код, активно находящийся на процессоре — запрос, ждущий 480 мс DB-запрос, покажет в CPU профиле только 20 мс вычислений. Соотношение CPU/wall-time — диагностический сигнал: менее 30% означает, что узкое место off-CPU. Heap profiler в Go сэмплирует при 1 на 512 КиБ для affordable always-on memory profiling. Комбинация всех четырёх даёт полную картину; использование только CPU profiling для I/O-bound сервиса гарантирует нахождение не того узкого места.

Связанные уроки
встречается в167
Продолжить восхождение ↑Continuous profiling: always-on flame graphs с eBPF и корреляцией trace-id
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.