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.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,223 @@
<?php
declare(strict_types=1);
use App\Framework\LiveComponents\Cache\CacheMetricsCollector;
use App\Framework\LiveComponents\Cache\CacheType;
describe('CacheMetricsCollector', function () {
it('initializes with empty metrics for all cache types', function () {
$collector = new CacheMetricsCollector();
$stateMetrics = $collector->getMetrics(CacheType::STATE);
$slotMetrics = $collector->getMetrics(CacheType::SLOT);
$templateMetrics = $collector->getMetrics(CacheType::TEMPLATE);
expect($stateMetrics->hits)->toBe(0)
->and($slotMetrics->hits)->toBe(0)
->and($templateMetrics->hits)->toBe(0);
});
it('records hits for specific cache type', function () {
$collector = new CacheMetricsCollector();
$collector->recordHit(CacheType::STATE, 0.5);
$collector->recordHit(CacheType::STATE, 0.6);
$metrics = $collector->getMetrics(CacheType::STATE);
expect($metrics->hits)->toBe(2)
->and($metrics->misses)->toBe(0);
});
it('records misses for specific cache type', function () {
$collector = new CacheMetricsCollector();
$collector->recordMiss(CacheType::SLOT, 1.0);
$collector->recordMiss(CacheType::SLOT, 1.2);
$metrics = $collector->getMetrics(CacheType::SLOT);
expect($metrics->hits)->toBe(0)
->and($metrics->misses)->toBe(2);
});
it('records invalidations for specific cache type', function () {
$collector = new CacheMetricsCollector();
$collector->recordInvalidation(CacheType::TEMPLATE);
$collector->recordInvalidation(CacheType::TEMPLATE);
$metrics = $collector->getMetrics(CacheType::TEMPLATE);
expect($metrics->invalidations)->toBe(2);
});
it('updates cache size for specific cache type', function () {
$collector = new CacheMetricsCollector();
$collector->updateSize(CacheType::STATE, 150);
$metrics = $collector->getMetrics(CacheType::STATE);
expect($metrics->totalSize)->toBe(150);
});
it('maintains separate metrics for different cache types', function () {
$collector = new CacheMetricsCollector();
$collector->recordHit(CacheType::STATE, 0.5);
$collector->recordMiss(CacheType::SLOT, 1.0);
$collector->recordInvalidation(CacheType::TEMPLATE);
expect($collector->getMetrics(CacheType::STATE)->hits)->toBe(1)
->and($collector->getMetrics(CacheType::SLOT)->misses)->toBe(1)
->and($collector->getMetrics(CacheType::TEMPLATE)->invalidations)->toBe(1);
});
it('returns all metrics', function () {
$collector = new CacheMetricsCollector();
$collector->recordHit(CacheType::STATE, 0.5);
$collector->recordHit(CacheType::SLOT, 0.6);
$allMetrics = $collector->getAllMetrics();
expect($allMetrics)->toBeArray()
->and($allMetrics)->toHaveKey('state')
->and($allMetrics)->toHaveKey('slot')
->and($allMetrics)->toHaveKey('template');
});
it('calculates aggregate metrics across all caches', function () {
$collector = new CacheMetricsCollector();
// State: 2 hits
$collector->recordHit(CacheType::STATE, 0.5);
$collector->recordHit(CacheType::STATE, 0.6);
// Slot: 1 hit, 1 miss
$collector->recordHit(CacheType::SLOT, 0.7);
$collector->recordMiss(CacheType::SLOT, 1.0);
// Template: 1 miss
$collector->recordMiss(CacheType::TEMPLATE, 1.2);
$aggregate = $collector->getAggregateMetrics();
expect($aggregate->cacheType)->toBe(CacheType::MERGED)
->and($aggregate->hits)->toBe(3)
->and($aggregate->misses)->toBe(2)
->and($aggregate->getTotalOperations())->toBe(5);
});
it('generates comprehensive summary', function () {
$collector = new CacheMetricsCollector();
$collector->recordHit(CacheType::STATE, 0.5);
$collector->recordMiss(CacheType::SLOT, 1.0);
$summary = $collector->getSummary();
expect($summary)->toHaveKey('overall')
->and($summary)->toHaveKey('by_type')
->and($summary)->toHaveKey('performance_assessment')
->and($summary['by_type'])->toHaveKey('state')
->and($summary['by_type'])->toHaveKey('slot')
->and($summary['by_type'])->toHaveKey('template');
});
it('assesses performance against targets', function () {
$collector = new CacheMetricsCollector();
// State: 80% hit rate (target: 70%)
for ($i = 0; $i < 8; $i++) {
$collector->recordHit(CacheType::STATE, 0.5);
}
for ($i = 0; $i < 2; $i++) {
$collector->recordMiss(CacheType::STATE, 1.0);
}
$assessment = $collector->assessPerformance();
expect($assessment['state_cache']['meets_target'])->toBeTrue()
->and($assessment['state_cache']['grade'])->toBe('B');
});
it('detects performance issues when targets not met', function () {
$collector = new CacheMetricsCollector();
// State: 50% hit rate (below 70% target)
$collector->recordHit(CacheType::STATE, 0.5);
$collector->recordMiss(CacheType::STATE, 1.0);
expect($collector->hasPerformanceIssues())->toBeTrue();
});
it('generates performance warnings for underperforming caches', function () {
$collector = new CacheMetricsCollector();
// State: 50% hit rate (below 70% target)
$collector->recordHit(CacheType::STATE, 0.5);
$collector->recordMiss(CacheType::STATE, 1.0);
$warnings = $collector->getPerformanceWarnings();
expect($warnings)->toBeArray()
->and($warnings)->not->toBeEmpty()
->and($warnings[0])->toContain('State cache hit rate');
});
it('returns no warnings when all caches meet targets', function () {
$collector = new CacheMetricsCollector();
// State: 90% hit rate (exceeds 70% target)
for ($i = 0; $i < 9; $i++) {
$collector->recordHit(CacheType::STATE, 0.5);
}
$collector->recordMiss(CacheType::STATE, 1.0);
// Slot: 80% hit rate (exceeds 60% target)
for ($i = 0; $i < 8; $i++) {
$collector->recordHit(CacheType::SLOT, 0.5);
}
for ($i = 0; $i < 2; $i++) {
$collector->recordMiss(CacheType::SLOT, 1.0);
}
// Template: 90% hit rate (exceeds 80% target)
for ($i = 0; $i < 9; $i++) {
$collector->recordHit(CacheType::TEMPLATE, 0.5);
}
$collector->recordMiss(CacheType::TEMPLATE, 1.0);
expect($collector->hasPerformanceIssues())->toBeFalse()
->and($collector->getPerformanceWarnings())->toBeEmpty();
});
it('exports metrics with timestamp', function () {
$collector = new CacheMetricsCollector();
$collector->recordHit(CacheType::STATE, 0.5);
$export = $collector->export();
expect($export)->toHaveKey('timestamp')
->and($export)->toHaveKey('metrics')
->and($export['timestamp'])->toBeInt();
});
it('resets all metrics', function () {
$collector = new CacheMetricsCollector();
$collector->recordHit(CacheType::STATE, 0.5);
$collector->recordHit(CacheType::SLOT, 0.6);
$collector->recordHit(CacheType::TEMPLATE, 0.7);
$collector->reset();
expect($collector->getMetrics(CacheType::STATE)->hits)->toBe(0)
->and($collector->getMetrics(CacheType::SLOT)->hits)->toBe(0)
->and($collector->getMetrics(CacheType::TEMPLATE)->hits)->toBe(0);
});
});

