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 testWorkerQueryPerformance(): void { // Create workers with different statuses and capacities $workers = array_merge( $this->createWorkers(50, 20, WorkerStatus::AVAILABLE), $this->createWorkers(20, 15, WorkerStatus::BUSY), $this->createWorkers(10, 25, WorkerStatus::FAILED) ); $this->registerWorkers($workers); echo "\nWorker Query Performance Test:\n"; echo "Total workers: " . count($workers) . "\n"; // Test worker lookup by ID $workerLookupTimes = []; for ($i = 0; $i < 100; $i++) { $randomWorker = $workers[array_rand($workers)]; $workerId = $randomWorker->id->toString(); $time = PerformanceTestHelper::measureTime(function () use ($workerId) { return $this->workerRegistry->getWorker($workerId); }); $workerLookupTimes[] = $time; } $lookupStats = PerformanceTestHelper::calculateStatistics($workerLookupTimes); echo "Worker lookup by ID: " . PerformanceTestHelper::formatStatistics($lookupStats) . "\n"; // Test getting available workers $availableWorkerTimes = []; for ($i = 0; $i < 100; $i++) { $time = PerformanceTestHelper::measureTime(function () { return $this->workerRegistry->getAvailableWorkers(); }); $availableWorkerTimes[] = $time; } $availableStats = PerformanceTestHelper::calculateStatistics($availableWorkerTimes); echo "Get available workers: " . PerformanceTestHelper::formatStatistics($availableStats) . "\n"; // Test worker status updates $updateTimes = []; for ($i = 0; $i < 100; $i++) { $randomWorker = $workers[array_rand($workers)]; $workerId = $randomWorker->id->toString(); $time = PerformanceTestHelper::measureTime(function () use ($workerId) { $this->updateWorkerStatus($workerId, WorkerStatus::BUSY); $this->updateWorkerStatus($workerId, WorkerStatus::AVAILABLE); }); $updateTimes[] = $time; } $updateStats = PerformanceTestHelper::calculateStatistics($updateTimes); echo "Worker status updates: " . PerformanceTestHelper::formatStatistics($updateStats) . "\n"; // Validate performance benchmarks $this->assertLessThan(1.0, $lookupStats['avg'], 'Worker lookup average time exceeds 1ms'); $this->assertLessThan(2.0, $availableStats['avg'], 'Available workers query average time exceeds 2ms'); $this->assertLessThan(5.0, $updateStats['avg'], 'Worker update average time exceeds 5ms'); PerformanceTestHelper::assertPerformance($workerLookupTimes, 1.0, 2.0, 'Worker lookup'); PerformanceTestHelper::assertPerformance($availableWorkerTimes, 2.0, 5.0, 'Available workers query'); } public function testJobQueryPerformance(): void { $workers = $this->createWorkers(20, 25); $this->registerWorkers($workers); // Create jobs with different statuses and priorities $jobs = array_merge( PerformanceTestHelper::createBulkJobs(500), PerformanceTestHelper::createBulkJobs(300, \App\Framework\Queue\Jobs\JobPriority::HIGH), PerformanceTestHelper::createBulkJobs(200, \App\Framework\Queue\Jobs\JobPriority::CRITICAL) ); // Distribute and update job statuses foreach ($jobs as $index => $job) { $this->distributionService->distributeJob($job); // Simulate some completed jobs if ($index % 3 === 0) { $this->updateJobStatus($job->id, JobStatus::COMPLETED); } elseif ($index % 5 === 0) { $this->updateJobStatus($job->id, JobStatus::FAILED); } } echo "\nJob Query Performance Test:\n"; echo "Total jobs: " . count($jobs) . "\n"; // Test job lookup by ID $jobLookupTimes = []; for ($i = 0; $i < 100; $i++) { $randomJob = $jobs[array_rand($jobs)]; $jobId = $randomJob->id; $time = PerformanceTestHelper::measureTime(function () use ($jobId) { return $this->getJobById($jobId); }); $jobLookupTimes[] = $time; } $lookupStats = PerformanceTestHelper::calculateStatistics($jobLookupTimes); echo "Job lookup by ID: " . PerformanceTestHelper::formatStatistics($lookupStats) . "\n"; // Test getting jobs by status $statusQueryTimes = []; $statuses = [JobStatus::PENDING, JobStatus::PROCESSING, JobStatus::COMPLETED, JobStatus::FAILED]; foreach ($statuses as $status) { for ($i = 0; $i < 25; $i++) { $time = PerformanceTestHelper::measureTime(function () use ($status) { return $this->getJobsByStatus($status); }); $statusQueryTimes[] = $time; } } $statusStats = PerformanceTestHelper::calculateStatistics($statusQueryTimes); echo "Jobs by status query: " . PerformanceTestHelper::formatStatistics($statusStats) . "\n"; // Test getting jobs by worker $workerJobTimes = []; for ($i = 0; $i < 50; $i++) { $randomWorker = $workers[array_rand($workers)]; $workerId = $randomWorker->id->toString(); $time = PerformanceTestHelper::measureTime(function () use ($workerId) { return $this->getJobsByWorker($workerId); }); $workerJobTimes[] = $time; } $workerJobStats = PerformanceTestHelper::calculateStatistics($workerJobTimes); echo "Jobs by worker query: " . PerformanceTestHelper::formatStatistics($workerJobStats) . "\n"; // Validate performance benchmarks $this->assertLessThan(1.0, $lookupStats['avg'], 'Job lookup average time exceeds 1ms'); $this->assertLessThan(5.0, $statusStats['avg'], 'Job status query average time exceeds 5ms'); $this->assertLessThan(3.0, $workerJobStats['avg'], 'Jobs by worker query average time exceeds 3ms'); PerformanceTestHelper::assertPerformance($jobLookupTimes, 1.0, 2.0, 'Job lookup'); PerformanceTestHelper::assertPerformance($statusQueryTimes, 5.0, 10.0, 'Job status queries'); } public function testBatchOperationPerformance(): void { echo "\nBatch Operation Performance Test:\n"; $batchSizes = [10, 50, 100, 500, 1000]; foreach ($batchSizes as $batchSize) { echo "Testing batch size: {$batchSize}\n"; // Test batch worker registration $workers = $this->createWorkers($batchSize, 20); $batchRegisterTime = PerformanceTestHelper::measureTime(function () use ($workers) { $this->registerWorkers($workers); }); // Test batch job distribution $jobs = PerformanceTestHelper::createBulkJobs($batchSize); $batchDistributeTime = PerformanceTestHelper::measureTime(function () use ($jobs) { foreach ($jobs as $job) { $this->distributionService->distributeJob($job); } }); // Test batch job status updates $batchUpdateTime = PerformanceTestHelper::measureTime(function () use ($jobs) { foreach ($jobs as $job) { $this->updateJobStatus($job->id, JobStatus::COMPLETED); } }); $perItemRegister = $batchRegisterTime / $batchSize; $perItemDistribute = $batchDistributeTime / $batchSize; $perItemUpdate = $batchUpdateTime / $batchSize; echo sprintf( " Register: %6.1fms total (%4.2fms/item), Distribute: %6.1fms (%4.2fms/item), Update: %6.1fms (%4.2fms/item)\n", $batchRegisterTime, $perItemRegister, $batchDistributeTime, $perItemDistribute, $batchUpdateTime, $perItemUpdate ); // Batch operations should be efficient $this->assertLessThan(2.0, $perItemRegister, "Worker registration too slow for batch size {$batchSize}"); $this->assertLessThan(5.0, $perItemDistribute, "Job distribution too slow for batch size {$batchSize}"); $this->assertLessThan(1.0, $perItemUpdate, "Job update too slow for batch size {$batchSize}"); $this->cleanupTestData(); } } public function testIndexEfficiency(): void { echo "\nIndex Efficiency Test:\n"; // Create large dataset to test index effectiveness $workerCount = 1000; $jobCount = 5000; $workers = $this->createWorkers($workerCount, 20); $this->registerWorkers($workers); $jobs = PerformanceTestHelper::createBulkJobs($jobCount); foreach ($jobs as $job) { $this->distributionService->distributeJob($job); } echo "Dataset: {$workerCount} workers, {$jobCount} jobs\n"; // Test indexed queries performance $indexTests = [ 'worker_by_id' => function () use ($workers) { $randomWorker = $workers[array_rand($workers)]; return $this->workerRegistry->getWorker($randomWorker->id->toString()); }, 'workers_by_status' => function () { return $this->workerRegistry->getWorkersByStatus(WorkerStatus::AVAILABLE); }, 'jobs_by_status' => function () { return $this->getJobsByStatus(JobStatus::PENDING); }, 'jobs_by_priority' => function () { return $this->getJobsByPriority(\App\Framework\Queue\Jobs\JobPriority::HIGH); }, 'jobs_by_queue' => function () { return $this->getJobsByQueue('test_queue'); }, ]; foreach ($indexTests as $testName => $testFunction) { $times = []; for ($i = 0; $i < 100; $i++) { $time = PerformanceTestHelper::measureTime($testFunction); $times[] = $time; } $stats = PerformanceTestHelper::calculateStatistics($times); echo "{$testName}: " . PerformanceTestHelper::formatStatistics($stats) . "\n"; // All indexed queries should be fast even with large dataset $this->assertLessThan(10.0, $stats['avg'], "Index query {$testName} too slow with large dataset"); $this->assertLessThan(25.0, $stats['p95'], "Index query {$testName} P95 too slow with large dataset"); } } public function testConnectionPoolingPerformance(): void { echo "\nConnection Pooling Performance Test:\n"; // Simulate multiple concurrent database operations $operationTypes = [ 'worker_lookup' => function () { $workerId = 'worker_' . rand(1, 100); return $this->workerRegistry->getWorker($workerId); }, 'job_insertion' => function () { $job = PerformanceTestHelper::createTestJob('pool_test_' . uniqid()); return $this->distributionService->distributeJob($job); }, 'status_query' => function () { return $this->getJobsByStatus(JobStatus::PENDING); }, ]; $concurrencyLevels = [1, 5, 10, 20]; foreach ($concurrencyLevels as $concurrency) { echo "Testing concurrency level: {$concurrency}\n"; $operationTimes = []; $operationsPerType = 20; foreach ($operationTypes as $typeName => $operation) { $typeTimes = []; // Simulate concurrent operations (simplified for single-threaded PHP) for ($i = 0; $i < $operationsPerType * $concurrency; $i++) { $time = PerformanceTestHelper::measureTime($operation); $typeTimes[] = $time; $operationTimes[] = $time; } $typeStats = PerformanceTestHelper::calculateStatistics($typeTimes); echo " {$typeName}: " . PerformanceTestHelper::formatStatistics($typeStats) . "\n"; } $overallStats = PerformanceTestHelper::calculateStatistics($operationTimes); echo " Overall: " . PerformanceTestHelper::formatStatistics($overallStats) . "\n"; // Performance should not degrade significantly with concurrency $this->assertLessThan(50.0, $overallStats['avg'], "Database operations too slow at concurrency {$concurrency}"); } } public function testQueryOptimizationEffectiveness(): void { echo "\nQuery Optimization Effectiveness Test:\n"; // Create test data with specific patterns to test optimization $workers = $this->createWorkers(500, 20); $this->registerWorkers($workers); $jobs = PerformanceTestHelper::createBulkJobs(2000); foreach ($jobs as $job) { $this->distributionService->distributeJob($job); } // Test complex queries that should benefit from optimization $complexQueries = [ 'workers_with_capacity_filter' => function () { return $this->getWorkersByCapacityRange(15, 25); }, 'jobs_with_multiple_filters' => function () { return $this->getJobsWithFilters(JobStatus::PENDING, \App\Framework\Queue\Jobs\JobPriority::NORMAL); }, 'job_count_aggregation' => function () { return $this->getJobCountsByStatus(); }, 'worker_utilization_stats' => function () { return $this->getWorkerUtilizationStats(); }, ]; foreach ($complexQueries as $queryName => $queryFunction) { $times = []; for ($i = 0; $i < 50; $i++) { $time = PerformanceTestHelper::measureTime($queryFunction); $times[] = $time; } $stats = PerformanceTestHelper::calculateStatistics($times); echo "{$queryName}: " . PerformanceTestHelper::formatStatistics($stats) . "\n"; // Complex queries should still be reasonably fast $this->assertLessThan(20.0, $stats['avg'], "Complex query {$queryName} not optimized enough"); $this->assertLessThan(50.0, $stats['p95'], "Complex query {$queryName} P95 not optimized enough"); } } public function testTransactionPerformance(): void { echo "\nTransaction Performance Test:\n"; $workers = $this->createWorkers(10, 20); $this->registerWorkers($workers); // Test transaction overhead $transactionSizes = [1, 5, 10, 50, 100]; foreach ($transactionSizes as $size) { $transactionTimes = []; for ($iteration = 0; $iteration < 20; $iteration++) { $time = PerformanceTestHelper::measureTime(function () use ($size, $iteration) { $pdo = $this->database->getConnection(); try { $pdo->beginTransaction(); for ($i = 0; $i < $size; $i++) { $job = PerformanceTestHelper::createTestJob("tx_job_{$iteration}_{$i}"); $this->distributionService->distributeJob($job); } $pdo->commit(); } catch (\Exception $e) { $pdo->rollBack(); throw $e; } }); $transactionTimes[] = $time; } $stats = PerformanceTestHelper::calculateStatistics($transactionTimes); $timePerOperation = $stats['avg'] / $size; echo sprintf( "Transaction size %3d: %6.1fms total (%5.2fms/operation)\n", $size, $stats['avg'], $timePerOperation ); // Transaction overhead should be reasonable $this->assertLessThan(200.0, $stats['avg'], "Transaction time too high for size {$size}"); $this->cleanupJobs(); } } private function createWorkers(int $count, int $capacity, WorkerStatus $status = WorkerStatus::AVAILABLE): array { $workers = []; for ($i = 1; $i <= $count; $i++) { $workers[] = PerformanceTestHelper::createTestWorker( "db_perf_worker_{$i}", $capacity, $status ); } return $workers; } private function registerWorkers(array $workers): void { foreach ($workers as $worker) { $this->workerRegistry->registerWorker($worker); } } private function updateWorkerStatus(string $workerId, WorkerStatus $status): void { $pdo = $this->database->getConnection(); $stmt = $pdo->prepare('UPDATE workers SET status = ? WHERE id = ?'); $stmt->execute([$status->value, $workerId]); } private function updateJobStatus(string $jobId, JobStatus $status): void { $pdo = $this->database->getConnection(); $stmt = $pdo->prepare('UPDATE jobs SET status = ? WHERE id = ?'); $stmt->execute([$status->value, $jobId]); } private function getJobById(string $jobId): ?array { $pdo = $this->database->getConnection(); $stmt = $pdo->prepare('SELECT * FROM jobs WHERE id = ?'); $stmt->execute([$jobId]); return $stmt->fetch(\PDO::FETCH_ASSOC) ?: null; } private function getJobsByStatus(JobStatus $status): array { $pdo = $this->database->getConnection(); $stmt = $pdo->prepare('SELECT * FROM jobs WHERE status = ? LIMIT 100'); $stmt->execute([$status->value]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } private function getJobsByWorker(string $workerId): array { $pdo = $this->database->getConnection(); $stmt = $pdo->prepare('SELECT * FROM jobs WHERE worker_id = ? LIMIT 100'); $stmt->execute([$workerId]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } private function getJobsByPriority(\App\Framework\Queue\Jobs\JobPriority $priority): array { $pdo = $this->database->getConnection(); $stmt = $pdo->prepare('SELECT * FROM jobs WHERE priority = ? LIMIT 100'); $stmt->execute([$priority->value]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } private function getJobsByQueue(string $queueName): array { $pdo = $this->database->getConnection(); $stmt = $pdo->prepare('SELECT * FROM jobs WHERE queue_name = ? LIMIT 100'); $stmt->execute([$queueName]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } private function getWorkersByCapacityRange(int $minCapacity, int $maxCapacity): array { $pdo = $this->database->getConnection(); $stmt = $pdo->prepare('SELECT * FROM workers WHERE capacity BETWEEN ? AND ?'); $stmt->execute([$minCapacity, $maxCapacity]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } private function getJobsWithFilters(JobStatus $status, \App\Framework\Queue\Jobs\JobPriority $priority): array { $pdo = $this->database->getConnection(); $stmt = $pdo->prepare('SELECT * FROM jobs WHERE status = ? AND priority = ? LIMIT 100'); $stmt->execute([$status->value, $priority->value]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } private function getJobCountsByStatus(): array { $pdo = $this->database->getConnection(); $stmt = $pdo->query('SELECT status, COUNT(*) as count FROM jobs GROUP BY status'); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } private function getWorkerUtilizationStats(): array { $pdo = $this->database->getConnection(); $stmt = $pdo->query(' SELECT w.status, AVG(w.capacity) as avg_capacity, COUNT(*) as worker_count, COUNT(j.id) as job_count FROM workers w LEFT JOIN jobs j ON w.id = j.worker_id GROUP BY w.status '); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } private function cleanupJobs(): void { $pdo = $this->database->getConnection(); $pdo->exec('DELETE FROM jobs'); } 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 ) '); // Comprehensive indexes for performance testing $pdo->exec('CREATE INDEX idx_workers_id ON workers(id)'); $pdo->exec('CREATE INDEX idx_workers_status ON workers(status)'); $pdo->exec('CREATE INDEX idx_workers_capacity ON workers(capacity)'); $pdo->exec('CREATE INDEX idx_workers_status_capacity ON workers(status, capacity)'); $pdo->exec('CREATE INDEX idx_jobs_id ON jobs(id)'); $pdo->exec('CREATE INDEX idx_jobs_status ON jobs(status)'); $pdo->exec('CREATE INDEX idx_jobs_priority ON jobs(priority)'); $pdo->exec('CREATE INDEX idx_jobs_queue ON jobs(queue_name)'); $pdo->exec('CREATE INDEX idx_jobs_worker ON jobs(worker_id)'); $pdo->exec('CREATE INDEX idx_jobs_status_priority ON jobs(status, priority)'); $pdo->exec('CREATE INDEX idx_jobs_created ON jobs(created_at)'); return new DatabaseManager($pdo); } private function cleanupTestData(): void { $pdo = $this->database->getConnection(); $pdo->exec('DELETE FROM workers'); $pdo->exec('DELETE FROM jobs'); } }