database = $this->createTestDatabase(); $this->workerRegistry = new WorkerRegistry($this->database); $this->distributionService = new JobDistributionService( $this->database, $this->workerRegistry ); $this->cleanupTestData(); PerformanceTestHelper::warmupDatabase($this->database->getConnection()); } protected function tearDown(): void { $this->cleanupTestData(); } public function testMemoryUsageUnderLoad(): void { $workers = $this->createWorkers(10, 25); $this->registerWorkers($workers); $initialMemory = PerformanceTestHelper::getMemoryUsage(); $memorySnapshots = [$initialMemory]; echo "\nMemory Usage Under Load Test:\n"; echo "Initial memory: {$initialMemory['current_mb']}MB (Peak: {$initialMemory['peak_mb']}MB)\n"; // Phase 1: Moderate load $this->simulateJobLoad(500, 'moderate', $memorySnapshots); // Phase 2: High load $this->simulateJobLoad(1000, 'high', $memorySnapshots); // Phase 3: Sustained load $this->simulateSustainedLoad(30, 50, $memorySnapshots); // 30 seconds, 50 jobs/sec $finalMemory = PerformanceTestHelper::getMemoryUsage(); $memorySnapshots[] = $finalMemory; echo "Final memory: {$finalMemory['current_mb']}MB (Peak: {$finalMemory['peak_mb']}MB)\n"; // Analyze memory usage patterns $this->analyzeMemoryUsage($memorySnapshots); // Memory usage should stay within reasonable bounds $this->assertLessThan( 100.0, $finalMemory['current_mb'], 'Current memory usage exceeds 100MB' ); $this->assertLessThan( 200.0, $finalMemory['peak_mb'], 'Peak memory usage exceeds 200MB' ); // Check for potential memory leaks $memoryIncrease = $finalMemory['current_mb'] - $initialMemory['current_mb']; $this->assertLessThan( 50.0, $memoryIncrease, 'Memory increase suggests potential memory leak' ); } public function testMemoryEfficiencyWithBulkOperations(): void { $workers = $this->createWorkers(5, 30); $this->registerWorkers($workers); echo "\nMemory Efficiency with Bulk Operations:\n"; $testCases = [ ['batch_size' => 10, 'batches' => 10], ['batch_size' => 50, 'batches' => 10], ['batch_size' => 100, 'batches' => 10], ['batch_size' => 500, 'batches' => 5], ['batch_size' => 1000, 'batches' => 3], ]; foreach ($testCases as $case) { $batchSize = $case['batch_size']; $batchCount = $case['batches']; $beforeMemory = PerformanceTestHelper::getMemoryUsage(); // Process batches $totalProcessingTime = 0; for ($batch = 0; $batch < $batchCount; $batch++) { $jobs = PerformanceTestHelper::createBulkJobs($batchSize); $batchTime = PerformanceTestHelper::measureTime(function () use ($jobs) { foreach ($jobs as $job) { $this->distributionService->distributeJob($job); } }); $totalProcessingTime += $batchTime; // Clean up completed jobs to simulate real processing if ($batch % 2 === 0) { $this->cleanupCompletedJobs(); } } $afterMemory = PerformanceTestHelper::getMemoryUsage(); $memoryIncrease = $afterMemory['current_mb'] - $beforeMemory['current_mb']; $totalJobs = $batchSize * $batchCount; $avgTimePerJob = $totalProcessingTime / $totalJobs; echo sprintf( "Batch size: %4d, Total jobs: %4d, Memory increase: %6.2fMB, Avg time: %6.3fms/job\n", $batchSize, $totalJobs, $memoryIncrease, $avgTimePerJob ); // Memory increase should not grow linearly with batch size $memoryPerJob = $memoryIncrease / $totalJobs; $this->assertLessThan( 0.1, $memoryPerJob, "Memory usage per job too high for batch size {$batchSize}" ); $this->cleanupTestData(); } } public function testGarbageCollectionImpact(): void { $workers = $this->createWorkers(8, 20); $this->registerWorkers($workers); echo "\nGarbage Collection Impact Test:\n"; $gcStats = []; $operationTimes = []; // Force garbage collection and measure baseline gc_collect_cycles(); $initialGcStats = gc_status(); // Perform operations that generate objects $iterations = 1000; for ($i = 0; $i < $iterations; $i++) { $job = PerformanceTestHelper::createTestJob("gc_test_job_{$i}"); $operationTime = PerformanceTestHelper::measureTime(function () use ($job) { return $this->distributionService->distributeJob($job); }); $operationTimes[] = $operationTime; // Collect GC stats every 100 operations if ($i % 100 === 0) { $gcStats[] = [ 'operation' => $i, 'memory' => PerformanceTestHelper::getMemoryUsage(), 'gc_stats' => gc_status(), ]; } } // Force final garbage collection $gcCycles = gc_collect_cycles(); $finalGcStats = gc_status(); echo "GC cycles collected: {$gcCycles}\n"; echo "Initial GC runs: {$initialGcStats['runs']}\n"; echo "Final GC runs: {$finalGcStats['runs']}\n"; echo "GC runs during test: " . ($finalGcStats['runs'] - $initialGcStats['runs']) . "\n"; // Analyze operation times for GC impact $stats = PerformanceTestHelper::calculateStatistics($operationTimes); echo "Operation performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n"; // GC should not cause significant performance degradation $this->assertLessThan( 20.0, $stats['avg'], 'Average operation time too high - possible GC impact' ); // P99 should not be extremely high (indicating GC pauses) $this->assertLessThan( 100.0, $stats['p99'], 'P99 operation time too high - possible GC pause impact' ); // Analyze memory usage patterns during GC foreach ($gcStats as $snapshot) { $memory = $snapshot['memory']; echo sprintf( "Operation %4d: Memory %6.2fMB, GC runs: %d\n", $snapshot['operation'], $memory['current_mb'], $snapshot['gc_stats']['runs'] ); } } public function testConcurrentOperationResourceUsage(): void { $workers = $this->createWorkers(15, 20); $this->registerWorkers($workers); echo "\nConcurrent Operation Resource Usage Test:\n"; $concurrencyLevels = [1, 5, 10, 20, 50]; $operationsPerLevel = 200; foreach ($concurrencyLevels as $concurrency) { $beforeMemory = PerformanceTestHelper::getMemoryUsage(); // Simulate concurrent operations $results = $this->simulateConcurrentOperations($concurrency, $operationsPerLevel); $afterMemory = PerformanceTestHelper::getMemoryUsage(); $memoryIncrease = $afterMemory['current_mb'] - $beforeMemory['current_mb']; $avgTime = array_sum($results['times']) / count($results['times']); $throughput = $results['total_operations'] / $results['duration']; echo sprintf( "Concurrency: %2d, Throughput: %6.1f ops/sec, Avg time: %6.3fms, Memory: +%6.2fMB\n", $concurrency, $throughput, $avgTime, $memoryIncrease ); // Memory usage should not grow excessively with concurrency $this->assertLessThan( 10.0, $memoryIncrease, "Memory increase too high for concurrency level {$concurrency}" ); // Throughput should generally increase with concurrency (up to a point) if ($concurrency <= 10) { $this->assertGreaterThan( $concurrency * 5, // At least 5 ops/sec per concurrent operation $throughput, "Throughput too low for concurrency level {$concurrency}" ); } $this->cleanupTestData(); } } public function testLongRunningProcessMemoryStability(): void { $workers = $this->createWorkers(6, 25); $this->registerWorkers($workers); echo "\nLong Running Process Memory Stability Test:\n"; $duration = 120; // 2 minutes $operationsPerSecond = 20; $memorySnapshots = []; $startTime = microtime(true); $endTime = $startTime + $duration; $operationCount = 0; while (microtime(true) < $endTime) { $cycleStart = microtime(true); // Perform operations for one second for ($i = 0; $i < $operationsPerSecond; $i++) { $job = PerformanceTestHelper::createTestJob("stability_job_{$operationCount}"); $this->distributionService->distributeJob($job); $operationCount++; } // Take memory snapshot every 10 seconds if ($operationCount % ($operationsPerSecond * 10) === 0) { $memory = PerformanceTestHelper::getMemoryUsage(); $elapsed = microtime(true) - $startTime; $memorySnapshots[] = [ 'time' => $elapsed, 'operations' => $operationCount, 'memory' => $memory, ]; echo sprintf( "Time: %3ds, Operations: %5d, Memory: %6.2fMB (Peak: %6.2fMB)\n", $elapsed, $operationCount, $memory['current_mb'], $memory['peak_mb'] ); } // Clean up periodically to simulate real-world processing if ($operationCount % ($operationsPerSecond * 5) === 0) { $this->cleanupCompletedJobs(); } // Maintain target operations per second $cycleTime = microtime(true) - $cycleStart; $sleepTime = 1.0 - $cycleTime; if ($sleepTime > 0) { usleep($sleepTime * 1000000); } } $actualDuration = microtime(true) - $startTime; $actualThroughput = $operationCount / $actualDuration; echo "Total operations: {$operationCount}\n"; echo "Actual duration: {$actualDuration} seconds\n"; echo "Actual throughput: {$actualThroughput} ops/sec\n"; // Analyze memory stability $this->analyzeMemoryStability($memorySnapshots); // Memory should remain stable over time $firstSnapshot = $memorySnapshots[0]; $lastSnapshot = end($memorySnapshots); $memoryDrift = $lastSnapshot['memory']['current_mb'] - $firstSnapshot['memory']['current_mb']; echo "Memory drift: {$memoryDrift}MB\n"; $this->assertLessThan( 20.0, abs($memoryDrift), 'Memory drift too high - indicates memory leak or accumulation' ); // Throughput should remain stable $this->assertGreaterThan( $operationsPerSecond * 0.8, $actualThroughput, 'Throughput degraded too much during long run' ); } public function testResourceCleanupEfficiency(): void { $workers = $this->createWorkers(5, 20); $this->registerWorkers($workers); echo "\nResource Cleanup Efficiency Test:\n"; // Create many jobs $jobCount = 2000; $jobs = PerformanceTestHelper::createBulkJobs($jobCount); $beforeMemory = PerformanceTestHelper::getMemoryUsage(); echo "Memory before job creation: {$beforeMemory['current_mb']}MB\n"; // Distribute all jobs foreach ($jobs as $job) { $this->distributionService->distributeJob($job); } $afterDistribution = PerformanceTestHelper::getMemoryUsage(); echo "Memory after distribution: {$afterDistribution['current_mb']}MB\n"; // Measure cleanup time $cleanupTime = PerformanceTestHelper::measureTime(function () { $this->cleanupCompletedJobs(); }); $afterCleanup = PerformanceTestHelper::getMemoryUsage(); echo "Memory after cleanup: {$afterCleanup['current_mb']}MB\n"; echo "Cleanup time: {$cleanupTime}ms\n"; $memoryRecovered = $afterDistribution['current_mb'] - $afterCleanup['current_mb']; echo "Memory recovered: {$memoryRecovered}MB\n"; // Cleanup should be efficient $this->assertLessThan( 200.0, $cleanupTime, 'Cleanup time too slow for 2000 jobs' ); // Should recover most of the memory $distributionMemoryUsage = $afterDistribution['current_mb'] - $beforeMemory['current_mb']; $recoveryRatio = $memoryRecovered / max(1, $distributionMemoryUsage); echo "Memory recovery ratio: " . round($recoveryRatio * 100, 1) . "%\n"; $this->assertGreaterThan( 0.5, $recoveryRatio, 'Should recover at least 50% of memory used during distribution' ); } private function simulateJobLoad(int $jobCount, string $phase, array &$memorySnapshots): void { echo "Phase: {$phase} ({$jobCount} jobs)\n"; $beforeMemory = PerformanceTestHelper::getMemoryUsage(); $memorySnapshots[] = $beforeMemory; $jobs = PerformanceTestHelper::createBulkJobs($jobCount); $distributionTimes = []; foreach ($jobs as $job) { $time = PerformanceTestHelper::measureTime(function () use ($job) { return $this->distributionService->distributeJob($job); }); $distributionTimes[] = $time; } $afterMemory = PerformanceTestHelper::getMemoryUsage(); $memorySnapshots[] = $afterMemory; $stats = PerformanceTestHelper::calculateStatistics($distributionTimes); $memoryIncrease = $afterMemory['current_mb'] - $beforeMemory['current_mb']; echo " Memory increase: {$memoryIncrease}MB\n"; echo " Distribution performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n"; } private function simulateSustainedLoad(int $duration, int $jobsPerSecond, array &$memorySnapshots): void { echo "Sustained load: {$jobsPerSecond} jobs/sec for {$duration} seconds\n"; $startTime = microtime(true); $endTime = $startTime + $duration; $jobCount = 0; $snapshotInterval = 5; // Take snapshot every 5 seconds $nextSnapshotTime = $startTime + $snapshotInterval; while (microtime(true) < $endTime) { $job = PerformanceTestHelper::createTestJob("sustained_job_{$jobCount}"); $this->distributionService->distributeJob($job); $jobCount++; // Take memory snapshot if (microtime(true) >= $nextSnapshotTime) { $memory = PerformanceTestHelper::getMemoryUsage(); $memorySnapshots[] = $memory; $elapsed = microtime(true) - $startTime; echo " {$elapsed}s: {$memory['current_mb']}MB\n"; $nextSnapshotTime += $snapshotInterval; } // Maintain target rate usleep(1000000 / $jobsPerSecond); // Convert to microseconds } echo " Total jobs: {$jobCount}\n"; } private function simulateConcurrentOperations(int $concurrency, int $totalOperations): array { $times = []; $startTime = microtime(true); $operationsPerWorker = intval($totalOperations / $concurrency); $actualOperations = 0; // Simulate concurrent operations (simplified for single-threaded PHP) for ($worker = 0; $worker < $concurrency; $worker++) { for ($op = 0; $op < $operationsPerWorker; $op++) { $job = PerformanceTestHelper::createTestJob("concurrent_job_{$worker}_{$op}"); $time = PerformanceTestHelper::measureTime(function () use ($job) { return $this->distributionService->distributeJob($job); }); $times[] = $time; $actualOperations++; } } $endTime = microtime(true); return [ 'times' => $times, 'total_operations' => $actualOperations, 'duration' => $endTime - $startTime, ]; } private function analyzeMemoryUsage(array $memorySnapshots): void { echo "\nMemory Usage Analysis:\n"; $memoryValues = array_column($memorySnapshots, 'current_mb'); $peakValues = array_column($memorySnapshots, 'peak_mb'); $memoryStats = PerformanceTestHelper::calculateStatistics($memoryValues); $peakStats = PerformanceTestHelper::calculateStatistics($peakValues); echo "Current Memory: " . PerformanceTestHelper::formatStatistics($memoryStats, 'MB') . "\n"; echo "Peak Memory: " . PerformanceTestHelper::formatStatistics($peakStats, 'MB') . "\n"; // Check for memory growth pattern $memoryTrend = end($memoryValues) - $memoryValues[0]; echo "Memory trend: " . ($memoryTrend >= 0 ? '+' : '') . "{$memoryTrend}MB\n"; } private function analyzeMemoryStability(array $memorySnapshots): void { echo "\nMemory Stability Analysis:\n"; $memoryValues = array_column(array_column($memorySnapshots, 'memory'), 'current_mb'); $timeValues = array_column($memorySnapshots, 'time'); // Calculate memory growth rate if (count($memoryValues) >= 2) { $firstMemory = $memoryValues[0]; $lastMemory = end($memoryValues); $timeSpan = end($timeValues) - $timeValues[0]; $growthRate = ($lastMemory - $firstMemory) / $timeSpan; // MB per second echo "Memory growth rate: " . round($growthRate * 60, 3) . " MB/minute\n"; $this->assertLessThan( 0.1, // Less than 0.1 MB/sec = 6 MB/minute abs($growthRate), 'Memory growth rate too high - indicates potential leak' ); } } private function createWorkers(int $count, int $capacity): array { $workers = []; for ($i = 1; $i <= $count; $i++) { $workers[] = PerformanceTestHelper::createTestWorker( "resource_worker_{$i}", $capacity ); } return $workers; } private function registerWorkers(array $workers): void { foreach ($workers as $worker) { $this->workerRegistry->registerWorker($worker); } } private function cleanupCompletedJobs(): void { $pdo = $this->database->getConnection(); $pdo->exec('DELETE FROM jobs WHERE status IN ("COMPLETED", "FAILED")'); } private function createTestDatabase(): DatabaseManager { $pdo = new \PDO('sqlite::memory:'); $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $pdo->exec(' CREATE TABLE workers ( id TEXT PRIMARY KEY, queue_names TEXT NOT NULL, capacity INTEGER NOT NULL, status TEXT NOT NULL, last_heartbeat TEXT NOT NULL, metadata TEXT ) '); $pdo->exec(' CREATE TABLE jobs ( id TEXT PRIMARY KEY, type TEXT NOT NULL, payload TEXT NOT NULL, queue_name TEXT NOT NULL, priority INTEGER NOT NULL, status TEXT NOT NULL, worker_id TEXT, created_at TEXT NOT NULL, started_at TEXT, completed_at TEXT, attempts INTEGER DEFAULT 0, error_message TEXT ) '); return new DatabaseManager($pdo); } private function cleanupTestData(): void { $pdo = $this->database->getConnection(); $pdo->exec('DELETE FROM workers'); $pdo->exec('DELETE FROM jobs'); } }