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

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Performance\Contracts;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use RuntimeException;
interface PerformanceReporterInterface

View File

@@ -0,0 +1,191 @@
<?php
declare(strict_types=1);
namespace App\Framework\Performance;
use App\Framework\Performance\ValueObjects\GcStatus;
use App\Framework\Performance\ValueObjects\GcResult;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Garbage Collection Monitor
*
* Framework-consistent wrapper for PHP's garbage collection system.
* Provides structured Value Objects instead of raw arrays and integrates
* with the Performance monitoring ecosystem.
*
* Features:
* - GC status tracking with Value Objects
* - GC cycle execution and measurement
* - Integration with MemoryMonitor for complete memory analysis
* - Performance metrics for GC overhead
*
* Usage:
* ```php
* $gcMonitor = new GarbageCollectionMonitor();
* $status = $gcMonitor->getStatus();
* $result = $gcMonitor->forceCollection();
* ```
*/
final readonly class GarbageCollectionMonitor
{
/**
* Get current garbage collection status
*/
public function getStatus(): GcStatus
{
$status = gc_status();
return new GcStatus(
runs: $status['runs'] ?? 0,
collected: $status['collected'] ?? 0,
threshold: $status['threshold'] ?? 10001,
roots: $status['roots'] ?? 0,
running: $status['running'] ?? false
);
}
/**
* Get number of GC runs
*/
public function getRuns(): int
{
return gc_status()['runs'] ?? 0;
}
/**
* Get number of collected cycles
*/
public function getCollected(): int
{
return gc_status()['collected'] ?? 0;
}
/**
* Get current GC threshold
*/
public function getThreshold(): int
{
return gc_status()['threshold'] ?? 10001;
}
/**
* Check if GC is currently running
*/
public function isRunning(): bool
{
return gc_status()['running'] ?? false;
}
/**
* Force garbage collection cycle and measure performance
*
* Returns GcResult with:
* - Collected cycles
* - Execution duration
* - Memory freed
* - Before/after GC status
*/
public function forceCollection(): GcResult
{
$memoryMonitor = new MemoryMonitor();
$startTime = Timestamp::now();
$memoryBefore = $memoryMonitor->getCurrentMemory();
$statusBefore = $this->getStatus();
// Force GC cycle
$collected = gc_collect_cycles();
$endTime = Timestamp::now();
$memoryAfter = $memoryMonitor->getCurrentMemory();
$statusAfter = $this->getStatus();
$duration = $startTime->diff($endTime);
$memoryFreed = $memoryBefore->subtract($memoryAfter);
return new GcResult(
collected: $collected,
duration: $duration,
memoryFreed: $memoryFreed,
statusBefore: $statusBefore,
statusAfter: $statusAfter,
timestamp: $startTime
);
}
/**
* Enable garbage collection
*/
public function enable(): void
{
gc_enable();
}
/**
* Disable garbage collection
*/
public function disable(): void
{
gc_disable();
}
/**
* Check if garbage collection is enabled
*/
public function isEnabled(): bool
{
return gc_enabled();
}
/**
* Get GC efficiency metrics
*
* Returns array with:
* - collection_rate: cycles collected per run
* - threshold_ratio: roots to threshold ratio
* - is_healthy: whether GC is performing well
*/
public function getEfficiencyMetrics(): array
{
$status = $this->getStatus();
$collectionRate = $status->runs > 0
? $status->collected / $status->runs
: 0.0;
$thresholdRatio = $status->threshold > 0
? $status->roots / $status->threshold
: 0.0;
// GC is healthy if:
// - Collection rate is reasonable (collecting cycles)
// - Threshold ratio is below 90% (not constantly hitting threshold)
$isHealthy = $collectionRate > 0 && $thresholdRatio < 0.9;
return [
'collection_rate' => round($collectionRate, 2),
'threshold_ratio' => round($thresholdRatio, 2),
'is_healthy' => $isHealthy,
'runs' => $status->runs,
'collected' => $status->collected,
'roots' => $status->roots,
'threshold' => $status->threshold,
];
}
/**
* Take GC snapshot for comparison
*
* Useful for tracking GC behavior over time
*/
public function takeSnapshot(string $label = 'checkpoint'): array
{
return [
'label' => $label,
'timestamp' => Timestamp::now()->toIso8601(),
'status' => $this->getStatus()->toArray(),
'efficiency' => $this->getEfficiencyMetrics(),
];
}
}

View File

