- 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.
825 lines
24 KiB
PHP
825 lines
24 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Performance\NestedPerformanceTracker;
|
|
use App\Framework\Performance\MemoryProfiler;
|
|
use App\Framework\Performance\PerformanceCategory;
|
|
use App\Framework\DateTime\SystemClock;
|
|
use App\Framework\DateTime\SystemHighResolutionClock;
|
|
use App\Framework\Performance\MemoryMonitor;
|
|
|
|
describe('MemoryProfiler - Leak Detection', function () {
|
|
beforeEach(function () {
|
|
$this->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);
|
|
});
|
|
});
|