'data'], queue: new QueueName('test_queue'), priority: $priority ), status: JobStatus::PENDING, createdAt: new \DateTimeImmutable(), attempts: 0 ); } public static function createBulkJobs(int $count, JobPriority $priority = JobPriority::NORMAL): array { $jobs = []; for ($i = 0; $i < $count; $i++) { $jobs[] = self::createTestJob( id: "job_{$i}", priority: $priority, payload: ['batch_id' => $i, 'data' => str_repeat('x', 100)] ); } return $jobs; } public static function measureTime(callable $operation): float { $start = microtime(true); $operation(); $end = microtime(true); return ($end - $start) * 1000; // Return milliseconds } public static function measureTimeWithResult(callable $operation): array { $start = microtime(true); $result = $operation(); $end = microtime(true); return [ 'result' => $result, 'time_ms' => ($end - $start) * 1000, ]; } public static function calculateStatistics(array $measurements): array { if (empty($measurements)) { return [ 'count' => 0, 'min' => 0, 'max' => 0, 'avg' => 0, 'median' => 0, 'p95' => 0, 'p99' => 0, 'stddev' => 0, ]; } sort($measurements); $count = count($measurements); $min = $measurements[0]; $max = $measurements[$count - 1]; $avg = array_sum($measurements) / $count; $median = $count % 2 === 0 ? ($measurements[$count / 2 - 1] + $measurements[$count / 2]) / 2 : $measurements[intval($count / 2)]; $p95Index = intval($count * 0.95) - 1; $p99Index = intval($count * 0.99) - 1; $p95 = $measurements[max(0, $p95Index)]; $p99 = $measurements[max(0, $p99Index)]; // Calculate standard deviation $sumSquaredDiff = 0; foreach ($measurements as $value) { $sumSquaredDiff += pow($value - $avg, 2); } $stddev = sqrt($sumSquaredDiff / $count); return [ 'count' => $count, 'min' => round($min, 3), 'max' => round($max, 3), 'avg' => round($avg, 3), 'median' => round($median, 3), 'p95' => round($p95, 3), 'p99' => round($p99, 3), 'stddev' => round($stddev, 3), ]; } public static function formatStatistics(array $stats, string $unit = 'ms'): string { return sprintf( "Count: %d, Min: %.3f%s, Max: %.3f%s, Avg: %.3f%s, P95: %.3f%s, P99: %.3f%s, StdDev: %.3f%s", $stats['count'], $stats['min'], $unit, $stats['max'], $unit, $stats['avg'], $unit, $stats['p95'], $unit, $stats['p99'], $unit, $stats['stddev'], $unit ); } public static function assertPerformance( array $measurements, float $expectedAvg, float $expectedP95, string $operation ): void { $stats = self::calculateStatistics($measurements); if ($stats['avg'] > $expectedAvg) { throw new \AssertionError( sprintf( "%s average performance exceeded: expected ≤%.3fms, got %.3fms", $operation, $expectedAvg, $stats['avg'] ) ); } if ($stats['p95'] > $expectedP95) { throw new \AssertionError( sprintf( "%s P95 performance exceeded: expected ≤%.3fms, got %.3fms", $operation, $expectedP95, $stats['p95'] ) ); } } public static function getMemoryUsage(): array { return [ 'current_mb' => round(memory_get_usage(true) / 1024 / 1024, 2), 'peak_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2), 'current_real_mb' => round(memory_get_usage(false) / 1024 / 1024, 2), 'peak_real_mb' => round(memory_get_peak_usage(false) / 1024 / 1024, 2), ]; } public static function warmupDatabase(\PDO $pdo): void { // Execute simple queries to warm up connections $pdo->query('SELECT 1'); $pdo->query('SELECT COUNT(*) FROM workers'); $pdo->query('SELECT COUNT(*) FROM jobs'); } public static function createConcurrentOperation(callable $operation, int $concurrency): \Generator { $operations = []; for ($i = 0; $i < $concurrency; $i++) { $operations[] = function () use ($operation, $i) { return $operation($i); }; } foreach ($operations as $op) { yield $op; } } public static function simulateLoad( callable $operation, int $totalOperations, int $concurrency, float $durationSeconds = null ): array { $results = []; $startTime = microtime(true); $endTime = $durationSeconds ? $startTime + $durationSeconds : PHP_FLOAT_MAX; $operationsCompleted = 0; $batch = 0; while ($operationsCompleted < $totalOperations && microtime(true) < $endTime) { $batchSize = min($concurrency, $totalOperations - $operationsCompleted); $batchResults = []; // Execute concurrent operations for ($i = 0; $i < $batchSize; $i++) { $result = self::measureTimeWithResult(function () use ($operation, $batch, $i) { return $operation($batch * $concurrency + $i); }); $batchResults[] = $result; } $results = array_merge($results, $batchResults); $operationsCompleted += $batchSize; $batch++; // Small delay to prevent overwhelming the system if (microtime(true) < $endTime) { usleep(1000); // 1ms } } return [ 'results' => $results, 'operations_completed' => $operationsCompleted, 'duration_seconds' => microtime(true) - $startTime, 'throughput_ops_per_sec' => $operationsCompleted / (microtime(true) - $startTime), ]; } public static function generatePerformanceReport(array $testResults): string { $report = "\n" . str_repeat("=", 80) . "\n"; $report .= "PERFORMANCE TEST REPORT\n"; $report .= str_repeat("=", 80) . "\n\n"; foreach ($testResults as $testName => $results) { $report .= "Test: {$testName}\n"; $report .= str_repeat("-", 40) . "\n"; if (isset($results['statistics'])) { $report .= "Statistics: " . self::formatStatistics($results['statistics']) . "\n"; } if (isset($results['throughput'])) { $report .= "Throughput: {$results['throughput']} ops/sec\n"; } if (isset($results['memory'])) { $report .= sprintf( "Memory: Current: %.2fMB, Peak: %.2fMB\n", $results['memory']['current_mb'], $results['memory']['peak_mb'] ); } if (isset($results['notes'])) { $report .= "Notes: {$results['notes']}\n"; } $report .= "\n"; } return $report; } }