@@ -0,0 +1,335 @@
<?php
declare(strict_types=1);
namespace App\Framework\Performance;
use App\Framework\Core\ValueObjects\Byte;
/**
* Memory Profiler for detailed memory usage analysis
*
* Provides advanced memory profiling capabilities:
* - Memory leak detection
* - Peak memory tracking
* - Memory allocation hotspot identification
* - Memory efficiency scoring
*/
final readonly class MemoryProfiler
{
public function __construct(
private NestedPerformanceTracker $performanceTracker
) {}
/**
* Detect potential memory leaks
*
* Analyzes memory growth patterns to identify leaks
*/
public function detectMemoryLeaks(float $thresholdMb = 5.0): array
{
$timeline = $this->performanceTracker->generateTimeline();
$leaks = [];
$cumulativeMemory = 0.0;
foreach ($timeline as $event) {
$cumulativeMemory += $event['memory_delta_mb'];
// Flag operations that allocate significant memory
if ($event['memory_delta_mb'] > $thresholdMb) {
$leaks[] = [
'operation' => $event['name'],
'memory_allocated_mb' => $event['memory_delta_mb'],
'cumulative_memory_mb' => $cumulativeMemory,
'category' => $event['category'],
'context' => $event['context'] ?? []
];
}
}
return [
'potential_leaks' => $leaks,
'total_memory_growth_mb' => $cumulativeMemory,
'leak_count' => count($leaks),
'threshold_mb' => $thresholdMb
];
}
/**
* Get memory allocation hotspots
*
* Identifies operations with highest memory usage
*/
public function getMemoryHotspots(int $limit = 10): array
{
$timeline = $this->performanceTracker->generateTimeline();
// Sort by memory delta descending
usort($timeline, fn($a, $b) => $b['memory_delta_mb'] <=> $a['memory_delta_mb']);
$hotspots = array_slice($timeline, 0, $limit);
return array_map(function ($event) {
return [
'operation' => $event['name'],
'memory_mb' => $event['memory_delta_mb'],
'duration_ms' => $event['duration_ms'],
'memory_per_ms' => $event['duration_ms'] > 0
? $event['memory_delta_mb'] / $event['duration_ms']
: 0,
'category' => $event['category'],
'depth' => $event['depth']
];
}, $hotspots);
}
/**
* Calculate memory efficiency score
*
* Scores based on memory usage vs. execution time
*/
public function calculateEfficiencyScore(): array
{
$timeline = $this->performanceTracker->generateTimeline();
if (empty($timeline)) {
return [
'score' => 100,
'rating' => 'N/A',
'total_memory_mb' => 0,
'total_time_ms' => 0
];
}
$totalMemory = array_sum(array_column($timeline, 'memory_delta_mb'));
$totalTime = array_sum(array_column($timeline, 'duration_ms'));
// Memory per millisecond (lower is better)
$memoryPerMs = $totalTime > 0 ? $totalMemory / $totalTime : 0;
// Score: 100 = excellent (0 memory), 0 = poor (>1MB per ms)
$score = max(0, min(100, 100 - ($memoryPerMs * 100)));
$rating = match (true) {
$score >= 90 => 'Excellent',
$score >= 75 => 'Good',
$score >= 50 => 'Fair',
$score >= 25 => 'Poor',
default => 'Critical'
};
return [
'score' => round($score, 2),
'rating' => $rating,
'total_memory_mb' => round($totalMemory, 2),
'total_time_ms' => round($totalTime, 2),
'memory_per_ms' => round($memoryPerMs, 4)
];
}
/**
* Generate memory usage report
*
* Comprehensive memory analysis report
*/
public function generateMemoryReport(): array
{
$timeline = $this->performanceTracker->generateTimeline();
if (empty($timeline)) {
return [
'summary' => [
'total_operations' => 0,
'total_memory_mb' => 0,
'avg_memory_per_operation_mb' => 0,
'peak_memory_mb' => 0
],
'by_category' => [],
'hotspots' => [],
'efficiency' => $this->calculateEfficiencyScore(),
'leaks' => $this->detectMemoryLeaks()
];
}
$totalMemory = array_sum(array_column($timeline, 'memory_delta_mb'));
$peakMemory = max(array_column($timeline, 'memory_delta_mb'));
// Group by category
$byCategory = [];
foreach ($timeline as $event) {
$category = $event['category'];
if (!isset($byCategory[$category])) {
$byCategory[$category] = [
'operations' => 0,
'total_memory_mb' => 0,
'avg_memory_mb' => 0,
'peak_memory_mb' => 0
];
}
$byCategory[$category]['operations']++;
$byCategory[$category]['total_memory_mb'] += $event['memory_delta_mb'];
$byCategory[$category]['peak_memory_mb'] = max(
$byCategory[$category]['peak_memory_mb'],
$event['memory_delta_mb']
);
}
// Calculate averages
foreach ($byCategory as $category => &$data) {
$data['avg_memory_mb'] = $data['operations'] > 0
? $data['total_memory_mb'] / $data['operations']
: 0;
}
return [
'summary' => [
'total_operations' => count($timeline),
'total_memory_mb' => round($totalMemory, 2),
'avg_memory_per_operation_mb' => round($totalMemory / count($timeline), 4),
'peak_memory_mb' => round($peakMemory, 2)
],
'by_category' => array_map(function ($data) {
return [
'operations' => $data['operations'],
'total_memory_mb' => round($data['total_memory_mb'], 2),
'avg_memory_mb' => round($data['avg_memory_mb'], 4),
'peak_memory_mb' => round($data['peak_memory_mb'], 2)
];
}, $byCategory),
'hotspots' => $this->getMemoryHotspots(5),
'efficiency' => $this->calculateEfficiencyScore(),
'leaks' => $this->detectMemoryLeaks()
];
}
/**
* Track memory over time
*
* Provides cumulative memory tracking for trend analysis
*/
public function trackMemoryOverTime(): array
{
$timeline = $this->performanceTracker->generateTimeline();
$memoryTrack = [];
$cumulativeMemory = 0.0;
$timestamp = 0.0;
foreach ($timeline as $event) {
$timestamp = $event['end_time'];
$cumulativeMemory += $event['memory_delta_mb'];
$memoryTrack[] = [
'timestamp' => $timestamp,
'operation' => $event['name'],
'delta_mb' => $event['memory_delta_mb'],
'cumulative_mb' => $cumulativeMemory
];
}
return [
'tracking_points' => $memoryTrack,
'final_cumulative_mb' => round($cumulativeMemory, 2),
'trend' => $this->analyzeTrend($memoryTrack)
];
}
/**
* Analyze memory trend
*
* Determines if memory usage is growing, stable, or decreasing
*/
private function analyzeTrend(array $memoryTrack): string
{
if (count($memoryTrack) < 2) {
return 'insufficient_data';
}
$startMemory = $memoryTrack[0]['cumulative_mb'];
$endMemory = $memoryTrack[count($memoryTrack) - 1]['cumulative_mb'];
$growthRate = $endMemory - $startMemory;
return match (true) {
$growthRate > 5.0 => 'rapidly_growing',
$growthRate > 1.0 => 'growing',
$growthRate > -1.0 && $growthRate < 1.0 => 'stable',
$growthRate > -5.0 => 'decreasing',
default => 'rapidly_decreasing'
};
}
/**
* Get operations exceeding memory budget
*
* Identifies operations that exceed specified memory limits
*/
public function getMemoryBudgetViolations(array $budgets): array
{
$timeline = $this->performanceTracker->generateTimeline();
$violations = [];
foreach ($timeline as $event) {
foreach ($budgets as $pattern => $budgetMb) {
if (str_contains($event['name'], $pattern)) {
if ($event['memory_delta_mb'] > $budgetMb) {
$violations[] = [
'operation' => $event['name'],
'memory_mb' => $event['memory_delta_mb'],
'budget_mb' => $budgetMb,
'exceeded_by_mb' => $event['memory_delta_mb'] - $budgetMb,
'percentage' => (($event['memory_delta_mb'] - $budgetMb) / $budgetMb) * 100
];
}
}
}
}
return [
'violations' => $violations,
'violation_count' => count($violations),
'budgets_checked' => count($budgets)
];
}
/**
* Compare memory usage between operations
*
* Useful for A/B testing memory optimizations
*/
public function compareOperations(string $operation1, string $operation2): array
{
$timeline = $this->performanceTracker->generateTimeline();
$op1Data = array_filter($timeline, fn($e) => str_contains($e['name'], $operation1));
$op2Data = array_filter($timeline, fn($e) => str_contains($e['name'], $operation2));
$op1Memory = !empty($op1Data) ? array_sum(array_column($op1Data, 'memory_delta_mb')) : 0;
$op2Memory = !empty($op2Data) ? array_sum(array_column($op2Data, 'memory_delta_mb')) : 0;
$difference = $op2Memory - $op1Memory;
$percentageDiff = $op1Memory > 0 ? ($difference / $op1Memory) * 100 : 0;
return [
'operation_1' => [
'name' => $operation1,
'memory_mb' => round($op1Memory, 2),
'executions' => count($op1Data)
],
'operation_2' => [
'name' => $operation2,
'memory_mb' => round($op2Memory, 2),
'executions' => count($op2Data)
],
'comparison' => [
'difference_mb' => round($difference, 2),
'percentage_diff' => round($percentageDiff, 2),
'winner' => $op1Memory < $op2Memory ? $operation1 : $operation2
]
];
}
}

