| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- <?php
- namespace App\Http\Middleware;
- use Closure;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Log;
- use Symfony\Component\HttpFoundation\Response;
- class PerformanceMonitor
- {
- /**
- * Initial performance markers
- */
- private float $startTime;
- private int $startMemory;
- private int $startPeakMemory;
- /**
- * Output buffer
- */
- private string $buffer = '';
- /**
- * ANSI color codes for console output
- */
- private const COLORS = [
- 'green' => "\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']
- ));
- }
- }
- }
- }
|