"\033[32m", 'yellow' => "\033[33m", 'magenta' => "\033[35m", 'cyan' => "\033[36m", 'gray' => "\033[90m", 'reset' => "\033[0m" ]; /** * Performance thresholds (in milliseconds) */ private const SLOW_REQUEST_THRESHOLD = 1000; private const SLOW_QUERY_THRESHOLD = 100; public function __construct() { $this->startTime = defined('LARAVEL_START') ? LARAVEL_START : microtime(true); $this->startMemory = memory_get_usage(); $this->startPeakMemory = memory_get_peak_usage(); } public function handle(Request $request, Closure $next): Response { if (app()->environment('local', 'development')) { DB::enableQueryLog(); } $response = $next($request); if (app()->environment('local', 'development')) { $this->logPerformanceMetrics($request); $this->flushBuffer(); } return $response; } private function gatherMetrics(): array { $memoryLimit = $this->getMemoryLimit(); return [ 'time' => $this->getExecutionTime(), 'memory' => $this->getMemoryUsage(), 'peakMemory' => $this->getPeakMemoryUsage(), 'memoryLimit' => $memoryLimit, 'memoryPercentage' => $this->calculateMemoryPercentage($memoryLimit), 'queries' => $this->getQueryMetrics(), 'cpu' => $this->getCpuMetrics(), 'cache' => $this->getCacheMetrics(), ]; } private function getExecutionTime(): float { return (microtime(true) - $this->startTime) * 1000; } private function getMemoryUsage(): float { return (memory_get_usage() - $this->startMemory) / 1024 / 1024; } private function getPeakMemoryUsage(): float { return (memory_get_peak_usage() - $this->startPeakMemory) / 1024 / 1024; } private function getMemoryLimit(): int { $limit = ini_get('memory_limit'); if (!$limit) { return 0; } preg_match('/^(\d+)(K|M|G)?$/i', $limit, $matches); $value = (int)($matches[1] ?? 0); $unit = strtoupper($matches[2] ?? ''); return match ($unit) { 'G' => $value * 1024 * 1024 * 1024, 'M' => $value * 1024 * 1024, 'K' => $value * 1024, default => $value, }; } private function calculateMemoryPercentage(int $limit): float { if ($limit === 0) { return 0.0; } return (memory_get_usage(true) / $limit) * 100; } private function getQueryMetrics(): ?array { $queries = DB::getQueryLog(); if (empty($queries)) { return null; } $totalTime = 0; $formattedQueries = []; foreach ($queries as $query) { $totalTime += $query['time']; if ($query['time'] > self::SLOW_QUERY_THRESHOLD) { $formattedQueries[] = [ 'sql' => $this->formatSql($query['query'], $query['bindings']), 'time' => $query['time'], ]; } } return [ 'count' => count($queries), 'time' => $totalTime, 'slow_queries' => $formattedQueries, ]; } private function formatSql(string $sql, array $bindings): string { return vsprintf(str_replace(['%', '?'], ['%%', "'%s'"], $sql), $bindings); } private function getCpuMetrics(): ?array { if (!function_exists('getrusage')) { return null; } $usage = getrusage(); return [ 'user' => ($usage['ru_utime.tv_sec'] * 1000 + intval($usage['ru_utime.tv_usec'] / 1000)), 'system' => ($usage['ru_stime.tv_sec'] * 1000 + intval($usage['ru_stime.tv_usec'] / 1000)), ]; } private function getCacheMetrics(): ?array { if (!function_exists('opcache_get_status')) { return null; } $status = opcache_get_status(false); if (!$status) { return null; } $stats = $status['opcache_statistics']; $hits = $stats['hits']; $misses = $stats['misses']; $total = $hits + $misses; return [ 'hit_rate' => $total > 0 ? ($hits / $total * 100) : 0, 'memory_usage' => $status['memory_usage'], ]; } private function appendToBuffer(string $text): void { $this->buffer .= $text; } private function flushBuffer(): void { if (empty($this->buffer)) { return; } try { $written = file_put_contents('php://stderr', $this->buffer); if ($written === false) { error_log('Failed to write performance metrics to stderr'); } } catch (\Throwable $e) { error_log("Error writing performance metrics: {$e->getMessage()}"); } finally { $this->buffer = ''; } } private function colorize(string $text, string $color): string { return self::COLORS[$color] . $text . self::COLORS['reset']; } private function logPerformanceMetrics(Request $request): void { $metrics = $this->gatherMetrics(); // Request information $this->appendToBuffer(sprintf( "\n%s %s %s[%s]%s\n", $this->colorize($request->method(), 'green'), $this->colorize($request->getRequestUri(), 'green'), self::COLORS['gray'], date('Y-m-d H:i:s'), self::COLORS['reset'] )); // Core metrics $this->appendToBuffer(sprintf( "➜ Time: %s%.2fms%s | Memory: %s%.2fMB%s (%.1f%%) | Peak: %s%.2fMB%s", self::COLORS['yellow'], $metrics['time'], self::COLORS['reset'], self::COLORS['magenta'], $metrics['memory'], self::COLORS['reset'], $metrics['memoryPercentage'], self::COLORS['magenta'], $metrics['peakMemory'], self::COLORS['reset'] )); // Query metrics if ($metrics['queries']) { $this->appendToBuffer(sprintf( " | Queries: %s%d%s (%s%.2fms%s)", self::COLORS['cyan'], $metrics['queries']['count'], self::COLORS['reset'], self::COLORS['cyan'], $metrics['queries']['time'], self::COLORS['reset'] )); } // CPU metrics if ($metrics['cpu']) { $totalCpu = $metrics['cpu']['user'] + $metrics['cpu']['system']; $this->appendToBuffer(sprintf( " | CPU: %s%.2fms%s", self::COLORS['cyan'], $totalCpu, self::COLORS['reset'] )); } // Cache metrics if ($metrics['cache']) { $this->appendToBuffer(sprintf( " | OPcache: %s%.1f%%%s", self::COLORS['cyan'], $metrics['cache']['hit_rate'], self::COLORS['reset'] )); } $this->appendToBuffer("\n"); // Log slow queries if ($metrics['time'] > self::SLOW_REQUEST_THRESHOLD && !empty($metrics['queries']['slow_queries'])) { $this->appendToBuffer("\n" . $this->colorize("Slow Queries Detected:", 'yellow') . "\n"); foreach ($metrics['queries']['slow_queries'] as $query) { $this->appendToBuffer(sprintf( "➜ %s%.2fms%s: %s\n", self::COLORS['yellow'], $query['time'], self::COLORS['reset'], $query['sql'] )); } } } }