- 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.
297 lines
11 KiB
PHP
297 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Performance\GarbageCollectionMonitor;
|
|
use App\Framework\Performance\ValueObjects\GcStatus;
|
|
use App\Framework\Performance\ValueObjects\GcResult;
|
|
|
|
describe('GarbageCollectionMonitor', function () {
|
|
beforeEach(function () {
|
|
$this->monitor = new GarbageCollectionMonitor();
|
|
});
|
|
|
|
it('returns current GC status', function () {
|
|
$status = $this->monitor->getStatus();
|
|
|
|
expect($status)->toBeInstanceOf(GcStatus::class);
|
|
expect($status->runs)->toBeInt();
|
|
expect($status->collected)->toBeInt();
|
|
expect($status->threshold)->toBeGreaterThan(0);
|
|
expect($status->roots)->toBeInt();
|
|
});
|
|
|
|
it('gets number of GC runs', function () {
|
|
$runs = $this->monitor->getRuns();
|
|
|
|
expect($runs)->toBeInt();
|
|
expect($runs)->toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('gets number of collected cycles', function () {
|
|
$collected = $this->monitor->getCollected();
|
|
|
|
expect($collected)->toBeInt();
|
|
expect($collected)->toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('gets GC threshold', function () {
|
|
$threshold = $this->monitor->getThreshold();
|
|
|
|
expect($threshold)->toBeInt();
|
|
expect($threshold)->toBeGreaterThan(0);
|
|
});
|
|
|
|
it('checks if GC is running', function () {
|
|
$isRunning = $this->monitor->isRunning();
|
|
|
|
expect($isRunning)->toBeBool();
|
|
});
|
|
|
|
it('checks if GC is enabled', function () {
|
|
$isEnabled = $this->monitor->isEnabled();
|
|
|
|
expect($isEnabled)->toBeBool();
|
|
});
|
|
|
|
it('can force garbage collection', function () {
|
|
// Create some circular references to collect
|
|
$obj1 = new stdClass();
|
|
$obj2 = new stdClass();
|
|
$obj1->ref = $obj2;
|
|
$obj2->ref = $obj1;
|
|
unset($obj1, $obj2);
|
|
|
|
$result = $this->monitor->forceCollection();
|
|
|
|
expect($result)->toBeInstanceOf(GcResult::class);
|
|
expect($result->collected)->toBeInt();
|
|
expect($result->duration)->toBeInstanceOf(App\Framework\Core\ValueObjects\Duration::class);
|
|
expect($result->memoryFreed)->toBeInstanceOf(App\Framework\Core\ValueObjects\Byte::class);
|
|
expect($result->statusBefore)->toBeInstanceOf(GcStatus::class);
|
|
expect($result->statusAfter)->toBeInstanceOf(GcStatus::class);
|
|
expect($result->timestamp)->toBeInstanceOf(App\Framework\Core\ValueObjects\Timestamp::class);
|
|
});
|
|
|
|
it('provides efficiency metrics', function () {
|
|
$metrics = $this->monitor->getEfficiencyMetrics();
|
|
|
|
expect($metrics)->toHaveKeys([
|
|
'collection_rate',
|
|
'threshold_ratio',
|
|
'is_healthy',
|
|
'runs',
|
|
'collected',
|
|
'roots',
|
|
'threshold',
|
|
]);
|
|
|
|
expect($metrics['collection_rate'])->toBeFloat();
|
|
expect($metrics['threshold_ratio'])->toBeFloat();
|
|
expect($metrics['is_healthy'])->toBeBool();
|
|
});
|
|
|
|
it('can take GC snapshot', function () {
|
|
$snapshot = $this->monitor->takeSnapshot('test_checkpoint');
|
|
|
|
expect($snapshot)->toHaveKeys(['label', 'timestamp', 'status', 'efficiency']);
|
|
expect($snapshot['label'])->toBe('test_checkpoint');
|
|
expect($snapshot['status'])->toBeArray();
|
|
expect($snapshot['efficiency'])->toBeArray();
|
|
});
|
|
|
|
it('can enable and disable GC', function () {
|
|
$this->monitor->disable();
|
|
expect(gc_enabled())->toBeFalse();
|
|
|
|
$this->monitor->enable();
|
|
expect(gc_enabled())->toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('GcStatus', function () {
|
|
it('creates from gc_status array', function () {
|
|
$status = GcStatus::fromGcStatus(gc_status());
|
|
|
|
expect($status)->toBeInstanceOf(GcStatus::class);
|
|
expect($status->runs)->toBeInt();
|
|
expect($status->collected)->toBeInt();
|
|
expect($status->threshold)->toBeInt();
|
|
expect($status->roots)->toBeInt();
|
|
});
|
|
|
|
it('creates current GC status', function () {
|
|
$status = GcStatus::current();
|
|
|
|
expect($status)->toBeInstanceOf(GcStatus::class);
|
|
});
|
|
|
|
it('calculates collection efficiency', function () {
|
|
$status = new GcStatus(runs: 10, collected: 50, threshold: 10001, roots: 100);
|
|
|
|
$efficiency = $status->getCollectionEfficiency();
|
|
|
|
expect($efficiency)->toBe(5.0); // 50 / 10
|
|
});
|
|
|
|
it('returns zero efficiency when no runs', function () {
|
|
$status = new GcStatus(runs: 0, collected: 0, threshold: 10001, roots: 0);
|
|
|
|
expect($status->getCollectionEfficiency())->toBe(0.0);
|
|
});
|
|
|
|
it('calculates threshold utilization', function () {
|
|
$status = new GcStatus(runs: 10, collected: 50, threshold: 1000, roots: 900);
|
|
|
|
$utilization = $status->getThresholdUtilization();
|
|
|
|
expect($utilization)->toBe(0.9); // 900 / 1000
|
|
});
|
|
|
|
it('checks if near threshold', function () {
|
|
$nearThreshold = new GcStatus(runs: 10, collected: 50, threshold: 1000, roots: 950);
|
|
$notNearThreshold = new GcStatus(runs: 10, collected: 50, threshold: 1000, roots: 500);
|
|
|
|
expect($nearThreshold->isNearThreshold(0.9))->toBeTrue();
|
|
expect($notNearThreshold->isNearThreshold(0.9))->toBeFalse();
|
|
});
|
|
|
|
it('determines if GC is healthy', function () {
|
|
$healthy = new GcStatus(runs: 10, collected: 50, threshold: 10000, roots: 100);
|
|
$unhealthy = new GcStatus(runs: 0, collected: 0, threshold: 1000, roots: 950);
|
|
|
|
expect($healthy->isHealthy())->toBeTrue();
|
|
expect($unhealthy->isHealthy())->toBeFalse();
|
|
});
|
|
|
|
it('converts to array', function () {
|
|
$status = new GcStatus(runs: 10, collected: 50, threshold: 10001, roots: 100);
|
|
|
|
$array = $status->toArray();
|
|
|
|
expect($array)->toHaveKeys([
|
|
'runs',
|
|
'collected',
|
|
'threshold',
|
|
'roots',
|
|
'running',
|
|
'collection_efficiency',
|
|
'threshold_utilization',
|
|
'is_healthy',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('GcResult', function () {
|
|
it('determines if GC was effective', function () {
|
|
$effective = new GcResult(
|
|
collected: 10,
|
|
duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
|
|
memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(1024),
|
|
statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
|
|
statusAfter: new GcStatus(runs: 2, collected: 10, threshold: 10001, roots: 90),
|
|
timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
|
|
);
|
|
|
|
$ineffective = new GcResult(
|
|
collected: 0,
|
|
duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
|
|
memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(0),
|
|
statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
|
|
statusAfter: new GcStatus(runs: 2, collected: 0, threshold: 10001, roots: 100),
|
|
timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
|
|
);
|
|
|
|
expect($effective->wasEffective())->toBeTrue();
|
|
expect($ineffective->wasEffective())->toBeFalse();
|
|
});
|
|
|
|
it('gets GC overhead in milliseconds', function () {
|
|
$result = new GcResult(
|
|
collected: 5,
|
|
duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(12.5),
|
|
memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(2048),
|
|
statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
|
|
statusAfter: new GcStatus(runs: 2, collected: 5, threshold: 10001, roots: 95),
|
|
timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
|
|
);
|
|
|
|
expect($result->getOverheadMs())->toBeFloat();
|
|
expect($result->getOverheadMs())->toBeGreaterThan(0);
|
|
});
|
|
|
|
it('gets memory freed in megabytes', function () {
|
|
$result = new GcResult(
|
|
collected: 5,
|
|
duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
|
|
memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(1048576), // 1 MB
|
|
statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
|
|
statusAfter: new GcStatus(runs: 2, collected: 5, threshold: 10001, roots: 95),
|
|
timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
|
|
);
|
|
|
|
expect($result->getMemoryFreedMB())->toBe(1.0);
|
|
});
|
|
|
|
it('calculates efficiency score', function () {
|
|
$result = new GcResult(
|
|
collected: 5,
|
|
duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
|
|
memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(1048576),
|
|
statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
|
|
statusAfter: new GcStatus(runs: 2, collected: 5, threshold: 10001, roots: 95),
|
|
timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
|
|
);
|
|
|
|
$score = $result->getEfficiencyScore();
|
|
|
|
expect($score)->toBeFloat();
|
|
expect($score)->toBeGreaterThanOrEqual(0);
|
|
expect($score)->toBeLessThanOrEqual(100);
|
|
});
|
|
|
|
it('gets status changes', function () {
|
|
$result = new GcResult(
|
|
collected: 5,
|
|
duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
|
|
memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(1024),
|
|
statusBefore: new GcStatus(runs: 1, collected: 10, threshold: 10001, roots: 100),
|
|
statusAfter: new GcStatus(runs: 2, collected: 15, threshold: 10001, roots: 95),
|
|
timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
|
|
);
|
|
|
|
$changes = $result->getStatusChanges();
|
|
|
|
expect($changes)->toHaveKeys(['runs_delta', 'collected_delta', 'roots_delta']);
|
|
expect($changes['runs_delta'])->toBe(1);
|
|
expect($changes['collected_delta'])->toBe(5);
|
|
expect($changes['roots_delta'])->toBe(-5);
|
|
});
|
|
|
|
it('converts to array', function () {
|
|
$result = new GcResult(
|
|
collected: 5,
|
|
duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
|
|
memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(1024),
|
|
statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
|
|
statusAfter: new GcStatus(runs: 2, collected: 5, threshold: 10001, roots: 95),
|
|
timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
|
|
);
|
|
|
|
$array = $result->toArray();
|
|
|
|
expect($array)->toHaveKeys([
|
|
'collected',
|
|
'duration_ms',
|
|
'memory_freed_bytes',
|
|
'memory_freed_mb',
|
|
'timestamp',
|
|
'was_effective',
|
|
'efficiency_score',
|
|
'status_before',
|
|
'status_after',
|
|
'status_changes',
|
|
]);
|
|
});
|
|
});
|