$id instanceof \App\Framework\Cache\CacheKey); return $this->driver->get(...$keys); } public function set(CacheItem ...$items): bool { return $this->driver->set(...$items); } public function has(CacheIdentifier ...$identifiers): array { $keys = array_filter($identifiers, fn ($id) => $id instanceof \App\Framework\Cache\CacheKey); return $this->driver->has(...$keys); } public function forget(CacheIdentifier ...$identifiers): bool { $keys = array_filter($identifiers, fn ($id) => $id instanceof \App\Framework\Cache\CacheKey); return $this->driver->forget(...$keys); } public function clear(): bool { return $this->driver->clear(); } public function remember(\App\Framework\Cache\CacheKey $key, callable $callback, ?Duration $ttl = null): CacheItem { $result = $this->driver->get($key); $item = $result->getItem($key); if ($item->isHit) { return $item; } $value = $callback(); $newItem = CacheItem::forSet($key, $value, $ttl); $this->driver->set($newItem); return CacheItem::hit($key, $value); } } /** * Simplified memory test focusing only on Discovery core without container dependencies */ final class SimpleMemoryTest extends TestCase { public function test_discovery_memory_usage_15_runs(): void { $iterations = 15; $memoryData = []; echo "\n=== Testing Discovery Memory Usage - $iterations Runs ===\n"; // Create discovery service directly with mock dependencies $pathProvider = new PathProvider('/var/www/html'); // Use InMemoryCache with wrapper to implement Cache interface $cacheDriver = new InMemoryCache(); $cache = new SimpleCacheWrapper($cacheDriver); $clock = new SystemClock(); // Create reflection provider without dependencies $reflectionProvider = new \App\Framework\ReflectionLegacy\CachedReflectionProvider(); $config = new \App\Framework\Discovery\ValueObjects\DiscoveryConfiguration( paths: ['/var/www/html/src'], attributeMappers: [ new \App\Framework\Core\RouteMapper(), new \App\Framework\DI\InitializerMapper(), ], targetInterfaces: [], useCache: false ); $discoveryService = new UnifiedDiscoveryService( pathProvider: $pathProvider, cache: $cache, clock: $clock, reflectionProvider: $reflectionProvider, configuration: $config, attributeMappers: [ new \App\Framework\Core\RouteMapper(), new \App\Framework\DI\InitializerMapper(), ], targetInterfaces: [] ); for ($i = 1; $i <= $iterations; $i++) { echo "\n--- Discovery Run $i/$iterations ---\n"; $memoryBefore = memory_get_usage(true); $peakBefore = memory_get_peak_usage(true); // Run discovery $registry = $discoveryService->discover(); $memoryAfter = memory_get_usage(true); $peakAfter = memory_get_peak_usage(true); $memoryDiff = $memoryAfter - $memoryBefore; $peakDiff = $peakAfter - $peakBefore; $memoryData[$i] = [ 'memory_before' => $memoryBefore, 'memory_after' => $memoryAfter, 'memory_diff' => $memoryDiff, 'peak_before' => $peakBefore, 'peak_after' => $peakAfter, 'peak_diff' => $peakDiff, ]; echo sprintf( "Run %2d: Memory %s -> %s (diff: %s), Peak %s -> %s (diff: %s)\n", $i, $this->formatBytes($memoryBefore), $this->formatBytes($memoryAfter), $this->formatBytes($memoryDiff), $this->formatBytes($peakBefore), $this->formatBytes($peakAfter), $this->formatBytes($peakDiff) ); // Log reflection stats every 5 runs if ($i % 5 === 0) { try { $health = $discoveryService->getHealthStatus(); echo " Health Status: " . json_encode($health) . "\n"; } catch (\Throwable $e) { echo " Health Status: Error - " . $e->getMessage() . "\n"; } } // Cleanup unset($registry); gc_collect_cycles(); // Check for excessive memory usage if ($memoryDiff > 100 * 1024 * 1024) { // 100MB limit $this->fail("Run $i: Excessive memory usage: " . $this->formatBytes($memoryDiff)); } } $this->analyzeResults($memoryData); } private function analyzeResults(array $memoryData): void { echo "\n=== Memory Analysis Results ===\n"; $memoryDiffs = array_column($memoryData, 'memory_diff'); $peakDiffs = array_column($memoryData, 'peak_diff'); $avgMemory = array_sum($memoryDiffs) / count($memoryDiffs); $avgPeak = array_sum($peakDiffs) / count($peakDiffs); $maxMemory = max($memoryDiffs); $minMemory = min($memoryDiffs); $totalGrowth = array_sum($memoryDiffs); echo "Average memory per run: " . $this->formatBytes($avgMemory) . "\n"; echo "Average peak per run: " . $this->formatBytes($avgPeak) . "\n"; echo "Max memory per run: " . $this->formatBytes($maxMemory) . "\n"; echo "Min memory per run: " . $this->formatBytes($minMemory) . "\n"; echo "Total cumulative growth: " . $this->formatBytes($totalGrowth) . "\n"; // Calculate growth trend $growthTrend = $this->calculateLinearTrend($memoryDiffs); echo "Growth trend (bytes per run): " . $this->formatBytes($growthTrend) . "\n"; // Memory leak detection if ($growthTrend > 2 * 1024 * 1024) { // 2MB growth per run $this->fail("Detected memory leak: " . $this->formatBytes($growthTrend) . " growth per run"); } if ($avgMemory > 50 * 1024 * 1024) { // 50MB average $this->fail("Average memory usage too high: " . $this->formatBytes($avgMemory)); } // Check for stability - memory usage should be consistent $memoryVariance = $this->calculateVariance($memoryDiffs); echo "Memory usage variance: " . $this->formatBytes($memoryVariance) . "\n"; if ($memoryVariance > 10 * 1024 * 1024) { // 10MB variance echo "WARNING: High memory usage variance detected\n"; } echo "\nāœ… Memory test completed successfully!\n"; echo "āœ… No significant memory leaks detected\n"; echo "āœ… Average memory usage within acceptable limits\n"; } private function calculateLinearTrend(array $values): float { $n = count($values); if ($n < 3) { return 0; } $sumX = 0; $sumY = 0; $sumXY = 0; $sumX2 = 0; for ($i = 0; $i < $n; $i++) { $x = $i + 1; $y = $values[$i]; $sumX += $x; $sumY += $y; $sumXY += $x * $y; $sumX2 += $x * $x; } return ($n * $sumXY - $sumX * $sumY) / ($n * $sumX2 - $sumX * $sumX); } private function calculateVariance(array $values): float { $mean = array_sum($values) / count($values); $squaredDiffs = array_map(fn ($value) => pow($value - $mean, 2), $values); return array_sum($squaredDiffs) / count($squaredDiffs); } private function formatBytes(int|float $bytes): string { if ($bytes < 0) { return '-' . $this->formatBytes(abs($bytes)); } $units = ['B', 'KB', 'MB', 'GB']; $unitIndex = 0; while ($bytes >= 1024 && $unitIndex < count($units) - 1) { $bytes /= 1024; $unitIndex++; } return round($bytes, 1) . ' ' . $units[$unitIndex]; } }