tracker = new NestedPerformanceTracker( new SystemClock(), new SystemHighResolutionClock(), new MemoryMonitor() ); $this->profiler = new MemoryProfiler($this->tracker); }); it('detects no leaks when memory usage is low', function () { $this->tracker->measure( 'small.operation', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 100, 'x'); usleep(1000); } ); $result = $this->profiler->detectMemoryLeaks(1.0); expect($result)->toHaveKeys([ 'potential_leaks', 'total_memory_growth_mb', 'leak_count', 'threshold_mb' ]); expect($result['leak_count'])->toBe(0); expect($result['potential_leaks'])->toBeArray(); expect($result['potential_leaks'])->toHaveCount(0); expect($result['threshold_mb'])->toBe(1.0); }); it('uses default threshold of 5MB', function () { $result = $this->profiler->detectMemoryLeaks(); expect($result['threshold_mb'])->toBe(5.0); }); it('tracks cumulative memory growth', function () { // Simulate multiple operations for ($i = 0; $i < 3; $i++) { $this->tracker->measure( "operation.{$i}", PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, str_repeat('x', 100)); usleep(500); } ); } $result = $this->profiler->detectMemoryLeaks(0.1); expect($result['total_memory_growth_mb'])->toBeFloat(); expect($result)->toHaveKey('leak_count'); }); it('includes context in leak reports', function () { $this->tracker->measure( 'large.allocation', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 10000, str_repeat('x', 100)); usleep(1000); }, ['test_context' => 'value'] ); $result = $this->profiler->detectMemoryLeaks(0.01); if ($result['leak_count'] > 0) { $leak = $result['potential_leaks'][0]; expect($leak)->toHaveKeys([ 'operation', 'memory_allocated_mb', 'cumulative_memory_mb', 'category', 'context' ]); } }); }); describe('MemoryProfiler - Hotspot Identification', function () { beforeEach(function () { $this->tracker = new NestedPerformanceTracker( new SystemClock(), new SystemHighResolutionClock(), new MemoryMonitor() ); $this->profiler = new MemoryProfiler($this->tracker); }); it('returns empty array when no operations measured', function () { $hotspots = $this->profiler->getMemoryHotspots(); expect($hotspots)->toBeArray(); expect($hotspots)->toHaveCount(0); }); it('returns hotspots sorted by memory usage', function () { // Small allocation $this->tracker->measure( 'small', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 100, 'x'); usleep(1000); } ); // Medium allocation $this->tracker->measure( 'medium', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(1000); } ); // Large allocation $this->tracker->measure( 'large', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 5000, 'x'); usleep(1000); } ); $hotspots = $this->profiler->getMemoryHotspots(); expect($hotspots)->toBeArray(); expect($hotspots)->toHaveCount(3); // Verify structure foreach ($hotspots as $hotspot) { expect($hotspot)->toHaveKeys([ 'operation', 'memory_mb', 'duration_ms', 'memory_per_ms', 'category', 'depth' ]); } }); it('respects limit parameter', function () { for ($i = 0; $i < 5; $i++) { $this->tracker->measure( "operation.{$i}", PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); } $hotspots = $this->profiler->getMemoryHotspots(3); expect($hotspots)->toBeArray(); expect($hotspots)->toHaveCount(3); }); it('calculates memory per millisecond correctly', function () { $this->tracker->measure( 'test.operation', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(1000); // ~1ms } ); $hotspots = $this->profiler->getMemoryHotspots(1); expect($hotspots)->toHaveCount(1); expect($hotspots[0]['memory_per_ms'])->toBeFloat(); expect($hotspots[0]['memory_per_ms'])->toBeGreaterThanOrEqual(0); }); it('handles zero duration operations', function () { $this->tracker->measure( 'instant.operation', PerformanceCategory::CUSTOM, function () { // Very fast operation $x = 1 + 1; } ); $hotspots = $this->profiler->getMemoryHotspots(1); expect($hotspots)->toHaveCount(1); // Should not cause division by zero - can be 0 (int) or 0.0 (float) expect($hotspots[0]['memory_per_ms'])->toBeGreaterThanOrEqual(0); }); }); describe('MemoryProfiler - Efficiency Scoring', function () { beforeEach(function () { $this->tracker = new NestedPerformanceTracker( new SystemClock(), new SystemHighResolutionClock(), new MemoryMonitor() ); $this->profiler = new MemoryProfiler($this->tracker); }); it('returns perfect score for empty tracker', function () { $efficiency = $this->profiler->calculateEfficiencyScore(); expect($efficiency)->toHaveKeys([ 'score', 'rating', 'total_memory_mb', 'total_time_ms' ]); expect($efficiency['score'])->toBe(100); expect($efficiency['rating'])->toBe('N/A'); expect($efficiency['total_memory_mb'])->toBe(0); expect($efficiency['total_time_ms'])->toBe(0); }); it('scores efficient operations highly', function () { $this->tracker->measure( 'efficient.operation', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 10, 'x'); // Minimal memory usleep(1000); // ~1ms } ); $efficiency = $this->profiler->calculateEfficiencyScore(); expect($efficiency['score'])->toBeGreaterThan(80); expect($efficiency['rating'])->toBeIn(['Excellent', 'Good']); }); it('includes memory per millisecond metric', function () { $this->tracker->measure( 'test.operation', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(2000); } ); $efficiency = $this->profiler->calculateEfficiencyScore(); expect($efficiency)->toHaveKey('memory_per_ms'); expect($efficiency['memory_per_ms'])->toBeFloat(); expect($efficiency['memory_per_ms'])->toBeGreaterThanOrEqual(0); }); it('provides correct rating categories', function () { $efficiency = $this->profiler->calculateEfficiencyScore(); expect($efficiency['rating'])->toBeIn([ 'Excellent', 'Good', 'Fair', 'Poor', 'Critical', 'N/A' ]); }); it('calculates total memory correctly', function () { $this->tracker->measure( 'operation.1', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); $this->tracker->measure( 'operation.2', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 500, 'x'); usleep(500); } ); $efficiency = $this->profiler->calculateEfficiencyScore(); expect($efficiency['total_memory_mb'])->toBeFloat(); expect($efficiency['total_time_ms'])->toBeFloat(); expect($efficiency['total_time_ms'])->toBeGreaterThan(0); }); }); describe('MemoryProfiler - Memory Report Generation', function () { beforeEach(function () { $this->tracker = new NestedPerformanceTracker( new SystemClock(), new SystemHighResolutionClock(), new MemoryMonitor() ); $this->profiler = new MemoryProfiler($this->tracker); }); it('generates empty report for no operations', function () { $report = $this->profiler->generateMemoryReport(); expect($report)->toHaveKeys([ 'summary', 'by_category', 'hotspots', 'efficiency', 'leaks' ]); expect($report['summary'])->toHaveKeys([ 'total_operations', 'total_memory_mb', 'avg_memory_per_operation_mb', 'peak_memory_mb' ]); expect($report['summary']['total_operations'])->toBe(0); expect($report['by_category'])->toBeArray(); expect($report['hotspots'])->toBeArray(); }); it('generates comprehensive report with operations', function () { $this->tracker->measure( 'database.query', PerformanceCategory::DATABASE, function () { $data = array_fill(0, 1000, 'x'); usleep(1000); } ); $this->tracker->measure( 'cache.load', PerformanceCategory::CACHE, function () { $data = array_fill(0, 500, 'x'); usleep(500); } ); $report = $this->profiler->generateMemoryReport(); expect($report['summary']['total_operations'])->toBe(2); expect($report['summary']['total_memory_mb'])->toBeFloat(); expect($report['summary']['avg_memory_per_operation_mb'])->toBeFloat(); expect($report['summary']['peak_memory_mb'])->toBeFloat(); }); it('groups operations by category', function () { $this->tracker->measure( 'db.query.1', PerformanceCategory::DATABASE, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); $this->tracker->measure( 'db.query.2', PerformanceCategory::DATABASE, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); $this->tracker->measure( 'cache.get', PerformanceCategory::CACHE, function () { $data = array_fill(0, 500, 'x'); usleep(300); } ); $report = $this->profiler->generateMemoryReport(); expect($report['by_category'])->toBeArray(); expect($report['by_category'])->toHaveKey('database'); expect($report['by_category'])->toHaveKey('cache'); $dbStats = $report['by_category']['database']; expect($dbStats)->toHaveKeys([ 'operations', 'total_memory_mb', 'avg_memory_mb', 'peak_memory_mb' ]); expect($dbStats['operations'])->toBe(2); }); it('includes top hotspots in report', function () { for ($i = 0; $i < 10; $i++) { $this->tracker->measure( "operation.{$i}", PerformanceCategory::CUSTOM, function () use ($i) { $data = array_fill(0, ($i + 1) * 100, 'x'); usleep(500); } ); } $report = $this->profiler->generateMemoryReport(); expect($report['hotspots'])->toBeArray(); expect($report['hotspots'])->toHaveCount(5); // Top 5 }); it('includes efficiency and leak detection', function () { $this->tracker->measure( 'test.operation', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(1000); } ); $report = $this->profiler->generateMemoryReport(); expect($report['efficiency'])->toHaveKeys([ 'score', 'rating', 'total_memory_mb', 'total_time_ms' ]); expect($report['leaks'])->toHaveKeys([ 'potential_leaks', 'leak_count', 'threshold_mb' ]); }); }); describe('MemoryProfiler - Memory Tracking Over Time', function () { beforeEach(function () { $this->tracker = new NestedPerformanceTracker( new SystemClock(), new SystemHighResolutionClock(), new MemoryMonitor() ); $this->profiler = new MemoryProfiler($this->tracker); }); it('returns empty tracking for no operations', function () { $tracking = $this->profiler->trackMemoryOverTime(); expect($tracking)->toHaveKeys([ 'tracking_points', 'final_cumulative_mb', 'trend' ]); expect($tracking['tracking_points'])->toBeArray(); expect($tracking['tracking_points'])->toHaveCount(0); expect($tracking['final_cumulative_mb'])->toBe(0.0); expect($tracking['trend'])->toBe('insufficient_data'); }); it('tracks cumulative memory over operations', function () { for ($i = 0; $i < 3; $i++) { $this->tracker->measure( "operation.{$i}", PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); } $tracking = $this->profiler->trackMemoryOverTime(); expect($tracking['tracking_points'])->toHaveCount(3); foreach ($tracking['tracking_points'] as $point) { expect($point)->toHaveKeys([ 'timestamp', 'operation', 'delta_mb', 'cumulative_mb' ]); expect($point['timestamp'])->toBeFloat(); expect($point['cumulative_mb'])->toBeFloat(); } }); it('provides trend analysis', function () { for ($i = 0; $i < 5; $i++) { $this->tracker->measure( "operation.{$i}", PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(300); } ); } $tracking = $this->profiler->trackMemoryOverTime(); expect($tracking['trend'])->toBeIn([ 'rapidly_growing', 'growing', 'stable', 'decreasing', 'rapidly_decreasing', 'insufficient_data' ]); }); it('calculates cumulative memory correctly', function () { $this->tracker->measure( 'operation.1', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); $this->tracker->measure( 'operation.2', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 500, 'x'); usleep(500); } ); $tracking = $this->profiler->trackMemoryOverTime(); expect($tracking['tracking_points'])->toHaveCount(2); $firstPoint = $tracking['tracking_points'][0]; $secondPoint = $tracking['tracking_points'][1]; // Cumulative should increase or stay same expect($secondPoint['cumulative_mb'])->toBeGreaterThanOrEqual($firstPoint['cumulative_mb']); }); }); describe('MemoryProfiler - Budget Validation', function () { beforeEach(function () { $this->tracker = new NestedPerformanceTracker( new SystemClock(), new SystemHighResolutionClock(), new MemoryMonitor() ); $this->profiler = new MemoryProfiler($this->tracker); }); it('returns no violations for empty operations', function () { $budgets = [ 'test.operation' => 1.0 ]; $violations = $this->profiler->getMemoryBudgetViolations($budgets); expect($violations)->toHaveKeys([ 'violations', 'violation_count', 'budgets_checked' ]); expect($violations['violation_count'])->toBe(0); expect($violations['budgets_checked'])->toBe(1); }); it('detects budget violations', function () { $this->tracker->measure( 'api.request', PerformanceCategory::API, function () { $data = array_fill(0, 10000, str_repeat('x', 100)); // Large allocation usleep(1000); } ); $budgets = [ 'api.request' => 0.01 // Very strict budget ]; $violations = $this->profiler->getMemoryBudgetViolations($budgets); // Violation detection depends on actual memory delta expect($violations['budgets_checked'])->toBe(1); expect($violations['violations'])->toBeArray(); }); it('includes violation details', function () { $this->tracker->measure( 'database.query', PerformanceCategory::DATABASE, function () { $data = array_fill(0, 10000, str_repeat('x', 100)); usleep(1000); } ); $budgets = [ 'database.query' => 0.001 ]; $violations = $this->profiler->getMemoryBudgetViolations($budgets); if ($violations['violation_count'] > 0) { $violation = $violations['violations'][0]; expect($violation)->toHaveKeys([ 'operation', 'memory_mb', 'budget_mb', 'exceeded_by_mb', 'percentage' ]); expect($violation['budget_mb'])->toBe(0.001); } }); it('supports pattern matching in budgets', function () { $this->tracker->measure( 'api.request.user', PerformanceCategory::API, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); $this->tracker->measure( 'api.request.product', PerformanceCategory::API, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); $budgets = [ 'api.request' => 0.001 // Matches both operations ]; $violations = $this->profiler->getMemoryBudgetViolations($budgets); expect($violations['budgets_checked'])->toBe(1); }); }); describe('MemoryProfiler - Operation Comparison', function () { beforeEach(function () { $this->tracker = new NestedPerformanceTracker( new SystemClock(), new SystemHighResolutionClock(), new MemoryMonitor() ); $this->profiler = new MemoryProfiler($this->tracker); }); it('compares memory usage between operations', function () { $this->tracker->measure( 'implementation.v1', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 5000, 'x'); usleep(1000); } ); $this->tracker->measure( 'implementation.v2', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 2500, 'x'); usleep(1000); } ); $comparison = $this->profiler->compareOperations('implementation.v1', 'implementation.v2'); expect($comparison)->toHaveKeys([ 'operation_1', 'operation_2', 'comparison' ]); expect($comparison['operation_1'])->toHaveKeys([ 'name', 'memory_mb', 'executions' ]); expect($comparison['operation_2'])->toHaveKeys([ 'name', 'memory_mb', 'executions' ]); expect($comparison['comparison'])->toHaveKeys([ 'difference_mb', 'percentage_diff', 'winner' ]); }); it('identifies winner correctly', function () { $this->tracker->measure( 'heavy.operation', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 10000, 'x'); usleep(1000); } ); $this->tracker->measure( 'light.operation', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 100, 'x'); usleep(1000); } ); $comparison = $this->profiler->compareOperations('heavy.operation', 'light.operation'); expect($comparison['comparison']['winner'])->toBeIn(['heavy.operation', 'light.operation']); }); it('handles operations with multiple executions', function () { // Execute v1 twice for ($i = 0; $i < 2; $i++) { $this->tracker->measure( 'version.1', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); } // Execute v2 once $this->tracker->measure( 'version.2', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 500, 'x'); usleep(500); } ); $comparison = $this->profiler->compareOperations('version.1', 'version.2'); expect($comparison['operation_1']['executions'])->toBe(2); expect($comparison['operation_2']['executions'])->toBe(1); }); it('calculates percentage difference correctly', function () { $this->tracker->measure( 'op.a', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); $this->tracker->measure( 'op.b', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); $comparison = $this->profiler->compareOperations('op.a', 'op.b'); expect($comparison['comparison']['percentage_diff'])->toBeFloat(); }); it('handles non-existent operations gracefully', function () { $this->tracker->measure( 'existing.operation', PerformanceCategory::CUSTOM, function () { $data = array_fill(0, 1000, 'x'); usleep(500); } ); $comparison = $this->profiler->compareOperations('existing.operation', 'non.existent'); expect($comparison['operation_1']['memory_mb'])->toBeFloat(); expect($comparison['operation_2']['memory_mb'])->toBe(0.0); expect($comparison['operation_2']['executions'])->toBe(0); }); });