View File

@@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\LiveComponents\Cache\CacheMetrics;
use App\Framework\LiveComponents\Cache\CacheType;
describe('CacheMetrics', function () {
it('creates empty metrics with zero values', function () {
$metrics = CacheMetrics::empty(CacheType::STATE);
expect($metrics->cacheType)->toBe(CacheType::STATE)
->and($metrics->hits)->toBe(0)
->and($metrics->misses)->toBe(0)
->and($metrics->hitRate->getValue())->toBe(0.0)
->and($metrics->missRate->getValue())->toBe(0.0)
->and($metrics->getTotalOperations())->toBe(0);
});
it('records cache hit and updates metrics', function () {
$metrics = CacheMetrics::empty(CacheType::STATE);
$updated = $metrics->withHit(0.5);
expect($updated->hits)->toBe(1)
->and($updated->misses)->toBe(0)
->and($updated->hitRate->getValue())->toBe(100.0)
->and($updated->missRate->getValue())->toBe(0.0)
->and($updated->averageLookupTimeMs)->toBe(0.5);
});
it('records cache miss and updates metrics', function () {
$metrics = CacheMetrics::empty(CacheType::STATE);
$updated = $metrics->withMiss(1.2);
expect($updated->hits)->toBe(0)
->and($updated->misses)->toBe(1)
->and($updated->hitRate->getValue())->toBe(0.0)
->and($updated->missRate->getValue())->toBe(100.0)
->and($updated->averageLookupTimeMs)->toBe(1.2);
});
it('calculates correct hit rate with mixed hits and misses', function () {
$metrics = CacheMetrics::empty(CacheType::STATE)
->withHit(0.5)
->withHit(0.6)
->withHit(0.4)
->withMiss(1.0);
expect($metrics->hits)->toBe(3)
->and($metrics->misses)->toBe(1)
->and($metrics->hitRate->getValue())->toBe(75.0)
->and($metrics->missRate->getValue())->toBe(25.0)
->and($metrics->getTotalOperations())->toBe(4);
});
it('calculates weighted average lookup time', function () {
$metrics = CacheMetrics::empty(CacheType::STATE)
->withHit(0.5) // Avg: 0.5
->withHit(1.5); // Avg: (0.5 + 1.5) / 2 = 1.0
expect($metrics->averageLookupTimeMs)->toBe(1.0);
});
it('records cache invalidations', function () {
$metrics = CacheMetrics::empty(CacheType::STATE)
->withInvalidation()
->withInvalidation();
expect($metrics->invalidations)->toBe(2);
});
it('updates cache size', function () {
$metrics = CacheMetrics::empty(CacheType::STATE)
->withSize(150);
expect($metrics->totalSize)->toBe(150);
});
it('checks performance target with Percentage', function () {
$metrics = CacheMetrics::empty(CacheType::STATE)
->withHit(0.5)
->withHit(0.6)
->withHit(0.7)
->withMiss(1.0); // 75% hit rate
$target = Percentage::from(70.0);
expect($metrics->meetsPerformanceTarget($target))->toBeTrue();
});
it('returns correct performance grade', function () {
expect(
CacheMetrics::empty(CacheType::STATE)
->withHit(0.5)->withHit(0.5)->withHit(0.5)->withHit(0.5)
->withHit(0.5)->withHit(0.5)->withHit(0.5)->withHit(0.5)
->withHit(0.5)->withMiss(1.0) // 90% hit rate
->getPerformanceGrade()
)->toBe('A');
expect(
CacheMetrics::empty(CacheType::STATE)
->withHit(0.5)->withHit(0.5)->withHit(0.5)->withHit(0.5)
->withMiss(1.0)->withMiss(1.0) // 66.67% hit rate
->getPerformanceGrade()
)->toBe('D');
});
it('converts to array for reporting', function () {
$metrics = CacheMetrics::empty(CacheType::STATE)
->withHit(0.5)
->withMiss(1.0);
$array = $metrics->toArray();
expect($array)->toHaveKey('cache_type')
->and($array)->toHaveKey('hits')
->and($array)->toHaveKey('misses')
->and($array)->toHaveKey('hit_rate')
->and($array)->toHaveKey('miss_rate')
->and($array)->toHaveKey('average_lookup_time_ms')
->and($array)->toHaveKey('performance_grade')
->and($array['cache_type'])->toBe('state')
->and($array['hits'])->toBe(1)
->and($array['misses'])->toBe(1);
});
it('merges metrics from multiple sources', function () {
$metrics1 = CacheMetrics::empty(CacheType::STATE)
->withHit(0.5)
->withHit(0.6);
$metrics2 = CacheMetrics::empty(CacheType::SLOT)
->withHit(0.7)
->withMiss(1.0);
$merged = CacheMetrics::merge($metrics1, $metrics2);
expect($merged->cacheType)->toBe(CacheType::MERGED)
->and($merged->hits)->toBe(3)
->and($merged->misses)->toBe(1)
->and($merged->hitRate->getValue())->toBe(75.0);
});
it('handles empty merge gracefully', function () {
$merged = CacheMetrics::merge();
expect($merged->cacheType)->toBe(CacheType::MERGED)
->and($merged->hits)->toBe(0)
->and($merged->misses)->toBe(0);
});
it('preserves immutability when recording operations', function () {
$original = CacheMetrics::empty(CacheType::STATE);
$updated = $original->withHit(0.5);
expect($original->hits)->toBe(0)
->and($updated->hits)->toBe(1)
->and($original)->not->toBe($updated);
});
});