- 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
255 lines
11 KiB
PHP
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;
|
|
}
|
|
}
|