Как анализировать и контролировать PHP процессы в системе
Мониторинг процессов PHP в системе
Запущенные процессы PHP требуют внимания администратора: от них зависит стабильность веб-приложений и сервера в целом. Ниже рассматриваются разные подходы к отслеживанию, анализу и управлению такими процессами.
Основной метод - командная строка с ps и grep
Как получить список всех PHP-процессов вместе с PID, потреблением CPU и памяти?
ps aux | grep -E 'php(-fpm|/usr/bin/php)' | grep -v grepPhp запущенные процессы (запущенные процессы php)
Эта команда выводит строки, где исполняемым файлом является интерпретатор PHP или PHP-FPM. Для каждого процесса отображаются пользователь, PID, %CPU, %MEM, время запуска и полная команда.
Типичная ошибка: если в системе запущено много процессов, вывод может быть слишком большим. Рекомендуется добавить сортировку по использованию ресурсов:
ps aux --sort=-%mem | grep php | head -20Возможна ситуация, когда процесс не виден из-за недостаточных прав. Команда от имени root покажет все процессы.
Цель: быстрая диагностика «зависших» скриптов, утечек памяти, неожиданно высокого потребления CPU.
Как отследить процессы PHP-FPM с помощью встроенного пула?
В конфигурации PHP-FPM имеется страница статуса. Для её активации добавляют в пул (например, /etc/php/8.1/fpm/pool.d/www.conf):
pm.status_path = /statusЗатем через Nginx или Apache организуют доступ к /status?plain. В ответ приходит таблица с числом активных, idle процессов, очередью и т.д.
Если страница статуса выдаёт пустой ответ или 404, проверяют, что путь не перекрыт другими директивами, и что PHP-FPM перезапущен. Ошибка конфигурации часто связана с синтаксисом или пробелами.
Как из PHP-скрипта получить информацию о родительских и дочерних процессах?
Функции exec или shell_exec позволяют выполнять системные команды и разбирать вывод. Пример скрипта, который отображает PID и команду каждого PHP-процесса:
$output = shell_exec("ps aux | grep php | grep -v grep");
echo "<pre>$output</pre>";Такой подход удобен для встраивания в веб-панель администратора, но требует осторожности с экранированием и правами.
При использовании в контексте веб-сервера (например, Apache mod_php) команда выполняется от пользователя www-data, который может не иметь прав на просмотр чужих процессов. Решение - добавить пользователя в группу, имеющую доступ к /proc, или настроить sudo.
Случаи использования: автоматизированные системы мониторинга, кастомные дашборды.
Как управлять фоновыми скриптами PHP с помощью nohup и disown?
Долгие задачи (парсинг, генерация отчётов) запускаются в фоне. Команда nohup php script.php > /dev/null 2>&1 & отвязывает процесс от терминала. Чтобы потом найти такой процесс, используют ps aux | grep script.php.
После выхода из терминала процесс может получить сигнал SIGHUP и завершиться, если не использовать nohup или не настроить disown. Другая проблема - накопление фоновых процессов. Рекомендуется записывать PID в файл:
nohup php script.php &
echo $! > /var/run/script.pidКак с помощью strace или ltrace исследовать зависший PHP-процесс?
Для глубокой диагностики применяют трассировку системных вызовов. Определив PID процесса (ps aux | grep php), выполняют:
strace -p PID -o /tmp/trace.log -e trace=network,fileВ логе отобразятся все операции открытия файлов, сетевые соединения, что позволяет выявить блокировки.
strace требует прав root или возможности CAP_SYS_PTRACE. Если процесс не откликается, strace может зависнуть. В таком случае используют gdb -p PID для создания дампа стека.
Расширенные примеры работы с процессами PHP
Ниже приведены практические сценарии, которые выходят за рамки базовых команд.
1. Скрипт мониторинга PHP-процессов с уведомлениями
Следующий PHP-скрипт каждую минуту проверяет количество запущенных процессов и, если превышен порог, отправляет e-mail:
#!/usr/bin/php
<?php
$threshold = 50;
$processes = shell_exec("ps aux | grep 'php' | grep -v 'grep' | wc -l");
$count = (int) trim($processes);
if ($count > $threshold) {
$msg = "Количество PHP-процессов: $count, порог $threshold превышен.";
mail('admin@example.com', 'PHP process alert', $msg);
file_put_contents('/var/log/php_proc.log', date('Y-m-d H:i:s') . ' ' . $msg . PHP_EOL, FILE_APPEND);
}Запуск скрипта по расписанию (cron):
* * * * * /usr/bin/php /usr/local/bin/check_php_proc.phpРезультат работы (пример лога):
2025-03-25 10:15:00 Количество PHP-процессов: 65, порог 50 превышен.
2. Получение иерархии процессов через /proc
Можно обойти утилиты и прочитать информацию напрямую из /proc. Пример демонстрирует PID и команду для каждого дочернего процесса указанного родителя:
#!/usr/bin/php
<?php
$parentPid = 1234; // PID основного PHP-FPM мастера
$procDir = '/proc';
$ppid = $parentPid;
echo "Дочерние процессы для $ppid:\n";
foreach (new DirectoryIterator($procDir) as $fileInfo) {
if ($fileInfo->isDir() && ctype_digit($fileInfo->getFilename())) {
$pid = $fileInfo->getFilename();
$stat = @file_get_contents("$procDir/$pid/stat");
if ($stat) {
// формат: PID (comm) state ppid ...
preg_match('/\d+ \((.+?)\) [A-Z] (\d+)/', $stat, $m);
if (isset($m[2]) && $m[2] == $ppid) {
echo "PID $pid: {$m[1]}\n";
}
}
}
}Результат (гипотетический):
Дочерние процессы для 1234: PID 1235: php-fpm PID 1236: php-fpm
3. Использование proc_open для асинхронного запуска и контроля
Иногда требуется запустить PHP-скрипт из другого скрипта и следить за его состоянием:
$descriptorspec = [
0 => ["pipe", "r"], // stdin
1 => ["pipe", "w"], // stdout
2 => ["pipe", "w"] // stderr
];
$process = proc_open('php long_task.php', $descriptorspec, $pipes);
if (is_resource($process)) {
fclose($pipes[0]); // закрываем stdin
$output = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$errors = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$return_value = proc_close($process);
echo "Код завершения: $return_value\n";
echo "Вывод: $output\n";
echo "Ошибки: $errors\n";
}Для неблокирующего чтения используют stream_set_blocking или цикл с proc_get_status. Пример отслеживания статуса без блокировки:
$status = proc_get_status($process);
while ($status['running']) {
echo "PID " . $status['pid'] . " ещё работает\n";
sleep(2);
$status = proc_get_status($process);
}
echo "Процесс завершился с кодом " . $status['exitcode'];4. Анализ утечки памяти через pmap
Утилита pmap показывает карту памяти процесса. Команда для одного PHP-процесса:
pmap -x 5678Вывод (сокращённый):
Address Kbytes RSS Dirty Mode Mapping 00007f1234000000 128 128 0 rw--- [ anon ] 00007f1234200000 16384 16384 0 r---- libphp8.so ...
Сравнение RSS с Dirty позволяет определить, какая часть памяти используется исключительно этим процессом. В PHP-скрипте можно парсить /proc/PID/smaps для детального отчета.
5. Завершение группы процессов с помощью proc_terminate
Если нужно остановить не только сам процесс, но и его дочерние элементы, используют сигнал и группу процессов. Пример на PHP:
$pid = 12345;
exec("kill -- -$pid"); // минус перед PID означает группуНо безопаснее сначала найти всех потомков через pgrep -P $pid и отправить SIGTERM каждому.
$children = explode(PHP_EOL, trim(shell_exec("pgrep -P $pid")));
foreach ($children as $child) {
if (!empty($child)) {
posix_kill((int)$child, SIGTERM);
}
}
posix_kill($pid, SIGTERM);