Files
sam-api/scripts/validation/performance_test.php

560 lines
21 KiB
PHP
Raw Normal View History

#!/usr/bin/env php
<?php
/**
* Parametric BOM Performance Testing Script
*
* This script performs performance testing of the parametric BOM system
* including load testing, memory usage analysis, and scalability testing.
*
* Usage: php scripts/validation/performance_test.php
*/
require_once __DIR__ . '/../../bootstrap/app.php';
use App\Models\Tenant;
use App\Models\Design\DesignModel;
use App\Services\Design\BomResolverService;
use App\Services\Design\ModelParameterService;
use App\Services\Design\ModelFormulaService;
use App\Services\Design\BomConditionRuleService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class BomPerformanceTester
{
private Tenant $tenant;
private DesignModel $model;
private BomResolverService $bomResolver;
private array $performanceResults = [];
private int $passedTests = 0;
private int $totalTests = 0;
// Performance thresholds (adjustable based on requirements)
private const THRESHOLDS = [
'single_resolution_ms' => 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();