Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
271
tests/Framework/Discovery/SimpleMemoryTest.php
Normal file
271
tests/Framework/Discovery/SimpleMemoryTest.php
Normal file
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Framework\Discovery;
|
||||
|
||||
use App\Framework\Cache\Cache;
|
||||
use App\Framework\Cache\CacheIdentifier;
|
||||
use App\Framework\Cache\CacheItem;
|
||||
use App\Framework\Cache\CacheResult;
|
||||
use App\Framework\Cache\Driver\InMemoryCache;
|
||||
use App\Framework\Core\PathProvider;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\DateTime\SystemClock;
|
||||
use App\Framework\Discovery\UnifiedDiscoveryService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Simple Cache implementation for testing
|
||||
*/
|
||||
final class SimpleCacheWrapper implements Cache
|
||||
{
|
||||
public function __construct(private InMemoryCache $driver)
|
||||
{
|
||||
}
|
||||
|
||||
public function get(CacheIdentifier ...$identifiers): CacheResult
|
||||
{
|
||||
$keys = array_filter($identifiers, fn ($id) => $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\Reflection\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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user