#!/usr/bin/env php 200, // Single BOM resolution should be under 200ms 'batch_10_resolution_ms' => 1500, // 10 resolutions should be under 1.5s 'batch_100_resolution_ms' => 12000, // 100 resolutions should be under 12s 'memory_usage_mb' => 50, // Memory usage should stay under 50MB 'db_queries_per_resolution' => 20, // Should not exceed 20 DB queries per resolution 'concurrent_resolution_ms' => 500 // Concurrent resolutions should complete under 500ms ]; public function __construct() { $this->output("\n=== Parametric BOM Performance Testing ==="); $this->output("Testing system performance and scalability...\n"); $this->setupServices(); } private function setupServices(): void { // Find test tenant and model $this->tenant = Tenant::where('code', 'KSS_DEMO')->first(); if (!$this->tenant) { throw new Exception('KSS_DEMO tenant not found. Please run KSS01ModelSeeder first.'); } $this->model = DesignModel::where('tenant_id', $this->tenant->id) ->where('code', 'KSS01') ->first(); if (!$this->model) { throw new Exception('KSS01 model not found. Please run KSS01ModelSeeder first.'); } // Setup BOM resolver with query logging $this->bomResolver = new BomResolverService( new ModelParameterService(), new ModelFormulaService(), new BomConditionRuleService() ); $this->bomResolver->setTenantId($this->tenant->id); $this->bomResolver->setApiUserId(1); } public function run(): void { try { // Enable query logging for database performance analysis DB::enableQueryLog(); // Test single resolution performance $this->testSingleResolutionPerformance(); // Test batch resolution performance $this->testBatchResolutionPerformance(); // Test memory usage $this->testMemoryUsage(); // Test database query efficiency $this->testDatabaseQueryEfficiency(); // Test different parameter combinations $this->testParameterVariationPerformance(); // Test concurrent resolution (simulated) $this->testConcurrentResolution(); // Test large dataset performance $this->testLargeDatasetPerformance(); // Generate performance report $this->generatePerformanceReport(); } catch (Exception $e) { $this->output("\n❌ Critical Error: " . $e->getMessage()); exit(1); } } private function testSingleResolutionPerformance(): void { $this->output("\n🏁 Testing Single Resolution Performance..."); $testParams = [ 'W0' => 1200, 'H0' => 800, 'screen_type' => 'FABRIC', 'install_type' => 'WALL' ]; // Warm-up run $this->bomResolver->resolveBom($this->model->id, $testParams); // Performance test runs $times = []; $runs = 10; for ($i = 0; $i < $runs; $i++) { $startTime = microtime(true); $result = $this->bomResolver->resolveBom($this->model->id, $testParams); $endTime = microtime(true); $times[] = ($endTime - $startTime) * 1000; } $avgTime = array_sum($times) / count($times); $minTime = min($times); $maxTime = max($times); $this->performanceResults['single_resolution'] = [ 'avg_time_ms' => $avgTime, 'min_time_ms' => $minTime, 'max_time_ms' => $maxTime, 'runs' => $runs ]; $passed = $avgTime < self::THRESHOLDS['single_resolution_ms']; $this->recordTest('Single Resolution Performance', $passed); $this->output(sprintf(" Avg: %.2fms, Min: %.2fms, Max: %.2fms (Target: <%.0fms)", $avgTime, $minTime, $maxTime, self::THRESHOLDS['single_resolution_ms'])); if ($passed) { $this->output(" ✅ Single resolution performance within threshold"); } else { $this->output(" ❌ Single resolution performance exceeds threshold"); } } private function testBatchResolutionPerformance(): void { $this->output("\n📈 Testing Batch Resolution Performance..."); $scenarios = $this->generateTestScenarios(10); // Test batch of 10 $startTime = microtime(true); foreach ($scenarios as $params) { $this->bomResolver->resolveBom($this->model->id, $params); } $batch10Time = (microtime(true) - $startTime) * 1000; $this->performanceResults['batch_10_resolution'] = [ 'total_time_ms' => $batch10Time, 'avg_per_resolution_ms' => $batch10Time / 10 ]; $passed10 = $batch10Time < self::THRESHOLDS['batch_10_resolution_ms']; $this->recordTest('Batch 10 Resolution Performance', $passed10); $this->output(sprintf(" Batch 10: %.2fms total, %.2fms avg (Target: <%.0fms total)", $batch10Time, $batch10Time / 10, self::THRESHOLDS['batch_10_resolution_ms'])); // Test batch of 100 $largeBatch = $this->generateTestScenarios(100); $startTime = microtime(true); foreach ($largeBatch as $params) { $this->bomResolver->resolveBom($this->model->id, $params); } $batch100Time = (microtime(true) - $startTime) * 1000; $this->performanceResults['batch_100_resolution'] = [ 'total_time_ms' => $batch100Time, 'avg_per_resolution_ms' => $batch100Time / 100 ]; $passed100 = $batch100Time < self::THRESHOLDS['batch_100_resolution_ms']; $this->recordTest('Batch 100 Resolution Performance', $passed100); $this->output(sprintf(" Batch 100: %.2fms total, %.2fms avg (Target: <%.0fms total)", $batch100Time, $batch100Time / 100, self::THRESHOLDS['batch_100_resolution_ms'])); } private function testMemoryUsage(): void { $this->output("\n💾 Testing Memory Usage..."); $initialMemory = memory_get_usage(true); $peakMemory = memory_get_peak_usage(true); // Perform multiple resolutions and monitor memory $scenarios = $this->generateTestScenarios(50); foreach ($scenarios as $params) { $this->bomResolver->resolveBom($this->model->id, $params); } $finalMemory = memory_get_usage(true); $finalPeakMemory = memory_get_peak_usage(true); $memoryUsed = ($finalMemory - $initialMemory) / 1024 / 1024; $peakMemoryUsed = ($finalPeakMemory - $peakMemory) / 1024 / 1024; $this->performanceResults['memory_usage'] = [ 'memory_used_mb' => $memoryUsed, 'peak_memory_used_mb' => $peakMemoryUsed, 'initial_memory_mb' => $initialMemory / 1024 / 1024, 'final_memory_mb' => $finalMemory / 1024 / 1024 ]; $passed = $peakMemoryUsed < self::THRESHOLDS['memory_usage_mb']; $this->recordTest('Memory Usage', $passed); $this->output(sprintf(" Memory used: %.2fMB, Peak: %.2fMB (Target: <%.0fMB)", $memoryUsed, $peakMemoryUsed, self::THRESHOLDS['memory_usage_mb'])); if ($passed) { $this->output(" ✅ Memory usage within acceptable limits"); } else { $this->output(" ❌ Memory usage exceeds threshold"); } } private function testDatabaseQueryEfficiency(): void { $this->output("\n📊 Testing Database Query Efficiency..."); // Clear query log DB::flushQueryLog(); $testParams = [ 'W0' => 1500, 'H0' => 1000, 'screen_type' => 'STEEL', 'install_type' => 'CEILING' ]; // Perform resolution and count queries $this->bomResolver->resolveBom($this->model->id, $testParams); $queries = DB::getQueryLog(); $queryCount = count($queries); // Analyze query types $selectQueries = 0; $totalQueryTime = 0; foreach ($queries as $query) { if (stripos($query['query'], 'select') === 0) { $selectQueries++; } $totalQueryTime += $query['time']; } $this->performanceResults['database_efficiency'] = [ 'total_queries' => $queryCount, 'select_queries' => $selectQueries, 'total_query_time_ms' => $totalQueryTime, 'avg_query_time_ms' => $queryCount > 0 ? $totalQueryTime / $queryCount : 0 ]; $passed = $queryCount <= self::THRESHOLDS['db_queries_per_resolution']; $this->recordTest('Database Query Efficiency', $passed); $this->output(sprintf(" Total queries: %d, Select queries: %d, Total time: %.2fms (Target: <%d queries)", $queryCount, $selectQueries, $totalQueryTime, self::THRESHOLDS['db_queries_per_resolution'])); if ($passed) { $this->output(" ✅ Database query count within threshold"); } else { $this->output(" ❌ Too many database queries per resolution"); } // Show some sample queries for analysis if (count($queries) > 0) { $this->output(" Sample queries:"); $sampleCount = min(3, count($queries)); for ($i = 0; $i < $sampleCount; $i++) { $query = $queries[$i]; $shortQuery = substr($query['query'], 0, 80) . (strlen($query['query']) > 80 ? '...' : ''); $this->output(sprintf(" %s (%.2fms)", $shortQuery, $query['time'])); } } } private function testParameterVariationPerformance(): void { $this->output("\n🔄 Testing Parameter Variation Performance..."); $variations = [ 'Small' => ['W0' => 600, 'H0' => 400, 'screen_type' => 'FABRIC', 'install_type' => 'WALL'], 'Medium' => ['W0' => 1200, 'H0' => 800, 'screen_type' => 'FABRIC', 'install_type' => 'WALL'], 'Large' => ['W0' => 2400, 'H0' => 1800, 'screen_type' => 'STEEL', 'install_type' => 'CEILING'], 'Extra Large' => ['W0' => 3000, 'H0' => 2500, 'screen_type' => 'STEEL', 'install_type' => 'RECESSED'] ]; $variationResults = []; foreach ($variations as $name => $params) { $times = []; $runs = 5; for ($i = 0; $i < $runs; $i++) { $startTime = microtime(true); $result = $this->bomResolver->resolveBom($this->model->id, $params); $endTime = microtime(true); $times[] = ($endTime - $startTime) * 1000; } $avgTime = array_sum($times) / count($times); $bomItemCount = count($result['resolved_bom']); $variationResults[$name] = [ 'avg_time_ms' => $avgTime, 'bom_items' => $bomItemCount, 'area' => $result['calculated_values']['area'] ]; $this->output(sprintf(" %s: %.2fms, %d BOM items, %.2fm²", $name, $avgTime, $bomItemCount, $result['calculated_values']['area'])); } $this->performanceResults['parameter_variations'] = $variationResults; // Check if performance is consistent across variations $times = array_column($variationResults, 'avg_time_ms'); $maxVariation = max($times) - min($times); $avgVariation = array_sum($times) / count($times); $passed = $maxVariation < ($avgVariation * 2); // Variation should not exceed 200% of average $this->recordTest('Parameter Variation Performance Consistency', $passed); if ($passed) { $this->output(" ✅ Performance consistent across parameter variations"); } else { $this->output(" ❌ Performance varies significantly with different parameters"); } } private function testConcurrentResolution(): void { $this->output("\n🚀 Testing Concurrent Resolution (Simulated)..."); // Simulate concurrent requests by rapidly executing multiple resolutions $concurrentCount = 5; $scenarios = $this->generateTestScenarios($concurrentCount); $startTime = microtime(true); // Simulate concurrent processing $results = []; foreach ($scenarios as $i => $params) { $resolveStart = microtime(true); $result = $this->bomResolver->resolveBom($this->model->id, $params); $resolveEnd = microtime(true); $results[] = [ 'index' => $i, 'time_ms' => ($resolveEnd - $resolveStart) * 1000, 'bom_items' => count($result['resolved_bom']) ]; } $totalTime = (microtime(true) - $startTime) * 1000; $avgConcurrentTime = array_sum(array_column($results, 'time_ms')) / count($results); $this->performanceResults['concurrent_resolution'] = [ 'concurrent_count' => $concurrentCount, 'total_time_ms' => $totalTime, 'avg_resolution_time_ms' => $avgConcurrentTime, 'results' => $results ]; $passed = $avgConcurrentTime < self::THRESHOLDS['concurrent_resolution_ms']; $this->recordTest('Concurrent Resolution Performance', $passed); $this->output(sprintf(" %d concurrent resolutions: %.2fms total, %.2fms avg (Target: <%.0fms avg)", $concurrentCount, $totalTime, $avgConcurrentTime, self::THRESHOLDS['concurrent_resolution_ms'])); if ($passed) { $this->output(" ✅ Concurrent resolution performance acceptable"); } else { $this->output(" ❌ Concurrent resolution performance degraded"); } } private function testLargeDatasetPerformance(): void { $this->output("\n📊 Testing Large Dataset Performance..."); // Test with large number of variations $largeDataset = $this->generateTestScenarios(200); $batchSize = 20; $batchTimes = []; for ($i = 0; $i < count($largeDataset); $i += $batchSize) { $batch = array_slice($largeDataset, $i, $batchSize); $batchStart = microtime(true); foreach ($batch as $params) { $this->bomResolver->resolveBom($this->model->id, $params); } $batchEnd = microtime(true); $batchTimes[] = ($batchEnd - $batchStart) * 1000; } $totalTime = array_sum($batchTimes); $avgBatchTime = $totalTime / count($batchTimes); $throughput = count($largeDataset) / ($totalTime / 1000); // resolutions per second $this->performanceResults['large_dataset'] = [ 'total_resolutions' => count($largeDataset), 'batch_size' => $batchSize, 'total_time_ms' => $totalTime, 'avg_batch_time_ms' => $avgBatchTime, 'throughput_per_second' => $throughput ]; $passed = $throughput >= 10; // At least 10 resolutions per second $this->recordTest('Large Dataset Throughput', $passed); $this->output(sprintf(" %d resolutions: %.2fms total, %.2f resolutions/sec (Target: >=10/sec)", count($largeDataset), $totalTime, $throughput)); if ($passed) { $this->output(" ✅ Large dataset throughput acceptable"); } else { $this->output(" ❌ Large dataset throughput too low"); } } private function generateTestScenarios(int $count): array { $scenarios = []; $widths = [600, 800, 1000, 1200, 1500, 1800, 2000, 2400, 3000]; $heights = [400, 600, 800, 1000, 1200, 1500, 1800, 2000, 2500]; $screenTypes = ['FABRIC', 'STEEL']; $installTypes = ['WALL', 'CEILING', 'RECESSED']; for ($i = 0; $i < $count; $i++) { $scenarios[] = [ 'W0' => $widths[array_rand($widths)], 'H0' => $heights[array_rand($heights)], 'screen_type' => $screenTypes[array_rand($screenTypes)], 'install_type' => $installTypes[array_rand($installTypes)] ]; } return $scenarios; } private function recordTest(string $name, bool $passed): void { $this->totalTests++; if ($passed) { $this->passedTests++; } } private function output(string $message): void { echo $message . "\n"; Log::info("BOM Performance: " . $message); } private function generatePerformanceReport(): void { $this->output("\n" . str_repeat("=", 70)); $this->output("PARAMETRIC BOM PERFORMANCE REPORT"); $this->output(str_repeat("=", 70)); $successRate = $this->totalTests > 0 ? round(($this->passedTests / $this->totalTests) * 100, 1) : 0; $this->output("Total Performance Tests: {$this->totalTests}"); $this->output("Passed: {$this->passedTests}"); $this->output("Failed: " . ($this->totalTests - $this->passedTests)); $this->output("Success Rate: {$successRate}%"); $this->output("\n📊 PERFORMANCE METRICS:"); // Single resolution performance if (isset($this->performanceResults['single_resolution'])) { $single = $this->performanceResults['single_resolution']; $this->output(sprintf(" Single Resolution: %.2fms avg (Target: <%.0fms)", $single['avg_time_ms'], self::THRESHOLDS['single_resolution_ms'])); } // Batch performance if (isset($this->performanceResults['batch_100_resolution'])) { $batch = $this->performanceResults['batch_100_resolution']; $this->output(sprintf(" Batch 100 Resolutions: %.2fms total, %.2fms avg", $batch['total_time_ms'], $batch['avg_per_resolution_ms'])); } // Memory usage if (isset($this->performanceResults['memory_usage'])) { $memory = $this->performanceResults['memory_usage']; $this->output(sprintf(" Memory Usage: %.2fMB peak (Target: <%.0fMB)", $memory['peak_memory_used_mb'], self::THRESHOLDS['memory_usage_mb'])); } // Database efficiency if (isset($this->performanceResults['database_efficiency'])) { $db = $this->performanceResults['database_efficiency']; $this->output(sprintf(" Database Queries: %d per resolution (Target: <%d)", $db['total_queries'], self::THRESHOLDS['db_queries_per_resolution'])); } // Throughput if (isset($this->performanceResults['large_dataset'])) { $throughput = $this->performanceResults['large_dataset']; $this->output(sprintf(" Throughput: %.2f resolutions/second (Target: >=10/sec)", $throughput['throughput_per_second'])); } $this->output("\n" . str_repeat("=", 70)); if ($successRate >= 90) { $this->output("🎉 PERFORMANCE TESTS PASSED - System meets performance requirements"); exit(0); } elseif ($successRate >= 70) { $this->output("⚠️ PERFORMANCE WARNING - Some performance issues detected"); exit(1); } else { $this->output("❌ PERFORMANCE TESTS FAILED - Critical performance issues found"); exit(2); } } } // Run the performance tests $tester = new BomPerformanceTester(); $tester->run();