reset(); } /** * Reset detector for new measurement */ public function reset(): void { $this->initialMemory = memory_get_usage(true); $this->startTime = microtime(true); gc_collect_cycles(); // Clean up before measurement } /** * Start measuring with initial statistics */ public function startMeasuring(?Statistics $initialStats = null): void { $this->initialStats = $initialStats ?? Statistics::countersOnly([]); $this->reset(); } /** * Create a comprehensive memory usage report */ public function createReport(?Statistics $finalStats = null): MemoryLeakReport { $finalMemory = memory_get_usage(true); $endTime = microtime(true); $memoryGrowth = $finalMemory - $this->initialMemory; $duration = $endTime - $this->startTime; return new MemoryLeakReport( initialMemory: $this->initialMemory, finalMemory: $finalMemory, memoryGrowth: $memoryGrowth, duration: $duration, initialStats: $this->initialStats, finalStats: $finalStats, thresholds: [ 'memory' => $this->memoryThresholdBytes, 'time' => $this->timeThresholdSeconds, ] ); } /** * Quick check if memory growth exceeds threshold */ public function hasMemoryLeak(): bool { $currentMemory = memory_get_usage(true); $memoryGrowth = $currentMemory - $this->initialMemory; return $memoryGrowth > $this->memoryThresholdBytes; } /** * Get current memory growth in bytes */ public function getCurrentMemoryGrowth(): int { return memory_get_usage(true) - $this->initialMemory; } /** * Get current duration in seconds */ public function getCurrentDuration(): float { return microtime(true) - $this->startTime; } /** * Force garbage collection and return freed memory */ public function forceGarbageCollection(): int { $memoryBefore = memory_get_usage(true); gc_collect_cycles(); $memoryAfter = memory_get_usage(true); return $memoryBefore - $memoryAfter; } /** * Run a memory-intensive operation with monitoring * * @param callable $operation The operation to monitor * @param int $iterations Number of iterations to run * @param bool $gcBetweenIterations Whether to run GC between iterations */ public function monitorOperation( callable $operation, int $iterations = 1, bool $gcBetweenIterations = false ): array { $measurements = []; $this->reset(); for ($i = 0; $i < $iterations; $i++) { $iterationStart = memory_get_usage(true); $timeStart = microtime(true); $operation($i); $iterationEnd = memory_get_usage(true); $timeEnd = microtime(true); $measurements[] = [ 'iteration' => $i, 'memory_before' => $iterationStart, 'memory_after' => $iterationEnd, 'memory_growth' => $iterationEnd - $iterationStart, 'duration' => $timeEnd - $timeStart, 'total_memory_growth' => $iterationEnd - $this->initialMemory, ]; if ($gcBetweenIterations && $i < $iterations - 1) { gc_collect_cycles(); } } return $measurements; } /** * Analyze memory growth pattern to detect leaks */ public function analyzeGrowthPattern(array $measurements): array { if (empty($measurements)) { return ['pattern' => 'no_data', 'severity' => 'none']; } $growthRates = []; $totalGrowth = end($measurements)['total_memory_growth']; foreach ($measurements as $i => $measurement) { if ($i > 0) { $prevTotal = $measurements[$i - 1]['total_memory_growth']; $currentTotal = $measurement['total_memory_growth']; $growthRates[] = $currentTotal - $prevTotal; } } $avgGrowthRate = array_sum($growthRates) / count($growthRates); $maxGrowthRate = max($growthRates); $growthVariance = $this->calculateVariance($growthRates); // Classify growth pattern $pattern = 'stable'; $severity = 'none'; if ($avgGrowthRate > 10 * 1024) { // > 10KB per iteration $pattern = 'linear_growth'; $severity = $avgGrowthRate > 100 * 1024 ? 'high' : 'medium'; } if ($growthVariance > 50 * 1024 * 1024) { // High variance $pattern = 'erratic_growth'; $severity = 'medium'; } if ($totalGrowth > $this->memoryThresholdBytes) { $severity = 'high'; } return [ 'pattern' => $pattern, 'severity' => $severity, 'total_growth' => $totalGrowth, 'avg_growth_rate' => $avgGrowthRate, 'max_growth_rate' => $maxGrowthRate, 'growth_variance' => $growthVariance, 'recommendations' => $this->generateRecommendations($pattern, $severity, $avgGrowthRate), ]; } /** * Calculate variance of an array of numbers */ private function calculateVariance(array $numbers): float { if (empty($numbers)) { return 0.0; } $mean = array_sum($numbers) / count($numbers); $squaredDiffs = array_map(fn ($x) => ($x - $mean) ** 2, $numbers); return array_sum($squaredDiffs) / count($numbers); } /** * Generate recommendations based on growth pattern */ private function generateRecommendations(string $pattern, string $severity, float $avgGrowthRate): array { $recommendations = []; switch ($pattern) { case 'linear_growth': $recommendations[] = 'Implement cache size limits or LRU eviction'; $recommendations[] = 'Review object lifecycle and ensure proper cleanup'; break; case 'erratic_growth': $recommendations[] = 'Investigate irregular memory allocations'; $recommendations[] = 'Check for memory spikes during specific operations'; break; } if ($severity === 'high') { $recommendations[] = 'Critical: Memory usage exceeds safe thresholds'; $recommendations[] = 'Profile application with memory debugging tools'; } if ($avgGrowthRate > 50 * 1024) { $recommendations[] = 'Consider implementing garbage collection triggers'; $recommendations[] = 'Review data structure choices for memory efficiency'; } return $recommendations; } /** * Set custom memory threshold */ public function setMemoryThreshold(int $bytes): self { $this->memoryThresholdBytes = $bytes; return $this; } /** * Set custom time threshold */ public function setTimeThreshold(float $seconds): self { $this->timeThresholdSeconds = $seconds; return $this; } } /** * Memory leak analysis report */ final readonly class MemoryLeakReport { public function __construct( public int $initialMemory, public int $finalMemory, public int $memoryGrowth, public float $duration, public Statistics $initialStats, public ?Statistics $finalStats, public array $thresholds ) { } /** * Check if this report indicates a memory leak */ public function hasMemoryLeak(): bool { return $this->memoryGrowth > $this->thresholds['memory']; } /** * Check if operation was too slow */ public function isSlowPerformance(): bool { return $this->duration > $this->thresholds['time']; } /** * Get memory growth in MB */ public function getMemoryGrowthMB(): float { return $this->memoryGrowth / (1024 * 1024); } /** * Get comprehensive summary */ public function getSummary(): array { $summary = [ 'memory_growth_bytes' => $this->memoryGrowth, 'memory_growth_mb' => $this->getMemoryGrowthMB(), 'duration_seconds' => $this->duration, 'has_memory_leak' => $this->hasMemoryLeak(), 'is_slow_performance' => $this->isSlowPerformance(), ]; if ($this->finalStats !== null) { $summary['cache_growth'] = $this->finalStats->getTotalCount() - $this->initialStats->getTotalCount(); if ($this->finalStats->getMemoryUsageMb() !== null) { $initialMemoryMB = $this->initialStats->getMemoryUsageMb() ?? 0; $finalMemoryMB = $this->finalStats->getMemoryUsageMb(); $summary['reported_memory_growth_mb'] = $finalMemoryMB - $initialMemoryMB; } } return $summary; } /** * Format report for display */ public function format(): string { $output = "\n=== Memory Leak Analysis Report ===\n"; $output .= sprintf( "Memory Growth: %s bytes (%.2f MB)\n", number_format($this->memoryGrowth), $this->getMemoryGrowthMB() ); $output .= sprintf("Duration: %.3f seconds\n", $this->duration); $output .= sprintf("Leak Detected: %s\n", $this->hasMemoryLeak() ? 'YES' : 'NO'); $output .= sprintf("Performance Issue: %s\n", $this->isSlowPerformance() ? 'YES' : 'NO'); if ($this->finalStats !== null) { $cacheGrowth = $this->finalStats->getTotalCount() - $this->initialStats->getTotalCount(); $output .= sprintf("Cache Items Growth: %d\n", $cacheGrowth); if ($this->finalStats->getMemoryUsageMb() !== null) { $memoryGrowth = $this->finalStats->getMemoryUsageMb() - ($this->initialStats->getMemoryUsageMb() ?? 0); $output .= sprintf("Reported Memory Growth: %.2f MB\n", $memoryGrowth); } } $output .= "===================================\n"; return $output; } }