Files
michaelschiemer/src/Framework/Console/Commands/SlowCommandsCommand.php
Michael Schiemer 5050c7d73a docs: consolidate documentation into organized structure
- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
2025-10-05 11:05:04 +02:00

255 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Console\Commands;
use App\Framework\Console\CommandGroup;
use App\Framework\Console\ConsoleColor;
use App\Framework\Console\ConsoleCommand;
use App\Framework\Console\ConsoleInput;
use App\Framework\Console\ConsoleOutputInterface;
use App\Framework\Console\ExitCode;
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
use App\Framework\Performance\PerformanceCategory;
#[CommandGroup(
name: 'Performance',
description: 'Console performance monitoring and analysis commands',
icon: '📊',
priority: 80
)]
final readonly class SlowCommandsCommand
{
public function __construct(
private PerformanceCollectorInterface $performanceCollector
) {
}
#[ConsoleCommand('perf:slow-commands', 'Show slowest console commands')]
public function showSlowCommands(ConsoleInput $input, ConsoleOutputInterface $output, int $limit = 10, float $threshold = 1000.0): ExitCode
{
$output->writeLine('🐌 Slowest Console Commands', ConsoleColor::BRIGHT_YELLOW);
$output->writeLine(sprintf('Threshold: %.0fms | Showing top %d', $threshold, $limit), ConsoleColor::GRAY);
$output->newLine();
$consoleMetrics = $this->performanceCollector->getMetrics(PerformanceCategory::CONSOLE);
$slowCommands = $this->findSlowCommands($consoleMetrics, $threshold);
if (empty($slowCommands)) {
$output->writeLine('🎉 No slow commands found!', ConsoleColor::GREEN);
$output->writeLine(sprintf('All commands executed faster than %.0fms', $threshold), ConsoleColor::GRAY);
return ExitCode::SUCCESS;
}
// Sort by duration (descending)
usort($slowCommands, fn ($a, $b) => $b['duration'] <=> $a['duration']);
$slowCommands = array_slice($slowCommands, 0, $limit);
$this->displaySlowCommandsTable($output, $slowCommands);
$output->newLine();
$this->displayPerformanceRecommendations($output, $slowCommands);
return ExitCode::SUCCESS;
}
#[ConsoleCommand('perf:watch-slow', 'Monitor for slow commands in real-time')]
public function watchSlowCommands(ConsoleInput $input, ConsoleOutputInterface $output, float $threshold = 5000.0): ExitCode
{
$output->writeLine('👀 Monitoring Slow Commands', ConsoleColor::BRIGHT_CYAN);
$output->writeLine(sprintf('Threshold: %.0fms | Press Ctrl+C to stop', $threshold), ConsoleColor::GRAY);
$output->newLine();
$lastMetricsCount = 0;
while (true) {
$consoleMetrics = $this->performanceCollector->getMetrics(PerformanceCategory::CONSOLE);
$currentMetricsCount = count($consoleMetrics);
// Check if new metrics were added
if ($currentMetricsCount > $lastMetricsCount) {
$newMetrics = array_slice($consoleMetrics, $lastMetricsCount);
$this->checkForSlowCommands($output, $newMetrics, $threshold);
$lastMetricsCount = $currentMetricsCount;
}
usleep(500000); // Wait 0.5 seconds
}
}
#[ConsoleCommand('perf:profile-command', 'Profile a specific command execution')]
public function profileCommand(ConsoleInput $input, ConsoleOutputInterface $output, string $commandName, string ...$args): ExitCode
{
$output->writeLine("🔍 Profiling Command: {$commandName}", ConsoleColor::BRIGHT_BLUE);
$output->newLine();
// Get baseline metrics
$beforeMetrics = $this->getCommandMetrics($commandName);
$output->writeLine('📊 Baseline metrics captured', ConsoleColor::GRAY);
$output->writeLine("Execute the command now: php console.php {$commandName} " . implode(' ', $args), ConsoleColor::YELLOW);
$output->writeLine('Press Enter when command execution is complete...', ConsoleColor::GRAY);
// Wait for user input
fgets(STDIN);
// Get after metrics
$afterMetrics = $this->getCommandMetrics($commandName);
$this->displayProfilingResults($output, $commandName, $beforeMetrics, $afterMetrics);
return ExitCode::SUCCESS;
}
private function findSlowCommands(array $metrics, float $threshold): array
{
$slowCommands = [];
foreach ($metrics as $metric) {
$key = $metric->getKey();
// Look for command timing metrics
if (str_contains($key, 'console_command_') && ! str_contains($key, '_error') && ! str_contains($key, '_memory')) {
$duration = $metric->getTotalDuration();
if ($duration >= $threshold) {
$context = $metric->getContext();
$commandName = $context['command_name'] ?? $this->extractCommandNameFromKey($key);
$slowCommands[] = [
'command' => $commandName,
'duration' => $duration,
'executions' => $metric->getCount(),
'average_duration' => $metric->getAverageDuration(),
'memory_usage' => $this->getMemoryUsageForCommand($metrics, $commandName),
];
}
}
}
return $slowCommands;
}
private function displaySlowCommandsTable(ConsoleOutputInterface $output, array $slowCommands): void
{
$output->writeLine('┌─────────────────────────────────────────────────────────────────────┐', ConsoleColor::GRAY);
$output->writeLine('│ Command │ Duration │ Executions │ Avg │ Memory │', ConsoleColor::GRAY);
$output->writeLine('├─────────────────────────────────────────────────────────────────────┤', ConsoleColor::GRAY);
foreach ($slowCommands as $command) {
$commandName = str_pad(substr($command['command'], 0, 25), 26);
$duration = str_pad(sprintf('%.0fms', $command['duration']), 8);
$executions = str_pad((string) $command['executions'], 10);
$average = str_pad(sprintf('%.0fms', $command['average_duration']), 6);
$memory = str_pad(sprintf('%.1fMB', $command['memory_usage']), 6);
$color = $command['duration'] > 10000 ? ConsoleColor::RED :
($command['duration'] > 5000 ? ConsoleColor::YELLOW : ConsoleColor::WHITE);
$output->writeLine("{$commandName}{$duration}{$executions}{$average}{$memory}", $color);
}
$output->writeLine('└─────────────────────────────────────────────────────────────────────┘', ConsoleColor::GRAY);
}
private function displayPerformanceRecommendations(ConsoleOutputInterface $output, array $slowCommands): void
{
$output->writeLine('💡 Performance Recommendations', ConsoleColor::BRIGHT_WHITE);
$output->writeLine('─────────────────────────────', ConsoleColor::GRAY);
$recommendations = [];
foreach ($slowCommands as $command) {
if ($command['duration'] > 30000) {
$recommendations[] = "🔴 {$command['command']}: Consider breaking into smaller operations or adding progress indicators";
} elseif ($command['duration'] > 10000) {
$recommendations[] = "🟡 {$command['command']}: Investigate for optimization opportunities";
}
if ($command['memory_usage'] > 100) {
$recommendations[] = "💾 {$command['command']}: High memory usage - consider streaming or batch processing";
}
}
if (empty($recommendations)) {
$output->writeLine('All commands are performing within acceptable limits! ✅', ConsoleColor::GREEN);
} else {
foreach ($recommendations as $recommendation) {
$output->writeLine(" {$recommendation}", ConsoleColor::WHITE);
}
}
}
private function checkForSlowCommands(ConsoleOutputInterface $output, array $newMetrics, float $threshold): void
{
foreach ($newMetrics as $metric) {
$duration = $metric->getTotalDuration();
if ($duration >= $threshold) {
$context = $metric->getContext();
$commandName = $context['command_name'] ?? 'unknown';
$timestamp = date('H:i:s');
$output->writeLine(
sprintf('[%s] 🐌 Slow command detected: %s (%.0fms)', $timestamp, $commandName, $duration),
ConsoleColor::RED
);
}
}
}
private function getCommandMetrics(string $commandName): array
{
$allMetrics = $this->performanceCollector->getMetrics(PerformanceCategory::CONSOLE);
return array_filter($allMetrics, function ($metric) use ($commandName) {
return str_contains($metric->getKey(), $commandName);
});
}
private function displayProfilingResults(ConsoleOutputInterface $output, string $commandName, array $before, array $after): void
{
$output->writeLine("📈 Profiling Results for: {$commandName}", ConsoleColor::BRIGHT_GREEN);
$output->writeLine('───────────────────────────────────────', ConsoleColor::GRAY);
$newMetrics = array_diff_key($after, $before);
if (empty($newMetrics)) {
$output->writeLine('No new metrics detected for this command execution.', ConsoleColor::YELLOW);
return;
}
foreach ($newMetrics as $metric) {
$output->writeLine(sprintf(' %s: %s', $metric->getKey(), $metric->getFormattedValue()), ConsoleColor::WHITE);
}
}
private function extractCommandNameFromKey(string $key): string
{
if (preg_match('/console_command_(.+)/', $key, $matches)) {
return $matches[1];
}
return 'unknown';
}
private function getMemoryUsageForCommand(array $metrics, string $commandName): float
{
foreach ($metrics as $metric) {
if (str_contains($metric->getKey(), "console_memory_usage") &&
str_contains($metric->getKey(), $commandName)) {
return $metric->getValue();
}
}
return 0.0;
}
}