config = array_merge($this->getDefaultConfig(), $config); $this->tracker = new MemoryUsageTracker(); $this->leakDetector = new MemoryLeakDetector(); $this->baselineMemory = memory_get_usage(true); } /** * Get memory analyzer instance. */ public static function getInstance(array $config = []): self { if (self::$instance === null) { self::$instance = new self($config); } return self::$instance; } /** * Enable memory analyzer. */ public function enable(): void { $this->enabled = true; $this->tracker->enable(); $this->leakDetector->enable(); } /** * Disable memory analyzer. */ public function disable(): void { $this->enabled = false; $this->tracker->disable(); $this->leakDetector->disable(); } /** * Check if analyzer is enabled. */ public function isEnabled(): bool { return $this->enabled; } /** * Take memory snapshot. */ public function snapshot(string $name, array $context = []): MemorySnapshot { if (!$this->enabled) { throw new \RuntimeException('Memory analyzer is not enabled'); } $snapshot = new MemorySnapshot($name, $context); $snapshot->capture(); $this->snapshots[$name] = $snapshot; return $snapshot; } /** * Get memory snapshot by name. */ public function getSnapshot(string $name): ?MemorySnapshot { return $this->snapshots[$name] ?? null; } /** * Get all snapshots. */ public function getSnapshots(): array { return $this->snapshots; } /** * Compare two snapshots. */ public function compare(string $from, string $to): array { $fromSnapshot = $this->getSnapshot($from); $toSnapshot = $this->getSnapshot($to); if (!$fromSnapshot || !$toSnapshot) { throw new \InvalidArgumentException('Both snapshots must exist'); } return [ 'from' => $fromSnapshot->getName(), 'to' => $toSnapshot->getName(), 'memory_diff' => $toSnapshot->getMemoryUsage() - $fromSnapshot->getMemoryUsage(), 'memory_diff_percent' => $this->calculatePercentageDiff( $fromSnapshot->getMemoryUsage(), $toSnapshot->getMemoryUsage() ), 'peak_memory_diff' => $toSnapshot->getPeakMemory() - $fromSnapshot->getPeakMemory(), 'objects_diff' => $toSnapshot->getObjectCount() - $fromSnapshot->getObjectCount(), 'time_diff' => $toSnapshot->getTimestamp() - $fromSnapshot->getTimestamp() ]; } /** * Get memory usage trend. */ public function getTrend(): array { if (empty($this->snapshots)) { return []; } $trend = []; $previousMemory = $this->baselineMemory; foreach ($this->snapshots as $name => $snapshot) { $currentMemory = $snapshot->getMemoryUsage(); $diff = $currentMemory - $previousMemory; $trend[] = [ 'name' => $name, 'timestamp' => $snapshot->getTimestamp(), 'memory_usage' => $currentMemory, 'memory_diff' => $diff, 'memory_diff_percent' => $this->calculatePercentageDiff($previousMemory, $currentMemory), 'peak_memory' => $snapshot->getPeakMemory(), 'object_count' => $snapshot->getObjectCount() ]; $previousMemory = $currentMemory; } return $trend; } /** * Detect memory leaks. */ public function detectLeaks(): array { if (!$this->enabled) { return []; } return $this->leakDetector->detect($this->snapshots); } /** * Get memory usage statistics. */ public function getStatistics(): array { if (empty($this->snapshots)) { return [ 'current' => memory_get_usage(true), 'peak' => memory_get_peak_usage(true), 'baseline' => $this->baselineMemory, 'growth' => memory_get_usage(true) - $this->baselineMemory, 'snapshots_count' => 0 ]; } $current = memory_get_usage(true); $peak = memory_get_peak_usage(true); $growth = $current - $this->baselineMemory; $memoryUsages = array_map(fn($s) => $s->getMemoryUsage(), $this->snapshots); $objectCounts = array_map(fn($s) => $s->getObjectCount(), $this->snapshots); return [ 'current' => $current, 'peak' => $peak, 'baseline' => $this->baselineMemory, 'growth' => $growth, 'growth_percent' => $this->calculatePercentageDiff($this->baselineMemory, $current), 'snapshots_count' => count($this->snapshots), 'min_memory' => min($memoryUsages), 'max_memory' => max($memoryUsages), 'avg_memory' => array_sum($memoryUsages) / count($memoryUsages), 'min_objects' => min($objectCounts), 'max_objects' => max($objectCounts), 'avg_objects' => array_sum($objectCounts) / count($objectCounts), 'memory_efficiency' => $this->calculateMemoryEfficiency(), 'leak_detected' => !empty($this->detectLeaks()) ]; } /** * Get memory usage by type. */ public function getUsageByType(): array { if (empty($this->snapshots)) { return []; } $latest = end($this->snapshots); return $latest->getUsageByType(); } /** * Get largest memory consumers. */ public function getLargestConsumers(int $limit = 10): array { if (empty($this->snapshots)) { return []; } $latest = end($this->snapshots); return $latest->getLargestConsumers($limit); } /** * Generate memory analysis report. */ public function generateReport(): array { $report = [ 'summary' => $this->getStatistics(), 'trend' => $this->getTrend(), 'snapshots' => [], 'leaks' => $this->detectLeaks(), 'usage_by_type' => $this->getUsageByType(), 'largest_consumers' => $this->getLargestConsumers(), 'recommendations' => $this->generateRecommendations(), 'generated_at' => time() ]; foreach ($this->snapshots as $name => $snapshot) { $report['snapshots'][$name] = $snapshot->toArray(); } return $report; } /** * Save memory analysis report. */ public function saveReport(string $filename): bool { $report = $this->generateReport(); $json = json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); return file_put_contents($filename, $json) !== false; } /** * Export memory data. */ public function export(string $format = 'json'): string { $report = $this->generateReport(); switch (strtolower($format)) { case 'json': return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); case 'csv': return $this->exportToCsv($report); case 'html': return $this->exportToHtml($report); default: throw new \InvalidArgumentException("Unsupported export format: {$format}"); } } /** * Export to CSV format. */ protected function exportToCsv(array $report): string { $csv = "Snapshot,Timestamp,Memory Usage (bytes),Peak Memory (bytes),Object Count\n"; foreach ($report['snapshots'] as $name => $snapshot) { $csv .= sprintf( "%s,%d,%d,%d,%d\n", $name, $snapshot['timestamp'], $snapshot['memory_usage'], $snapshot['peak_memory'], $snapshot['object_count'] ); } return $csv; } /** * Export to HTML format. */ protected function exportToHtml(array $report): string { $html = 'Memory Analysis Report'; $html .= ''; $html .= ''; $html .= '

Memory Analysis Report

'; // Summary section $html .= '

Summary

'; $html .= ''; foreach ($report['summary'] as $key => $value) { if (is_array($value)) { $value = json_encode($value); } elseif (is_bool($value)) { $value = $value ? 'Yes' : 'No'; } $class = ''; if (str_contains($key, 'leak') && $value) { $class = 'error'; } elseif (str_contains($key, 'growth') && $value > 0) { $class = 'warning'; } $html .= ""; } $html .= '
MetricValue
{$key}{$value}
'; // Snapshots section $html .= '

Memory Snapshots

'; $html .= ''; foreach ($report['snapshots'] as $name => $snapshot) { $html .= sprintf( "", $name, $snapshot['memory_usage'] / 1024 / 1024, $snapshot['peak_memory'] / 1024 / 1024, $snapshot['object_count'] ); } $html .= '
NameMemory (MB)Peak (MB)Objects
%s%.2f%.2f%d
'; // Recommendations section if (!empty($report['recommendations'])) { $html .= '

Recommendations

'; $html .= ''; } $html .= ''; return $html; } /** * Clear all snapshots. */ public function clear(): void { $this->snapshots = []; $this->tracker->clear(); $this->leakDetector->clear(); } /** * Reset analyzer. */ public function reset(): void { $this->clear(); $this->baselineMemory = memory_get_usage(true); } /** * Get configuration. */ public function getConfig(): array { return $this->config; } /** * Set configuration. */ public function setConfig(array $config): void { $this->config = array_merge($this->config, $config); } /** * Get memory usage tracker. */ public function getTracker(): MemoryUsageTracker { return $this->tracker; } /** * Get memory leak detector. */ public function getLeakDetector(): MemoryLeakDetector { return $this->leakDetector; } /** * Calculate percentage difference. */ protected function calculatePercentageDiff(int $from, int $to): float { if ($from === 0) { return $to > 0 ? 100 : 0; } return (($to - $from) / $from) * 100; } /** * Calculate memory efficiency. */ protected function calculateMemoryEfficiency(): float { if (empty($this->snapshots)) { return 100; } $totalMemory = array_sum(array_map(fn($s) => $s->getMemoryUsage(), $this->snapshots)); $avgMemory = $totalMemory / count($this->snapshots); $peakMemory = memory_get_peak_usage(true); if ($peakMemory === 0) { return 100; } return ($avgMemory / $peakMemory) * 100; } /** * Generate memory recommendations. */ protected function generateRecommendations(): array { $recommendations = []; $stats = $this->getStatistics(); // Check for memory growth if ($stats['growth'] > $this->config['growth_threshold']) { $recommendations[] = [ 'type' => 'memory', 'severity' => 'warning', 'message' => sprintf( 'High memory growth detected: %s (%.1f%%)', $this->formatBytes($stats['growth']), $stats['growth_percent'] ), 'suggestion' => 'Review memory allocation patterns and consider optimization' ]; } // Check for memory leaks if ($stats['leak_detected']) { $recommendations[] = [ 'type' => 'leak', 'severity' => 'error', 'message' => 'Memory leaks detected', 'suggestion' => 'Investigate object references and ensure proper cleanup' ]; } // Check memory efficiency if ($stats['memory_efficiency'] < $this->config['efficiency_threshold']) { $recommendations[] = [ 'type' => 'efficiency', 'severity' => 'info', 'message' => sprintf( 'Low memory efficiency: %.1f%%', $stats['memory_efficiency'] ), 'suggestion' => 'Consider memory optimization techniques' ]; } // Check peak memory usage if ($stats['peak'] > $this->config['peak_threshold']) { $recommendations[] = [ 'type' => 'peak', 'severity' => 'warning', 'message' => sprintf( 'High peak memory usage: %s', $this->formatBytes($stats['peak']) ), 'suggestion' => 'Monitor memory usage patterns and optimize peak consumption' ]; } return $recommendations; } /** * Get default configuration. */ protected function getDefaultConfig(): array { return [ 'growth_threshold' => 50 * 1024 * 1024, // 50MB 'peak_threshold' => 100 * 1024 * 1024, // 100MB 'efficiency_threshold' => 70, // 70% 'auto_snapshot' => true, 'snapshot_interval' => 1000, // ms 'max_snapshots' => 100, 'track_objects' => true, 'track_types' => true, 'detect_leaks' => true ]; } /** * Format bytes to human readable format. */ protected function formatBytes(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= (1 << (10 * $pow)); return round($bytes, 2) . ' ' . $units[$pow]; } /** * Monitor memory usage continuously. */ public function startMonitoring(): void { if (!$this->enabled || !$this->config['auto_snapshot']) { return; } $interval = $this->config['snapshot_interval'] * 1000; // Convert to microseconds while ($this->enabled) { $this->snapshot('auto_' . time()); usleep($interval); // Limit number of snapshots if (count($this->snapshots) > $this->config['max_snapshots']) { array_shift($this->snapshots); } } } /** * Get memory heatmap data. */ public function getHeatmap(): array { if (empty($this->snapshots)) { return []; } $heatmap = []; $maxMemory = max(array_map(fn($s) => $s->getMemoryUsage(), $this->snapshots)); foreach ($this->snapshots as $name => $snapshot) { $memory = $snapshot->getMemoryUsage(); $intensity = $maxMemory > 0 ? ($memory / $maxMemory) : 0; $heatmap[] = [ 'name' => $name, 'timestamp' => $snapshot->getTimestamp(), 'memory' => $memory, 'intensity' => $intensity, 'color' => $this->getHeatmapColor($intensity) ]; } return $heatmap; } /** * Get heatmap color based on intensity. */ protected function getHeatmapColor(float $intensity): string { if ($intensity < 0.3) { return '#27ae60'; // Green } elseif ($intensity < 0.7) { return '#f39c12'; // Orange } else { return '#e74c3c'; // Red } } /** * Analyze memory patterns. */ public function analyzePatterns(): array { if (empty($this->snapshots)) { return []; } $patterns = []; $memoryUsages = array_map(fn($s) => $s->getMemoryUsage(), $this->snapshots); // Detect growth pattern $isGrowing = $memoryUsages[count($memoryUsages) - 1] > $memoryUsages[0]; $patterns['growth_trend'] = $isGrowing ? 'increasing' : 'stable'; // Detect volatility $avg = array_sum($memoryUsages) / count($memoryUsages); $variance = array_sum(array_map(fn($m) => pow($m - $avg, 2), $memoryUsages)) / count($memoryUsages); $stdDev = sqrt($variance); $patterns['volatility'] = $stdDev / $avg; // Coefficient of variation // Detect spikes $spikes = []; for ($i = 1; $i < count($memoryUsages) - 1; $i++) { $prev = $memoryUsages[$i - 1]; $current = $memoryUsages[$i]; $next = $memoryUsages[$i + 1]; if ($current > $prev && $current > $next && $current > $avg + $stdDev) { $spikes[] = [ 'index' => $i, 'memory' => $current, 'timestamp' => $this->snapshots[$i]->getTimestamp() ]; } } $patterns['spikes'] = $spikes; return $patterns; } }