View File

@@ -410,6 +410,121 @@ final class NestedPerformanceTracker
}
}
/**
* Generate Flamegraph data in Brendan Gregg format
*
* Returns array of stack traces with sample counts for flamegraph visualization
*
* Format: ['stack_trace' => 'func1;func2;func3', 'samples' => duration_ms]
*
* @return array<int, array{stack_trace: string, samples: float}>
*/
public function generateFlamegraph(): array
{
$flamegraphData = [];
foreach ($this->completedOperations as $operation) {
$flamegraphData = $this->collectFlamegraphData($operation, [], $flamegraphData);
}
return $flamegraphData;
}
/**
* Generate Timeline data for visualization
*
* Returns array of timeline events with start/end times
*
* @return array<int, array{
* name: string,
* category: string,
* start_time: float,
* end_time: float,
* duration_ms: float,
* depth: int,
* operation_id: string,
* parent_id: string|null,
* self_time_ms: float,
* memory_delta_mb: float,
* context: array<string, mixed>
* }>
*/
public function generateTimeline(): array
{
$timeline = [];
foreach ($this->completedOperations as $operation) {
$timeline = $this->collectTimelineEvents($operation, $timeline);
}
// Sort by start time
usort($timeline, fn($a, $b) => $a['start_time'] <=> $b['start_time']);
return $timeline;
}
/**
* Recursively collect flamegraph data from nested measurements
*/
private function collectFlamegraphData(
NestedMeasurement $measurement,
array $parentStack,
array $flamegraphData
): array {
// Build stack trace
$currentStack = array_merge($parentStack, [$measurement->name]);
$stackTrace = implode(';', $currentStack);
// Add to flamegraph data with duration as samples
$samples = $measurement->getSelfTime()->toMilliseconds();
if ($samples > 0) {
$flamegraphData[] = [
'stack_trace' => $stackTrace,
'samples' => $samples,
];
}
// Recursively process children
foreach ($measurement->children as $child) {
$flamegraphData = $this->collectFlamegraphData($child, $currentStack, $flamegraphData);
}
return $flamegraphData;
}
/**
* Recursively collect timeline events from nested measurements
*/
private function collectTimelineEvents(
NestedMeasurement $measurement,
array $timeline
): array {
if (!$measurement->duration || !$measurement->startTime || !$measurement->endTime) {
return $timeline;
}
$timeline[] = [
'name' => $measurement->name,
'category' => $measurement->category->value,
'start_time' => $measurement->startTime->toFloat(),
'end_time' => $measurement->endTime->toFloat(),
'duration_ms' => $measurement->duration->toMilliseconds(),
'depth' => $measurement->depth,
'operation_id' => $measurement->operationId,
'parent_id' => $measurement->parentId,
'self_time_ms' => $measurement->getSelfTime()->toMilliseconds(),
'memory_delta_mb' => $measurement->getMemoryUsed()->toMegabytes(),
'context' => $measurement->context,
];
// Recursively process children
foreach ($measurement->children as $child) {
$timeline = $this->collectTimelineEvents($child, $timeline);
}
return $timeline;
}
/**
* Debug info
*/

