diff --git a/api/health.php b/api/health.php index 908eb31..8be48e4 100644 --- a/api/health.php +++ b/api/health.php @@ -129,6 +129,39 @@ if (version_compare(PHP_VERSION, $requirements['min_php_version'], '>=')) { $healthy = false; } +// Check 7: memory_limit / max_execution_time sanity (warnings, not fatal — a +// low default doesn't fail requests until something large actually runs, so +// surface it here rather than waiting for a mysterious failure under load). +$memLimitIni = ini_get('memory_limit'); +$memLimitUnit = strtolower(substr(trim($memLimitIni), -1)); +$memLimitBytes = $memLimitIni === '-1' + ? -1 + : (int)$memLimitIni * match ($memLimitUnit) { + 'g' => 1024 * 1024 * 1024, + 'm' => 1024 * 1024, + 'k' => 1024, + default => 1, + }; +$minMemBytes = $requirements['min_memory_limit_mb'] * 1024 * 1024; +if ($memLimitBytes === -1 || $memLimitBytes >= $minMemBytes) { + $checks['memory_limit'] = ['status' => 'ok', 'message' => $memLimitIni]; +} else { + $checks['memory_limit'] = [ + 'status' => 'warning', + 'message' => sprintf('%s is below the recommended minimum %dM', $memLimitIni, $requirements['min_memory_limit_mb']) + ]; +} + +$maxExecTime = (int)ini_get('max_execution_time'); +if ($maxExecTime === 0 || $maxExecTime >= $requirements['min_max_execution_time']) { + $checks['max_execution_time'] = ['status' => 'ok', 'message' => (string)$maxExecTime]; +} else { + $checks['max_execution_time'] = [ + 'status' => 'warning', + 'message' => sprintf('%ds is below the recommended minimum %ds', $maxExecTime, $requirements['min_max_execution_time']) + ]; +} + // Calculate response time $responseTime = round((microtime(true) - $startTime) * 1000, 2); diff --git a/config/requirements.php b/config/requirements.php index d44c6ba..68784f0 100644 --- a/config/requirements.php +++ b/config/requirements.php @@ -25,4 +25,11 @@ return [ 'fileinfo', // api/upload_attachment.php — MIME validation 'json', // request/response encoding (bundled, but assert anyway) ], + + // Sanity-check thresholds (warnings, not hard failures). A host with a low + // default memory_limit passes a bare extension/version check cleanly and + // only surfaces as a mysterious failure under real load — a large CSV + // export, an oversized dashboard query on a big install. + 'min_memory_limit_mb' => 256, + 'min_max_execution_time' => 30, // seconds; 0 (unlimited) always passes ]; diff --git a/scripts/check_requirements.php b/scripts/check_requirements.php index 3e60ffb..c0f48c8 100644 --- a/scripts/check_requirements.php +++ b/scripts/check_requirements.php @@ -10,9 +10,30 @@ * Usage: php scripts/check_requirements.php */ +/** + * Parse a php.ini size value (e.g. "128M", "1G", "-1") into bytes. + * Returns -1 for unlimited. + */ +function parseIniBytes(string $val): int +{ + $val = trim($val); + if ($val === '' || $val === '-1') { + return -1; + } + $unit = strtolower(substr($val, -1)); + $num = (int)$val; + return match ($unit) { + 'g' => $num * 1024 * 1024 * 1024, + 'm' => $num * 1024 * 1024, + 'k' => $num * 1024, + default => $num, + }; +} + $req = require __DIR__ . '/../config/requirements.php'; $errors = []; +$warnings = []; // PHP version $minPhp = $req['min_php_version']; @@ -27,6 +48,28 @@ foreach ($req['required_extensions'] as $ext) { } } +// memory_limit / max_execution_time sanity checks (warnings, not hard +// failures — see config/requirements.php for why these matter). +$memLimitIni = ini_get('memory_limit'); +$memLimitBytes = parseIniBytes($memLimitIni); +$minMemBytes = $req['min_memory_limit_mb'] * 1024 * 1024; +if ($memLimitBytes !== -1 && $memLimitBytes < $minMemBytes) { + $warnings[] = sprintf( + 'memory_limit is %s, below the recommended minimum %dM', + $memLimitIni, + $req['min_memory_limit_mb'] + ); +} + +$maxExecTime = (int)ini_get('max_execution_time'); +if ($maxExecTime !== 0 && $maxExecTime < $req['min_max_execution_time']) { + $warnings[] = sprintf( + 'max_execution_time is %ds, below the recommended minimum %ds', + $maxExecTime, + $req['min_max_execution_time'] + ); +} + if (!empty($errors)) { fwrite(STDERR, "Requirement check FAILED:\n"); foreach ($errors as $err) { @@ -35,6 +78,10 @@ if (!empty($errors)) { exit(1); } +foreach ($warnings as $warn) { + fwrite(STDERR, "Requirement check WARNING: " . $warn . "\n"); +} + printf( "Requirement check passed: PHP %s (>= %s); extensions: %s\n", PHP_VERSION,