From 9dcfa7f93f0f3b2a5b2ca28e04784804fe30e1f5 Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Tue, 16 Jul 2024 19:33:43 -0400 Subject: [PATCH 1/3] feat: PHP-FPM setup check / max_children check Signed-off-by: Josh Richards --- .../composer/composer/autoload_classmap.php | 1 + .../composer/composer/autoload_static.php | 1 + apps/settings/lib/AppInfo/Application.php | 2 + apps/settings/lib/SetupChecks/PhpSAPI.php | 42 +++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 apps/settings/lib/SetupChecks/PhpSAPI.php diff --git a/apps/settings/composer/composer/autoload_classmap.php b/apps/settings/composer/composer/autoload_classmap.php index aa288301381b7..d7d47680d3e89 100644 --- a/apps/settings/composer/composer/autoload_classmap.php +++ b/apps/settings/composer/composer/autoload_classmap.php @@ -131,6 +131,7 @@ 'OCA\\Settings\\SetupChecks\\PhpOpcacheSetup' => $baseDir . '/../lib/SetupChecks/PhpOpcacheSetup.php', 'OCA\\Settings\\SetupChecks\\PhpOutdated' => $baseDir . '/../lib/SetupChecks/PhpOutdated.php', 'OCA\\Settings\\SetupChecks\\PhpOutputBuffering' => $baseDir . '/../lib/SetupChecks/PhpOutputBuffering.php', + 'OCA\\Settings\\SetupChecks\\PhpSAPI' => $baseDir . '/../lib/SetupChecks/PhpSAPI.php', 'OCA\\Settings\\SetupChecks\\PushService' => $baseDir . '/../lib/SetupChecks/PushService.php', 'OCA\\Settings\\SetupChecks\\RandomnessSecure' => $baseDir . '/../lib/SetupChecks/RandomnessSecure.php', 'OCA\\Settings\\SetupChecks\\ReadOnlyConfig' => $baseDir . '/../lib/SetupChecks/ReadOnlyConfig.php', diff --git a/apps/settings/composer/composer/autoload_static.php b/apps/settings/composer/composer/autoload_static.php index ec3304c1e7530..b8376dfc8a7a8 100644 --- a/apps/settings/composer/composer/autoload_static.php +++ b/apps/settings/composer/composer/autoload_static.php @@ -146,6 +146,7 @@ class ComposerStaticInitSettings 'OCA\\Settings\\SetupChecks\\PhpOpcacheSetup' => __DIR__ . '/..' . '/../lib/SetupChecks/PhpOpcacheSetup.php', 'OCA\\Settings\\SetupChecks\\PhpOutdated' => __DIR__ . '/..' . '/../lib/SetupChecks/PhpOutdated.php', 'OCA\\Settings\\SetupChecks\\PhpOutputBuffering' => __DIR__ . '/..' . '/../lib/SetupChecks/PhpOutputBuffering.php', + 'OCA\\Settings\\SetupChecks\\PhpSAPI' => __DIR__ . '/..' . '/../lib/SetupChecks/PhpSAPI.php', 'OCA\\Settings\\SetupChecks\\PushService' => __DIR__ . '/..' . '/../lib/SetupChecks/PushService.php', 'OCA\\Settings\\SetupChecks\\RandomnessSecure' => __DIR__ . '/..' . '/../lib/SetupChecks/RandomnessSecure.php', 'OCA\\Settings\\SetupChecks\\ReadOnlyConfig' => __DIR__ . '/..' . '/../lib/SetupChecks/ReadOnlyConfig.php', diff --git a/apps/settings/lib/AppInfo/Application.php b/apps/settings/lib/AppInfo/Application.php index 5faf7b5110b0c..d14cb0b7cac45 100644 --- a/apps/settings/lib/AppInfo/Application.php +++ b/apps/settings/lib/AppInfo/Application.php @@ -65,6 +65,7 @@ use OCA\Settings\SetupChecks\PhpOpcacheSetup; use OCA\Settings\SetupChecks\PhpOutdated; use OCA\Settings\SetupChecks\PhpOutputBuffering; +use OCA\Settings\SetupChecks\PhpSAPI; use OCA\Settings\SetupChecks\PushService; use OCA\Settings\SetupChecks\RandomnessSecure; use OCA\Settings\SetupChecks\ReadOnlyConfig; @@ -209,6 +210,7 @@ public function register(IRegistrationContext $context): void { $context->registerSetupCheck(PhpOpcacheSetup::class); $context->registerSetupCheck(PhpOutdated::class); $context->registerSetupCheck(PhpOutputBuffering::class); + $context->registerSetupCheck(PhpSAPI::class); $context->registerSetupCheck(RandomnessSecure::class); $context->registerSetupCheck(ReadOnlyConfig::class); $context->registerSetupCheck(RepairSanitizeSystemTagsAvailable::class); diff --git a/apps/settings/lib/SetupChecks/PhpSAPI.php b/apps/settings/lib/SetupChecks/PhpSAPI.php new file mode 100644 index 0000000000000..060c04e422224 --- /dev/null +++ b/apps/settings/lib/SetupChecks/PhpSAPI.php @@ -0,0 +1,42 @@ +l10n->t('PHP SAPI'); + } + + public function getCategory(): string { + return 'php'; + } + + public function run(): SetupResult { + $sapi_type = php_sapi_name(); + + if ($sapi_type === 'fpm-fcgi') { + $sapi_max_children_reached = fpm_get_status()['max-children-reached']; // since last restart + if ($sapi_max_children_reached === 1) { + $sapi_max_children_reached_actual = fpm_get_status()['max-active-processes']; + return SetupResult::error($this->l10n->t('Your PHP-FPM pool reached it\'s maximum number of allowed processes (' . $sapi_max_children_reached_actual . ') at least once since your last restart. You may want to increase your pm.max_children value in your PHP-FPM pool configuration to avoid problems such as Gateway Timeouts, client connection errors, and slow performance.'), $this->urlGenerator->linkToDocs('admin-php-fpm')); + } + return SetupResult::success(); + } +} From 16f8287a098de912e06bbf294895c798e8d8ccd4 Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Tue, 16 Jul 2024 19:47:05 -0400 Subject: [PATCH 2/3] fix: typo Signed-off-by: Josh Richards --- apps/settings/lib/SetupChecks/PhpSAPI.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/settings/lib/SetupChecks/PhpSAPI.php b/apps/settings/lib/SetupChecks/PhpSAPI.php index 060c04e422224..59dfaa442531d 100644 --- a/apps/settings/lib/SetupChecks/PhpSAPI.php +++ b/apps/settings/lib/SetupChecks/PhpSAPI.php @@ -36,7 +36,9 @@ public function run(): SetupResult { if ($sapi_max_children_reached === 1) { $sapi_max_children_reached_actual = fpm_get_status()['max-active-processes']; return SetupResult::error($this->l10n->t('Your PHP-FPM pool reached it\'s maximum number of allowed processes (' . $sapi_max_children_reached_actual . ') at least once since your last restart. You may want to increase your pm.max_children value in your PHP-FPM pool configuration to avoid problems such as Gateway Timeouts, client connection errors, and slow performance.'), $this->urlGenerator->linkToDocs('admin-php-fpm')); + } } + return SetupResult::success(); } } From f5334198e96e34069d885809fb75445c5757346a Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 31 Oct 2026 12:17:00 -0400 Subject: [PATCH 3/3] feat(settings): add PHP-FPM process limit setup check Report when PHP-FPM reaches its configured pm.max_children limit, including the peak active process count and pool restart time. Handle unavailable or incomplete FPM status with a warning, and link admins to the PHP-FPM documentation for troubleshooting guidance. Signed-off-by: Josh --- apps/settings/lib/SetupChecks/PhpSAPI.php | 65 +++++++++++++++++++---- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/apps/settings/lib/SetupChecks/PhpSAPI.php b/apps/settings/lib/SetupChecks/PhpSAPI.php index 59dfaa442531d..9741bc55dba0f 100644 --- a/apps/settings/lib/SetupChecks/PhpSAPI.php +++ b/apps/settings/lib/SetupChecks/PhpSAPI.php @@ -3,9 +3,10 @@ declare(strict_types=1); /** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ + namespace OCA\Settings\SetupChecks; use OCP\IL10N; @@ -20,23 +21,67 @@ public function __construct( ) { } + #[\Override] public function getName(): string { - return $this->l10n->t('PHP SAPI'); + return $this->l10n->t('PHP-FPM process limit'); } + #[\Override] public function getCategory(): string { return 'php'; } + #[\Override] public function run(): SetupResult { - $sapi_type = php_sapi_name(); - - if ($sapi_type === 'fpm-fcgi') { - $sapi_max_children_reached = fpm_get_status()['max-children-reached']; // since last restart - if ($sapi_max_children_reached === 1) { - $sapi_max_children_reached_actual = fpm_get_status()['max-active-processes']; - return SetupResult::error($this->l10n->t('Your PHP-FPM pool reached it\'s maximum number of allowed processes (' . $sapi_max_children_reached_actual . ') at least once since your last restart. You may want to increase your pm.max_children value in your PHP-FPM pool configuration to avoid problems such as Gateway Timeouts, client connection errors, and slow performance.'), $this->urlGenerator->linkToDocs('admin-php-fpm')); - } + if (php_sapi_name() !== 'fpm-fcgi') { + return SetupResult::success(); + } + + if (!function_exists('fpm_get_status')) { + return SetupResult::warning( + $this->l10n->t('PHP-FPM status is unavailable, so the max children check could not be completed.'), + $this->urlGenerator->linkToDocs('admin-php-fpm') + ); + } + + $status = fpm_get_status(); + if ($status === false) { + return SetupResult::warning( + $this->l10n->t('PHP-FPM status could not be read, so the max children check could not be completed.'), + $this->urlGenerator->linkToDocs('admin-php-fpm') + ); + } + + $maxChildrenReached = $status['max-children-reached'] ?? null; + $maxActiveProcesses = $status['max-active-processes'] ?? null; + $startTimeEpoch = $status['start-time'] ?? null; + + if (!is_int($maxChildrenReached) || !is_int($maxActiveProcesses) || !is_int($startTimeEpoch)) { + return SetupResult::warning( + $this->l10n->t('PHP-FPM status is incomplete, so the max children check could not be completed.'), + $this->urlGenerator->linkToDocs('admin-php-fpm') + ); + } + + if ($maxChildrenReached > 0) { + $startTime = date('Y-m-d H:i:s T', $startTimeEpoch); + $timesReached = $this->l10n->n('once', '%n times', $maxChildrenReached); + + $message = $this->l10n->t( + 'PHP-FPM reached its configured "pm.max_children" limit %s since its last restart at %s (peak active processes: %s).', + [$timesReached, $startTime, (string)$maxActiveProcesses], + ); + $message .= ' ' . $this->l10n->t( + 'When the pool is saturated, additional requests may be queued, time out, or fail. This can cause slow responses, client connection errors, or 502/504 gateway errors.' + ); + $message .= "\n\n" . $this->l10n->t( + 'The counter resets when PHP-FPM restarts. If saturation is ongoing or recurring, review pool configuration and server capacity and consider increasing "pm.max_children".' + ); + + return SetupResult::warning( + $message, + $this->urlGenerator->linkToDocs('admin-php-fpm'), + ); } return SetupResult::success();