Files
michaelschiemer/tests/Framework/Queue/Performance/SystemResourcesTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

620 lines
21 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Framework\Queue\Performance;
use App\Framework\Database\DatabaseManager;
use App\Framework\Queue\Distribution\JobDistributionService;
use App\Framework\Queue\Workers\WorkerRegistry;
use PHPUnit\Framework\TestCase;
final class SystemResourcesTest extends TestCase
{
private DatabaseManager $database;
private WorkerRegistry $workerRegistry;
private JobDistributionService $distributionService;
protected function setUp(): void
{
$this->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');
}
}