View File

@@ -6,7 +6,7 @@ namespace App\Framework\Performance;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\SystemClock;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
use App\Framework\Performance\Contracts\PerformanceReporterInterface;
use App\Framework\Performance\ValueObjects\CategoryMetrics;

View File

@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace App\Framework\Performance\ValueObjects;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* GC Result Value Object
*
* Immutable result of a garbage collection cycle execution.
* Captures performance metrics and memory impact.
*/
final readonly class GcResult
{
/**
* @param int $collected Number of cycles collected
* @param Duration $duration Time taken for GC cycle
* @param Byte $memoryFreed Memory freed by GC
* @param GcStatus $statusBefore GC status before collection
* @param GcStatus $statusAfter GC status after collection
* @param Timestamp $timestamp When GC was executed
*/
public function __construct(
public int $collected,
public Duration $duration,
public Byte $memoryFreed,
public GcStatus $statusBefore,
public GcStatus $statusAfter,
public Timestamp $timestamp
) {
}
/**
* Check if GC was effective
*
* GC is considered effective if it collected cycles
* and freed a reasonable amount of memory.
*/
public function wasEffective(): bool
{
return $this->collected > 0 && $this->memoryFreed->toBytes() > 0;
}
/**
* Get GC overhead in milliseconds
*/
public function getOverheadMs(): float
{
return $this->duration->toMilliseconds();
}
/**
* Get memory freed in megabytes
*/
public function getMemoryFreedMB(): float
{
return $this->memoryFreed->toMegabytes(2);
}
/**
* Calculate GC efficiency score (0-100)
*
* Based on:
* - Cycles collected (50%)
* - Memory freed (30%)
* - Execution speed (20%)
*/
public function getEfficiencyScore(): float
{
$cycleScore = min(100, $this->collected * 10); // 10 cycles = 100 score
$memoryScore = min(100, $this->getMemoryFreedMB() * 10); // 10MB = 100 score
$speedScore = $this->getOverheadMs() < 10 ? 100 : (100 - ($this->getOverheadMs() * 5));
return ($cycleScore * 0.5) + ($memoryScore * 0.3) + ($speedScore * 0.2);
}
/**
* Get status changes
*/
public function getStatusChanges(): array
{
return [
'runs_delta' => $this->statusAfter->runs - $this->statusBefore->runs,
'collected_delta' => $this->statusAfter->collected - $this->statusBefore->collected,
'roots_delta' => $this->statusAfter->roots - $this->statusBefore->roots,
];
}
/**
* Convert to array for serialization
*/
public function toArray(): array
{
return [
'collected' => $this->collected,
'duration_ms' => $this->getOverheadMs(),
'memory_freed_bytes' => $this->memoryFreed->toBytes(),
'memory_freed_mb' => $this->getMemoryFreedMB(),
'timestamp' => $this->timestamp->toIso8601(),
'was_effective' => $this->wasEffective(),
'efficiency_score' => round($this->getEfficiencyScore(), 2),
'status_before' => $this->statusBefore->toArray(),
'status_after' => $this->statusAfter->toArray(),
'status_changes' => $this->getStatusChanges(),
];
}
}

