181 lines
6.2 KiB
PHP
181 lines
6.2 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Services\Admin\AdminSlowRequestReporter;
|
|
use Illuminate\Console\Command;
|
|
|
|
class ReportAdminSlowRequests extends Command
|
|
{
|
|
/**
|
|
* The name and signature of the console command.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'admin:slow-requests
|
|
{--from= : Startzeitpunkt, z.B. 2026-04-29 oder 2026-04-29 08:00:00}
|
|
{--to= : Endzeitpunkt}
|
|
{--route= : Auf Route-Namen filtern}
|
|
{--path= : Auf Pfad filtern}
|
|
{--status= : Auf HTTP-Statuscode filtern}
|
|
{--min-duration= : Mindestdauer in Millisekunden}
|
|
{--top=10 : Anzahl Top-Zeilen pro Abschnitt}
|
|
{--limit=25 : Anzahl Detailzeilen}
|
|
{--file=* : Optionaler Logdatei-Pfad oder Glob}';
|
|
|
|
/**
|
|
* The console command description.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = 'Wertet Slow-Admin-Request-Logs aus.';
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*/
|
|
public function handle(AdminSlowRequestReporter $reporter): int
|
|
{
|
|
$top = max(1, (int) $this->option('top'));
|
|
$limit = max(1, (int) $this->option('limit'));
|
|
$files = array_values(array_filter(array_map('strval', (array) $this->option('file'))));
|
|
|
|
$report = $reporter->report(
|
|
filters: [
|
|
'from' => $this->option('from') !== null ? (string) $this->option('from') : null,
|
|
'to' => $this->option('to') !== null ? (string) $this->option('to') : null,
|
|
'route' => $this->option('route') !== null ? (string) $this->option('route') : null,
|
|
'path' => $this->option('path') !== null ? (string) $this->option('path') : null,
|
|
'status' => $this->option('status') !== null ? (int) $this->option('status') : null,
|
|
'min_duration_ms' => $this->option('min-duration') !== null ? (int) $this->option('min-duration') : null,
|
|
],
|
|
top: $top,
|
|
limit: $limit,
|
|
paths: $files !== [] ? $files : null,
|
|
);
|
|
|
|
$this->info('Slow-Admin-Request-Report');
|
|
$this->newLine();
|
|
$this->line("Dateien: {$report['summary']['files']}");
|
|
$this->line("Requests: {$report['summary']['total_requests']}");
|
|
$this->line("Routen: {$report['summary']['unique_routes']}");
|
|
$this->line("Durchschnitt Dauer: {$report['summary']['average_duration_ms']} ms");
|
|
$this->line("Max. Dauer: {$report['summary']['max_duration_ms']} ms");
|
|
$this->line("Durchschnitt DB-Zeit: {$report['summary']['average_database_time_ms']} ms");
|
|
$this->line("Max. Query-Anzahl: {$report['summary']['max_query_count']}");
|
|
|
|
$this->renderTopTable('Top Routen', $report['top_routes']);
|
|
$this->renderTopTable('Top Pfade', $report['top_paths']);
|
|
$this->renderTopTable('Statuscodes', $report['status_codes']);
|
|
$this->renderRequests('Langsamste Requests', $report['slowest_requests']);
|
|
$this->renderRequests('Query-lastige Requests', $report['query_heavy_requests']);
|
|
$this->renderSlowQueries($report['slow_queries']);
|
|
$this->renderExplainPlans($report['explain_plans']);
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string, mixed>> $rows
|
|
*/
|
|
private function renderTopTable(string $title, array $rows): void
|
|
{
|
|
if ($rows === []) {
|
|
return;
|
|
}
|
|
|
|
$this->newLine();
|
|
$this->line($title);
|
|
$this->table(
|
|
['Wert', 'Requests', 'Ø Dauer', 'Max Dauer', 'Ø DB', 'Queries'],
|
|
collect($rows)
|
|
->map(fn (array $row): array => [
|
|
$row['value'],
|
|
$row['requests'],
|
|
$row['average_duration_ms'].' ms',
|
|
$row['max_duration_ms'].' ms',
|
|
$row['average_database_time_ms'].' ms',
|
|
$row['total_queries'],
|
|
])
|
|
->all()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string, mixed>> $rows
|
|
*/
|
|
private function renderRequests(string $title, array $rows): void
|
|
{
|
|
if ($rows === []) {
|
|
return;
|
|
}
|
|
|
|
$this->newLine();
|
|
$this->line($title);
|
|
$this->table(
|
|
['Zeit', 'Route', 'Pfad', 'Status', 'Dauer', 'DB', 'Queries'],
|
|
collect($rows)
|
|
->map(fn (array $row): array => [
|
|
$row['timestamp'],
|
|
$row['route_name'],
|
|
$row['path'],
|
|
$row['status_code'],
|
|
$row['duration_ms'].' ms',
|
|
$row['database_time_ms'].' ms',
|
|
$row['query_count'],
|
|
])
|
|
->all()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string, mixed>> $rows
|
|
*/
|
|
private function renderSlowQueries(array $rows): void
|
|
{
|
|
if ($rows === []) {
|
|
return;
|
|
}
|
|
|
|
$this->newLine();
|
|
$this->line('Häufige Slow Queries');
|
|
$this->table(
|
|
['SQL', 'Vorkommen', 'Ø Zeit', 'Max Zeit'],
|
|
collect($rows)
|
|
->map(fn (array $row): array => [
|
|
str($row['sql'])->limit(100)->toString(),
|
|
$row['occurrences'],
|
|
$row['average_time_ms'].' ms',
|
|
$row['max_time_ms'].' ms',
|
|
])
|
|
->all()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string, mixed>> $rows
|
|
*/
|
|
private function renderExplainPlans(array $rows): void
|
|
{
|
|
if ($rows === []) {
|
|
return;
|
|
}
|
|
|
|
$this->newLine();
|
|
$this->line('EXPLAIN Top Slow Queries');
|
|
|
|
foreach ($rows as $row) {
|
|
$this->line(str($row['sql'])->limit(120)->toString());
|
|
|
|
if ($row['error'] !== null) {
|
|
$this->warn((string) $row['error']);
|
|
|
|
continue;
|
|
}
|
|
|
|
$this->table(
|
|
array_keys($row['plan'][0] ?? []),
|
|
collect($row['plan'])->map(fn (array $planRow): array => array_values($planRow))->all()
|
|
);
|
|
}
|
|
}
|
|
}
|