Сервер задыхается, нагрузка на процессор под потолок, и первый порыв - открыть привычный монитор и посмотреть, кто виноват. Монитор честно показывает процесс с цифрой под сотню процентов. Но на этом расследование обычно и застревает: процесс известен, а что именно внутри него жжёт процессор - загадка. Высокая нагрузка сама по себе лишь симптом, а не диагноз. Тот же процесс может крутиться в бесконечном цикле, ждать диск, штурмовать ядро системными вызовами или вовсе оказаться скрытым майнером. Каждая причина требует своего лечения, и узнать её можно, лишь заглянув глубже монитора процессов.
Грамотная диагностика идёт слоями: сначала монитор показывает горячий процесс и тип нагрузки, затем профилировщик вскрывает горячие функции внутри него - вплоть до функций ядра. Эта связка отличает поверхностное наблюдение от настоящего понимания, почему процессор раскалён. Разберём, как читать монитор, на что смотреть в первую очередь, и как профилировщиком найти конкретную функцию, пожирающую такты.
Монитор показывает горячий процесс и распределение нагрузки
Начинают с обзорного монитора. Привычная утилита сортирует процессы по нагрузке, а её более удобный собрат рисует ту же картину нагляднее, с цветными полосами и деревом процессов.
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 # сводка системных вызовов процесса
Сводка выдаёт долю времени по каждому виду вызова - и сразу видно, если программа, скажем, без меры читает или пишет, утопая в обращениях к ядру. Это объясняет высокую системную долю, замеченную в мониторе на первом шаге.
Стоит держать в уме и неприятную возможность подмены причины. Незнакомый процесс с подозрительным именем, жгущий процессор под сотню процентов, бывает скрытым майнером или иным зловредом. Проверяют происхождение процесса - путь к его исполняемому файлу, сетевые соединения, совпадение имени с известными вредоносами. Высокая нагрузка от неопознанной программы заслуживает такой проверки, прежде чем списать всё на честную работу.
Что складывается в методику
Картина выстраивается в слоистую методику. Сперва обзорный монитор показывает горячий процесс, но важнее имени - распределение нагрузки по видам: пользовательское время уводит к коду программы, системное к ядру, время ожидания к диску. Разбивка по ядрам ловит однопоточную нагрузку, упёршуюся в одно ядро.
Затем учитывают ловушки - многопоточность и служебные потоки ядра, - и переходят к профилировщику. Он вскрывает горячие функции внутри процесса, включая функции ядра, превращая абстрактную нагрузку в конкретное имя. Снимок профиля со стеками показывает путь до горячего места, а сводка системных вызовов объясняет шторм обращений к ядру. И всюду держат в уме возможность зловреда под видом честного процесса.
Главная мысль: высокая нагрузка на процессор - это симптом, а не ответ. Монитор называет процесс, но настоящая диагностика начинается там, где профилировщик показывает горячую функцию внутри него. Стоит освоить чтение долей нагрузки и связку монитора с профилировщиком, и расследование перестаёт упираться в имя процесса - оно доходит до конкретной строки кода или функции ядра, где такты сгорают на самом деле.