Сервер задыхается, нагрузка на процессор под потолок, и первый порыв - открыть привычный монитор и посмотреть, кто виноват. Монитор честно показывает процесс с цифрой под сотню процентов. Но на этом расследование обычно и застревает: процесс известен, а что именно внутри него жжёт процессор - загадка. Высокая нагрузка сама по себе лишь симптом, а не диагноз. Тот же процесс может крутиться в бесконечном цикле, ждать диск, штурмовать ядро системными вызовами или вовсе оказаться скрытым майнером. Каждая причина требует своего лечения, и узнать её можно, лишь заглянув глубже монитора процессов.

Грамотная диагностика идёт слоями: сначала монитор показывает горячий процесс и тип нагрузки, затем профилировщик вскрывает горячие функции внутри него - вплоть до функций ядра. Эта связка отличает поверхностное наблюдение от настоящего понимания, почему процессор раскалён. Разберём, как читать монитор, на что смотреть в первую очередь, и как профилировщиком найти конкретную функцию, пожирающую такты.

Монитор показывает горячий процесс и распределение нагрузки

Начинают с обзорного монитора. Привычная утилита сортирует процессы по нагрузке, а её более удобный собрат рисует ту же картину нагляднее, с цветными полосами и деревом процессов.

top -o %CPU            # отсортировать процессы по нагрузке на процессор
htop                   # удобный монитор с деревом и сортировкой по F6

Но главное при высокой нагрузке - не имя процесса, а распределение нагрузки по видам. В строке состояния монитор разбивает процессорное время на доли, и каждая доля рассказывает свою историю. Доля пользовательского времени - это работа самих программ. Доля системного времени - работа ядра, системные вызовы и обработка прерываний. Доля ожидания - простой в ожидании диска.

%Cpu(s): 93.4 us,  6.6 sy,  0.0 ni,  0.0 id,  0.0 wa

Чтение этих долей задаёт направление всего расследования. Если велика доля пользовательского времени, дело в коде самой программы - бесконечный цикл, тяжёлые вычисления. Если устойчиво велика доля системного времени, скажем, выше трети, ядро занято системными вызовами или прерываниями - и копать надо в сторону ядра. Если велика доля ожидания, процессор на самом деле не загружен, а простаивает в ожидании диска, и проблема вовсе не в процессоре, а в подсистеме хранения.

Полезно и взглянуть на нагрузку по отдельным ядрам, потому что общая цифра усредняет картину. Когда одно ядро забито под потолок, а остальные простаивают, это намекает на однопоточную нагрузку, упёршуюся в одно ядро.

mpstat -P ALL 2 5      # разбивка нагрузки по каждому ядру

Многопоточность и системные процессы требуют отдельного взгляда

Прежде чем нырять глубже, стоит учесть две частые ловушки чтения монитора. Первая - многопоточные программы. Некоторые приложения работают множеством потоков, и монитор по умолчанию может показывать либо суммарную нагрузку процесса, либо отдельные потоки. Цифра выше ста процентов у одного процесса означает, что он занимает больше одного ядра несколькими потоками, и это нормально для многопоточной нагрузки.

Вторая ловушка - служебные процессы ядра. Когда нагрузку создаёт ядро, в мониторе нередко всплывают рабочие потоки ядра с характерными именами. Их высокая активность - сигнал, что дело не в прикладной программе, а в самом ядре или драйверах.

ps aux | grep kworker          # рабочие потоки ядра
ps aux --sort=-%cpu | head     # верхние процессы по нагрузке

Высокая системная доля вместе с активными рабочими потоками ядра уводит расследование в сторону прерываний и драйверов. Тут полезно заглянуть в счётчики прерываний, чтобы заметить, не штурмует ли систему какое-то устройство.

watch -n1 cat /proc/interrupts   # следить за ростом прерываний

Профилировщик вскрывает горячие функции внутри процесса

Вот где диагностика переходит на новый уровень. Монитор показал процесс, но не показал, что внутри него горит. Это работа профилировщика - инструмента, который выборочно опрашивает, какие функции выполняются прямо сейчас, и строит картину самых нагруженных. Его понадобится сначала установить, если он не стоит.

