/ Анализ активных php процессов

Анализ активных PHP-процессов в PHP-FPM: диагностика нагрузки, slowlog и /proc

4 фев 2025
Дмитрий М.
1102
Анализ активных php процессов

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

Когда сайт на PHP начинает отвечать медленно, снаружи это выглядит одинаково: растёт время ответа, появляются 502/504, сервер начинает есть больше CPU и памяти. Но реальный вопрос почти всегда один: что сейчас делают активные PHP-процессы и какой запрос занял воркеры php-fpm.

Эту тему удобно разбирать по слоям: от встроенной диагностики php-fpm до анализа конкретного PID в Linux. Ниже — практический разбор с примерами и нормальной последовательностью действий.

Что вообще считать активным PHP-процессом

В большинстве современных проектов под активными PHP-процессами имеют в виду воркеры php-fpm, которые прямо сейчас обрабатывают HTTP-запросы.

Важно не путать несколько сущностей:

  • PHP-FPM pool — пул воркеров, например www.
  • active processes — число воркеров, которые прямо сейчас заняты запросами.
  • idle processes — свободные воркеры, которые ждут следующий запрос.
  • listen queue — очередь запросов, которым не хватило свободного воркера.

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

С чего начинать диагностику

Правильная последовательность обычно такая:

  1. Посмотреть состояние пула через php-fpm status.
  2. Понять, какие URI и скрипты заняли воркеры.
  3. Включить slowlog, если запросы зависают внутри PHP-кода.
  4. Проверить конкретный PID через /proc/, ps и pidstat.
  5. Только после этого трогать pm.max_children, таймауты и лимиты.

Главная мысль простая: сначала находим узкое место, потом увеличиваем ресурсы. Иначе можно просто размазать проблему по большему числу процессов.

1. Самый полезный инструмент: страница статуса php-fpm

php-fpm умеет отдавать встроенную страницу статуса. Это лучший первый источник правды, потому что он показывает не абстрактную загрузку сервера, а состояние самого пула: сколько воркеров занято, есть ли очередь, какой запрос выполнялся последним и сколько он занял времени.

Минимальная конфигурация пула

; /etc/php/8.2/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6

pm.status_path = /fpm-status
ping.path = /fpm-ping

request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/www-slow.log

После изменения конфигурации нужно перезагрузить FPM. Саму страницу статуса нельзя открывать публично: она показывает URI запросов, путь к скриптам и состояние пула. Обычно её ограничивают локальным доступом или отдельным служебным location.

Если на пике нагрузки основной пул может быть полностью занят, полезно посмотреть на директиву pm.status_listen. Она создаёт отдельную точку обслуживания статуса, чтобы метрики были доступны даже тогда, когда рабочие воркеры уже закончились.

Запрос статуса

bash
curl http://127.0.0.1/fpm-status
curl http://127.0.0.1/fpm-status?full
curl http://127.0.0.1/fpm-status?json
curl http://127.0.0.1/fpm-status?json&full

Для автоматизации удобнее всего JSON.

Что означают ключевые поля

  • active processes — сколько воркеров занято прямо сейчас.
  • idle processes — сколько воркеров свободно.
  • listen queue — сколько запросов ждут свободный воркер.
  • max children reached — был ли достигнут лимит pm.max_children.
  • slow requests — сколько запросов превысили request_slowlog_timeout.
  • request duration — длительность последнего запроса в микросекундах.
  • request uri и script — какой URI и какой PHP-скрипт заняли воркер.
  • last request cpu и last request memory — сколько CPU и памяти потребил последний завершённый запрос.

Если listen queue > 0, значит запросы уже стоят в очереди. Если max children reached > 0, значит пул реально упирался в лимит. Это два самых важных сигнала.

Пример разбора JSON-ответа

{
  "pool": "www",
  "process manager": "dynamic",
  "accepted conn": 18421,
  "listen queue": 3,
  "idle processes": 0,
  "active processes": 20,
  "total processes": 20,
  "max active processes": 20,
  "max children reached": 4,
  "slow requests": 17,
  "processes": [
    {
      "pid": 24731,
      "state": "Running",
      "request duration": 18852341,
      "request method": "GET",
      "request uri": "/catalog/?section=servers",
      "script": "/var/www/project/public/index.php"
    }
  ]
}

Из этого примера видно:

  • Все 20 воркеров заняты.
  • Свободных процессов нет.
  • Новые запросы уже ждут в очереди.
  • Лимит pm.max_children уже достигался.
  • Один из запросов выполняется почти 19 секунд.

На практике этого уже достаточно, чтобы переходить от абстрактного "сайт тормозит" к конкретному URI или сценарию нагрузки.

2. Быстрый снимок прямо из PHP через fpm_get_status()

