Parse rotated nginx JSON access logs into host/route timings so production hotspots can be measured without ad-hoc one-off scripts.
119 lines
3.1 KiB
PHP
119 lines
3.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Read-only analyzer for nginx skinbase-performance JSON logs.
|
|
*
|
|
* Single file (backward compatible):
|
|
* php scripts/analyze-http-performance.php --file=/var/log/nginx/skinbase-performance.log --since=24h --host=skinbase.org
|
|
*
|
|
* Rotation-aware 24h:
|
|
* php scripts/analyze-http-performance.php --glob='/var/log/nginx/skinbase-performance.log*' --since=24h --host=skinbase.org
|
|
*
|
|
* Status:
|
|
* --status=404 exact HTTP 404
|
|
* --status=4xx 400-499
|
|
* --status=5xx 500-599
|
|
*/
|
|
|
|
use App\Support\Http\HttpPerformanceLogAnalyzer;
|
|
use App\Support\Http\HttpUriNormalizer;
|
|
|
|
require __DIR__.'/../vendor/autoload.php';
|
|
|
|
$files = [];
|
|
$globs = [];
|
|
$options = [
|
|
'since' => '24h',
|
|
'until' => null,
|
|
'min_requests' => 1,
|
|
'top' => 20,
|
|
'status' => null,
|
|
'uri' => null,
|
|
'host' => null,
|
|
'slow_ms' => null,
|
|
];
|
|
|
|
foreach (array_slice($argv, 1) as $arg) {
|
|
if (! str_starts_with($arg, '--')) {
|
|
fwrite(STDERR, "Unknown argument: {$arg}\n");
|
|
exit(2);
|
|
}
|
|
[$key, $value] = array_pad(explode('=', substr($arg, 2), 2), 2, '1');
|
|
if ($key === 'file') {
|
|
$files[] = $value;
|
|
continue;
|
|
}
|
|
if ($key === 'glob') {
|
|
$globs[] = $value;
|
|
continue;
|
|
}
|
|
$map = [
|
|
'since' => 'since',
|
|
'until' => 'until',
|
|
'hours' => 'hours',
|
|
'min-requests' => 'min_requests',
|
|
'top' => 'top',
|
|
'status' => 'status',
|
|
'uri' => 'uri',
|
|
'host' => 'host',
|
|
'slow-ms' => 'slow_ms',
|
|
];
|
|
if (! isset($map[$key])) {
|
|
fwrite(STDERR, "Unknown option: --{$key}\n");
|
|
exit(2);
|
|
}
|
|
$options[$map[$key]] = $value;
|
|
}
|
|
|
|
if (isset($options['hours']) && is_numeric($options['hours'])) {
|
|
$options['since'] = ((int) $options['hours']).'h';
|
|
}
|
|
|
|
$analyzer = new HttpPerformanceLogAnalyzer(new HttpUriNormalizer());
|
|
|
|
$discovered = $files;
|
|
foreach ($globs as $pattern) {
|
|
$discovered = array_merge($discovered, $analyzer->expandGlob($pattern));
|
|
}
|
|
|
|
if ($discovered === [] && $globs === []) {
|
|
$discovered[] = '/var/log/nginx/skinbase-performance.log';
|
|
}
|
|
|
|
$discovered = $analyzer->deduplicatePaths($discovered);
|
|
if ($discovered === []) {
|
|
fwrite(STDERR, "No matching performance log files.\n");
|
|
exit(1);
|
|
}
|
|
|
|
foreach ($discovered as $path) {
|
|
if (! is_readable($path)) {
|
|
fwrite(STDERR, "Cannot read {$path}\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
try {
|
|
$result = $analyzer->analyze($discovered, [
|
|
'since' => $options['since'],
|
|
'until' => $options['until'],
|
|
'min_requests' => (int) $options['min_requests'],
|
|
'top' => (int) $options['top'],
|
|
'status' => $options['status'],
|
|
'uri' => $options['uri'],
|
|
'host' => $options['host'],
|
|
'slow_ms' => $options['slow_ms'] !== null ? (float) $options['slow_ms'] : null,
|
|
]);
|
|
} catch (InvalidArgumentException $e) {
|
|
fwrite(STDERR, $e->getMessage()."\n");
|
|
exit(2);
|
|
}
|
|
|
|
foreach ($result['warnings'] ?? [] as $warning) {
|
|
fwrite(STDERR, $warning."\n");
|
|
}
|
|
|
|
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;
|