Когда сервер «тормозит», ощущения мало: нужно понять, где именно узкое место — процессор, диск, сеть или память. В Linux есть набор инструментов профилирования: perf, pidstat, iostat, vmstat. Разберём, как ими пользоваться по порядку, чтобы за несколько минут сузить круг поиска.
Первый шаг: общая картина
Начинайте с top/htop и vmstat: они сразу показывают, перегружена ли CPU, ждёт ли система ввода-вывода или упирается в память.
# сводка по системе за 5 секунд
vmstat 1 5
# поля: r — в очереди, b — ждущие I/O, si/so — своп, wa — ожидание диска
# нагрузка по ядрам
mpstat -P ALL 1 5
# топ процессов по CPU
ps -eo pid,user,pcpu,pmem,comm --sort=-pcpu | head -12
Значение wa выше единицы и большой b указывают на диск. Большое r с высоким us — на CPU.
I/O: iostat и поиск виновника
Диск — самая частая причина «зависаний». Смотрите не только %util, но и латентность: при высокой она читается в %await.
# установить
sudo apt install sysstat
# I/O по устройствам
iostat -xz 1 5
# топ процессов по I/O
pidstat -d 1 5
# кто именно пишет
sudo iotop -oPa 2>/dev/null | head -20
Если %util у SSD около 100%, это ещё не перегрузка — SSD параллельны. Ориентируйтесь на await и достижимую пропускную способность.
perf: профилирование CPU
perf собирает статистику процессора и показывает, где тратится время. Он требует понимания, но даёт конкретные функции, а не общие слова.
# установить (нужен debug-символы для полной картины)
sudo apt install linux-perf
# сводка по процессу (PID подставьте свой)
perf stat -p PID sleep 10
# профиль горячих функций
perf record -g -p PID -- sleep 10
perf report --stdio | head -40
# включить запись по юниту (пример)
perf top -C 0-3 2>/dev/null | head -20
Без debug-символов отчёт покажет адреса вместо имён. Установите пакет linux-image-...-dbgsym или используйте perf report с подсказками по низкоуровневым функциям.
Память: где утечка
# потребление и своп
free -h
# давление на память
cat /proc/pressure/memory
# топ по RSS
ps -eo pid,rss,comm --sort=-rss | head -12
# динамика процесса
pidstat -r 1 5
Растущий RSS при простой нагрузке — признак утечки. Смотрите динамику десятки секунд, а не одиночный снимок.
Сеть: задержка и потери
# состояние соединений
ss -tan | awk 'END{print NR" соединений"}'
# ошибки и потери на интерфейсе
ip -s link show eth0
netstat -s 2>/dev/null | grep -iE "retrans|drop" | head
# задержка до хоста
mtr -rwc 30 example.com | tail -12
Ретрансмиссии TCP растут при перегрузке сети или буферов — проверьте ss -tm и значения net.core.wmem.
Собираем картину вместе
vmstat— CPU, диск, память, своп.pidstat -dиpidstat -r— кто нагружает.iostat -xz— латентность диска.perf record— горячие функции.- Сравните с моментом, когда всё работало хорошо.
# сохранить срез для отчёта
mkdir -p ~/perf && cd ~/perf
vmstat 1 5 > vmstat.txt
iostat -xz 1 5 > iostat.txt
pidstat -dur 1 5 > pidstat.txt
perf record -g -- sleep 10 2>/dev/null
Частые ошибки измерений
- Профилирование на тестовом стенде без реальной нагрузки.
- Одиночный снимок вместо динамики.
- Игнорирование времени самого инструмента:
perfсам потребляет CPU. - Сравнение чисел разных конфигураций без фиксации настроек.
# зафиксировать условия теста
uname -r > env.txt; lscpu >> env.txt; free -h >> env.txt
Воспроизводимые измерения
Цифры полезны только в сравнении: зафиксируйте версию ядра, настройки и нагрузку до и после изменения. Иначе «стало быстрее» превращается в субъективное впечатление.
# условия теста
uname -r > bench/env.txt
lscpu >> bench/env.txt
free -h >> bench/env.txt
cat /proc/cmdline >> bench/env.txt
# один и тот же тест до/после
fio --name=bench --rw=randread --size=1G --runtime=60 --time_based | tee -a bench/io.txt
Прогоняйте тест несколько раз подряд: первое измерение почти всегда отличается из-за разогрева кэшей и планировщика. Берите медиану, а не максимум.
Профилирование внутри контейнеров
В контейнере perf видит процессы хоста, но сбор символов может блокироваться из-за изоляции. Запускайте профилирование на хосте с указанием PID.
# найти PID контейнера на хосте
docker inspect -f '{{.State.Pid}}' mycontainer 2>/dev/null
# профилировать процесс хоста
perf record -g -p PID -- sleep 10
# ограничения ресурсов контейнера
docker stats --no-stream 2>/dev/null | head
Если сервис работает под systemd, удобнее профилировать юнит: perf record -u nginx.service — это избавляет от поиска PID.
Когда инструменты не помогают
Бывает, что нагрузка уходит в ожидание, которое не видно ни в top, ни в iostat: блокировки базы данных, очереди в приложении, задержки внешних API. В этом случае смотрите логи приложения и трассируйте вызовы через strace -c -p PID или eBPF-инструменты вроде bpftrace.
# счётчик системных вызовов процесса
sudo strace -c -p PID 2>/dev/null &
sleep 10 && kill %1 2>/dev/null
# проверить, есть ли bpftrace
which bpftrace 2>/dev/null || echo "установите из репозитория"
Собирайте данные из нескольких источников и сопоставляйте по времени: только тогда картинка становится полной.
Итог
Поиск узкого места — последовательность: vmstat для выбора направления, pidstat/iostat для виновника, perf для конкретных функций. Фиксируйте условия и динамику, а не одиночные значения: так решение принимается на данных, а не на ощущениях.