Если нужно получить состояние пула не через отдельный endpoint, а из PHP-кода, можно использовать fpm_get_status(). Функция доступна только тогда, когда скрипт действительно исполняется через FPM.

 'fpm_get_status() недоступна вне PHP-FPM',
    ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
    exit;
}

$status = fpm_get_status();

if ($status === false) {
    http_response_code(500);
    echo json_encode([
        'error' => 'Не удалось получить статус пула',
    ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
    exit;
}

$running = array_values(array_filter(
    $status['procs'] ?? [],
    static fn(array $proc): bool => ($proc['state'] ?? '') === 'Running'
));

usort($running, static function (array $a, array $b): int {
    return ($b['request duration'] ?? 0) <=> ($a['request duration'] ?? 0);
});

echo json_encode([
    'pool' => $status['pool'] ?? null,
    'active_processes' => $status['active processes'] ?? null,
    'idle_processes' => $status['idle processes'] ?? null,
    'listen_queue' => $status['listen queue'] ?? null,
    'running_processes' => $running,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

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

3. Как понять, какой именно процесс завис

После того как вы увидели проблемный PID в статусе FPM, можно спуститься на уровень Linux.

Посмотреть список процессов php-fpm

Здесь обычно интересны PID, процент CPU, процент памяти и время жизни процесса. Если один воркер существует давно и стабильно растёт по памяти, это повод проверить логику приложения и значение pm.max_requests.

Снять статистику по CPU, памяти и I/O

Команда покажет, тратит ли процесс CPU, ждёт ли диск и как ведёт себя память. Это важный момент: не каждый долгий PHP-процесс грузит процессор. Иногда он просто ждёт базу, Redis, внешний API, файловую систему или блокировку сессии.

Посмотреть информацию через /proc/

cat /proc/24731/status
cat /proc/24731/stat
tr '\0' ' ' < /proc/24731/cmdline
readlink /proc/24731/cwd
ls /proc/24731/fd | wc -l

Практически полезны такие файлы:

  • /proc//status — человекочитаемое состояние, RSS, контекстные переключения, лимиты.
  • /proc//stat — машинный формат, который используют ps и другие утилиты.
  • /proc//cmdline — командная строка процесса.
  • /proc//fd — открытые файловые дескрипторы.

Например, если в /proc//status вы видите большой VmRSS, процесс реально держит память. Если дескрипторов подозрительно много, можно проверить утечки соединений или файловых ресурсов.

4. slowlog: когда нужно видеть стек вызовов

Страница статуса показывает, какой процесс занят и каким URI. Но она не отвечает на главный вопрос разработчика: на какой строке кода процесс завис. Для этого нужен slowlog.

request_slowlog_timeout = 5s
request_slowlog_trace_depth = 20
slowlog = /var/log/php-fpm/www-slow.log

Когда запрос выполняется дольше пяти секунд, FPM пишет в slowlog стек PHP-вызовов. Это часто самый быстрый способ увидеть, что тормозит:

    - долгий SQL-запрос; - зависший HTTP-клиент; - медленная работа с файлами; - тяжёлый цикл в PHP; - блокировка сессии или файловая блокировка.

Фрагмент лога может выглядеть так:

[24-May-2026 14:17:08]  [pool www] pid 24731
script_filename = /var/www/project/public/index.php
[0x00007f4eebf5f4c0] curl_exec() /var/www/project/src/Service/ExternalApi.php:84
[0x00007f4eebf5f3d0] request() /var/www/project/src/Controller/CatalogController.php:118
[0x00007f4eebf5f250] show() /var/www/project/public/index.php:32

Это уже не просто "активный PHP-процесс", а конкретный ответ: воркер завис на внешнем HTTP-запросе внутри ExternalApi.php:84.

5. Почему много активных PHP-процессов не всегда означает высокий CPU

Это одна из самых частых ошибок в интерпретации.

Воркеры могут быть активны, но при этом:

  • ждать ответ от MySQL;
  • ждать внешний API;
  • стоять на блокировке сессии;
  • ждать сеть или диск;
  • висеть после fastcgi_finish_request().

Поэтому картина "20 активных процессов" сама по себе ничего не доказывает. Нужно смотреть комбинацию метрик:

  • много Running и высокий CPU — вероятен тяжёлый PHP-код;
  • много Running, но CPU низкий — вероятны ожидания I/O или блокировки;
  • listen queue > 0 и idle processes = 0 — пул не успевает обслуживать входящий поток;
  • slow requests растёт — запросы стабильно выходят за разумный порог времени;
  • один и тот же request uri повторяется в нескольких воркерах — проблема локализована в конкретной странице или сценарии.

6. Отдельная ловушка: fastcgi_finish_request() не освобождает воркер

fastcgi_finish_request() полезна, когда нужно быстро отдать ответ клиенту и продолжить фоновые действия внутри того же запроса. Но у неё есть критическое ограничение: FPM-процесс остаётся занят до полного завершения скрипта.

Это значит, что такая конструкция:

 'ok']);

if (function_exists('fastcgi_finish_request')) {
    fastcgi_finish_request();
}

doVeryLongTask();

не делает код "бесплатным" для пула. Пользователь ответ уже получил, но воркер всё ещё занят долгой задачей. Если так сделать в массовом сценарии, можно быстро упереться в pm.max_children и получить очередь или 502.

Практические рекомендации здесь две:

  • закрывайте сессию как можно раньше через session_write_close(), иначе следующие запросы пользователя могут блокироваться на сессии;
  • длинные фоновые операции лучше выносить в очередь, cron или отдельный worker, а не держать FPM-процесс занятым.

7. Практический сценарий разбора проблемы

Представим, что сайт периодически отдаёт 504.

  1. Открываем /fpm-status?json&full и видим: active processes = 20, idle processes = 0, listen queue = 7.
  2. Смотрим массив процессов и замечаем, что половина воркеров занята URI /catalog/?section=servers.
  3. Проверяем slowlog и видим стек, который упирается в получение цен из внешнего API.
  4. Запускаем pidstat для одного из PID и убеждаемся, что CPU невысокий, зато запросы длятся долго.
  5. Вывод: проблема не в числе воркеров, а в синхронном внешнем запросе внутри страницы каталога.

В такой ситуации простое увеличение pm.max_children может слегка отсрочить аварию, но не уберёт первопричину. Правильнее кэшировать ответ, вынести интеграцию из пользовательского запроса или ограничить таймаут внешнего вызова.

8. Команды, которые стоит держать под рукой

# Краткий статус пула
curl http://127.0.0.1/fpm-status?json | jq

# Полный статус с процессами
curl http://127.0.0.1/fpm-status?json&full | jq

# Все процессы php-fpm
ps -eo pid,ppid,user,%cpu,%mem,etime,cmd | grep '[p]hp-fpm'

# Наблюдение за конкретным PID
pidstat -u -r -d -p 24731 1 5

# Человекочитаемое состояние процесса
cat /proc/24731/status

# Командная строка процесса
tr '\0' ' ' < /proc/24731/cmdline

# Количество открытых дескрипторов
ls /proc/24731/fd | wc -l

9. Что делать после диагностики

Когда причина уже понятна, исправления обычно попадают в один из четырёх классов:

  • оптимизация кода — убрать тяжёлый цикл, сократить сериализацию, добавить кэш;
  • оптимизация зависимостей — ускорить SQL, Redis, файловые операции, HTTP-вызовы во внешние сервисы;
  • архитектурное разделение — вынести долгие действия в очередь и фоновые воркеры;
  • тюнинг FPM — корректно выставить pm.max_children, pm.max_requests, таймауты и slowlog.

Именно в таком порядке. Тюнинг пула без понимания причины помогает только временно.

Итог

Анализ активных PHP-процессов — это не одна команда и не один график. Надёжная схема выглядит так:

  1. php-fpm status показывает общую картину по пулу.
  2. fpm_get_status() помогает встроить диагностику в приложение.
  3. slowlog показывает, на каком участке PHP-кода завис запрос.
  4. ps, pidstat и /proc/ позволяют добраться до конкретного процесса на уровне ОС.

Если использовать эти инструменты вместе, можно быстро ответить на три главных вопроса:

  • сколько воркеров занято;
  • чем именно они заняты;
  • почему они не освобождаются вовремя.

Источники

Статья была полезна? Поблагодарите автора.

Оглавление
    Самые читаемые
    #1С Битрикс, #Bitrix CMS, #.htaccess, #настройка редиректов
    4 авг 2019
    #bitrix:news, #сортировка, #фильтрация, #bitrix:catalog, #catalog.section, #news.list
    16 дек 2020
    #bitrix, #свойства элементов, #обработчик событий, #OnBeforeIBlockElementUpdate, #OnIBlockElementSetPropertyValues
    21 июл 2020
    #Хлебные крошки, #1С Битрикс, #Bitrix CMS, #bitrix:breadcrumbs, #component_epilog, #кэширование
    1 окт 2018
    #Bitrix CMS, #breadcrumb, #bitrix:breadcrumbs, #хлебные крошки, #настройка
    13 фев 2019
    #ресайз изображений, #1С Битрикс, #Bitrix CMS
    3 мар 2019
    #bitrix, #robots.txt, #sitemap.xml, #поддомены, #мультисайтовость
    16 окт 2018
    #bitrix, #bitrix:catalog.section, #скидки, #товары со скидкой, #страница скидок, #страница со скидками
    4 окт 2018
    #bitrix, #пользовательские свойства, #тип свойств, #привязка к элементам
    27 ноя 2019