# установка профилировщика
sudo apt install linux-tools-common linux-tools-generic   # Debian, Ubuntu
sudo yum install perf                                      # RHEL, CentOS

Простейший режим показывает горячие функции в реальном времени, как монитор процессов, но на уровне функций. Запущенный с указанием процесса, он выводит, какие именно функции этого процесса съедают такты.

sudo perf top                  # горячие функции по всей системе
sudo perf top -p 12345         # горячие функции конкретного процесса

Ценность профилировщика в том, что он видит не только функции прикладной программы, но и функции ядра. Если процессорное время уходит в ядро, профилировщик покажет конкретные символы ядра, занимающие процессор, - например, функцию обработки сетевых пакетов или работы с памятью. Так абстрактная высокая системная доля превращается в конкретное имя функции, которую можно искать дальше.

Снимок профиля для последующего анализа стеков

Режим реального времени хорош для быстрого взгляда, но для основательного разбора снимают профиль за период, а потом изучают. Профилировщик записывает выборку за указанное время с захватом стеков вызовов, после чего отчёт показывает, откуда вызывались горячие функции.

# снять профиль процесса за 30 секунд со стеками вызовов
sudo perf record -p 12345 -g -- sleep 30
sudo perf report               # изучить снятый профиль

Захват стеков через соответствующий флаг принципиально важен. Он показывает не просто горячую функцию, а цепочку вызовов, приведшую к ней, - так становится понятен путь, которым программа дошла до прожорливого места. Снятый профиль удобно превращают в наглядную диаграмму пламени, где ширина полос показывает долю времени, а высота - глубину стека вызовов, что делает горячий путь видимым с одного взгляда.

На боевых системах важно соблюдать осторожность с нагрузкой от самой диагностики. Профилировщик в режиме выборки лёгок, а вот полная трассировка всех функций тяжела, и её включают лишь на короткие промежутки. Здравый порядок - сначала лёгкие инструменты вроде выборочного профиля и метрик системы, и лишь при нужде короткий захват полных стеков.

Системные вызовы и подмена причины

Иногда профиль указывает не на вычисления, а на шторм системных вызовов - программа без устали дёргает ядро. Тогда полезно посмотреть, какие именно вызовы она делает, через трассировщик системных вызовов в сводном режиме. Он подсчитывает вызовы по видам и показывает, на что уходит время.

strace -p 12345 -c -f          # сводка системных вызовов процесса

Сводка выдаёт долю времени по каждому виду вызова - и сразу видно, если программа, скажем, без меры читает или пишет, утопая в обращениях к ядру. Это объясняет высокую системную долю, замеченную в мониторе на первом шаге.

Стоит держать в уме и неприятную возможность подмены причины. Незнакомый процесс с подозрительным именем, жгущий процессор под сотню процентов, бывает скрытым майнером или иным зловредом. Проверяют происхождение процесса - путь к его исполняемому файлу, сетевые соединения, совпадение имени с известными вредоносами. Высокая нагрузка от неопознанной программы заслуживает такой проверки, прежде чем списать всё на честную работу.

Что складывается в методику

Картина выстраивается в слоистую методику. Сперва обзорный монитор показывает горячий процесс, но важнее имени - распределение нагрузки по видам: пользовательское время уводит к коду программы, системное к ядру, время ожидания к диску. Разбивка по ядрам ловит однопоточную нагрузку, упёршуюся в одно ядро.

Затем учитывают ловушки - многопоточность и служебные потоки ядра, - и переходят к профилировщику. Он вскрывает горячие функции внутри процесса, включая функции ядра, превращая абстрактную нагрузку в конкретное имя. Снимок профиля со стеками показывает путь до горячего места, а сводка системных вызовов объясняет шторм обращений к ядру. И всюду держат в уме возможность зловреда под видом честного процесса.

Главная мысль: высокая нагрузка на процессор - это симптом, а не ответ. Монитор называет процесс, но настоящая диагностика начинается там, где профилировщик показывает горячую функцию внутри него. Стоит освоить чтение долей нагрузки и связку монитора с профилировщиком, и расследование перестаёт упираться в имя процесса - оно доходит до конкретной строки кода или функции ядра, где такты сгорают на самом деле.