View File

@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace App\Framework\Performance\ValueObjects;
/**
* GC Status Value Object
*
* Immutable representation of PHP garbage collection status.
* Wraps gc_status() array into type-safe Value Object.
*/
final readonly class GcStatus
{
/**
* @param int $runs Number of times the GC algorithm has run
* @param int $collected Number of cycles collected
* @param int $threshold GC threshold (number of root buffers before GC runs)
* @param int $roots Current number of root buffers
* @param bool $running Whether GC is currently running
*/
public function __construct(
public int $runs,
public int $collected,
public int $threshold,
public int $roots,
public bool $running = false
) {
}
/**
* Calculate collection efficiency
*
* Returns ratio of collected cycles to runs.
* Higher values indicate more effective GC.
*/
public function getCollectionEfficiency(): float
{
if ($this->runs === 0) {
return 0.0;
}
return $this->collected / $this->runs;
}
/**
* Calculate threshold utilization
*
* Returns ratio of current roots to threshold.
* Values close to 1.0 indicate GC is about to run.
*/
public function getThresholdUtilization(): float
{
if ($this->threshold === 0) {
return 0.0;
}
return $this->roots / $this->threshold;
}
/**
* Check if GC threshold is close to being reached
*/
public function isNearThreshold(float $percentage = 0.9): bool
{
return $this->getThresholdUtilization() >= $percentage;
}
/**
* Check if GC is performing well
*
* GC is considered healthy if:
* - It has run at least once
* - Collection efficiency is reasonable (> 0)
* - Not constantly near threshold
*/
public function isHealthy(): bool
{
return $this->runs > 0
&& $this->getCollectionEfficiency() > 0
&& !$this->isNearThreshold(0.95);
}
/**
* Convert to array for serialization
*/
public function toArray(): array
{
return [
'runs' => $this->runs,
'collected' => $this->collected,
'threshold' => $this->threshold,
'roots' => $this->roots,
'running' => $this->running,
'collection_efficiency' => $this->getCollectionEfficiency(),
'threshold_utilization' => $this->getThresholdUtilization(),
'is_healthy' => $this->isHealthy(),
];
}
/**
* Create from gc_status() array
*/
public static function fromGcStatus(array $status): self
{
return new self(
runs: $status['runs'] ?? 0,
collected: $status['collected'] ?? 0,
threshold: $status['threshold'] ?? 10001,
roots: $status['roots'] ?? 0,
running: $status['running'] ?? false
);
}
/**
* Get current GC status
*/
public static function current(): self
{
return self::fromGcStatus(gc_status());
}
}