Как анализировать и контролировать PHP процессы в системе

Раздел: Администрирование PHP -> Мониторинг PHP

Мониторинг процессов PHP в системе

Запущенные процессы PHP требуют внимания администратора: от них зависит стабильность веб-приложений и сервера в целом. Ниже рассматриваются разные подходы к отслеживанию, анализу и управлению такими процессами.

Основной метод - командная строка с ps и grep

Как получить список всех PHP-процессов вместе с PID, потреблением CPU и памяти?

ps aux | grep -E 'php(-fpm|/usr/bin/php)' | grep -v grep

Php запущенные процессы (запущенные процессы 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);

Запущенные процессы PHP - comments

En
Php запущенные процессы (php)