Add memory_limit/max_execution_time sanity checks (#106)
config/requirements.php only checked PHP version and 6 extensions. A deployment on a host with a low default memory_limit (e.g. shared- hosting-style 128M) passed the startup requirements check cleanly and only surfaced as a mysterious failure under real load — a large CSV export, an oversized dashboard query on a big install. Added min_memory_limit_mb (256) and min_max_execution_time (30s) thresholds to config/requirements.php, checked as warnings (not hard failures, since a low limit doesn't break every request) in both scripts/check_requirements.php (CI) and api/health.php (production monitoring). -1/0 (unlimited) always passes. Verified the ini-size parsing and warning logic directly with low/high/unlimited memory_limit and max_execution_time values. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X
This commit is contained in:
@@ -129,6 +129,39 @@ if (version_compare(PHP_VERSION, $requirements['min_php_version'], '>=')) {
|
|||||||
$healthy = false;
|
$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
|
// Calculate response time
|
||||||
$responseTime = round((microtime(true) - $startTime) * 1000, 2);
|
$responseTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||||
|
|
||||||
|
|||||||
@@ -25,4 +25,11 @@ return [
|
|||||||
'fileinfo', // api/upload_attachment.php — MIME validation
|
'fileinfo', // api/upload_attachment.php — MIME validation
|
||||||
'json', // request/response encoding (bundled, but assert anyway)
|
'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
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -10,9 +10,30 @@
|
|||||||
* Usage: php scripts/check_requirements.php
|
* 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';
|
$req = require __DIR__ . '/../config/requirements.php';
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
$warnings = [];
|
||||||
|
|
||||||
// PHP version
|
// PHP version
|
||||||
$minPhp = $req['min_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)) {
|
if (!empty($errors)) {
|
||||||
fwrite(STDERR, "Requirement check FAILED:\n");
|
fwrite(STDERR, "Requirement check FAILED:\n");
|
||||||
foreach ($errors as $err) {
|
foreach ($errors as $err) {
|
||||||
@@ -35,6 +78,10 @@ if (!empty($errors)) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($warnings as $warn) {
|
||||||
|
fwrite(STDERR, "Requirement check WARNING: " . $warn . "\n");
|
||||||
|
}
|
||||||
|
|
||||||
printf(
|
printf(
|
||||||
"Requirement check passed: PHP %s (>= %s); extensions: %s\n",
|
"Requirement check passed: PHP %s (>= %s); extensions: %s\n",
|
||||||
PHP_VERSION,
|
PHP_VERSION,
|
||||||
|
|||||||
Reference in New Issue
Block a user