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,504 @@
<?php
declare(strict_types=1);
use App\Framework\Performance\NestedPerformanceTracker;
use App\Framework\Performance\PerformanceCategory;
use App\Framework\DateTime\SystemClock;
use App\Framework\DateTime\SystemHighResolutionClock;
use App\Framework\Performance\MemoryMonitor;
use App\Framework\Core\ValueObjects\Duration;
describe('NestedPerformanceTracker - Flamegraph Generation', function () {
beforeEach(function () {
$this->clock = new SystemClock();
$this->highResClock = new SystemHighResolutionClock();
$this->memoryMonitor = new MemoryMonitor();
$this->tracker = new NestedPerformanceTracker(
$this->clock,
$this->highResClock,
$this->memoryMonitor
);
});
it('generates flamegraph data for single operation', function () {
$this->tracker->measure(
'database.query',
PerformanceCategory::DATABASE,
function () {
usleep(10000); // 10ms
}
);
$flamegraph = $this->tracker->generateFlamegraph();
expect($flamegraph)->toBeArray();
expect($flamegraph)->toHaveCount(1);
expect($flamegraph[0])->toHaveKeys(['stack_trace', 'samples']);
expect($flamegraph[0]['stack_trace'])->toBe('database.query');
expect($flamegraph[0]['samples'])->toBeGreaterThan(0);
});
it('generates flamegraph data for nested operations', function () {
$this->tracker->measure(
'http.request',
PerformanceCategory::CONTROLLER,
function () {
usleep(5000); // 5ms
$this->tracker->measure(
'database.query',
PerformanceCategory::DATABASE,
function () {
usleep(3000); // 3ms
}
);
usleep(2000); // 2ms
}
);
$flamegraph = $this->tracker->generateFlamegraph();
expect($flamegraph)->toBeArray();
expect($flamegraph)->toHaveCount(2);
// Find parent operation
$parentStack = array_values(array_filter(
$flamegraph,
fn($entry) => $entry['stack_trace'] === 'http.request'
));
expect($parentStack)->toHaveCount(1);
expect($parentStack[0]['samples'])->toBeGreaterThan(0);
// Find child operation
$childStack = array_values(array_filter(
$flamegraph,
fn($entry) => $entry['stack_trace'] === 'http.request;database.query'
));
expect($childStack)->toHaveCount(1);
expect($childStack[0]['samples'])->toBeGreaterThan(0);
});
it('generates flamegraph for deeply nested operations', function () {
$this->tracker->measure(
'controller.action',
PerformanceCategory::CONTROLLER,
function () {
usleep(2000);
$this->tracker->measure(
'service.process',
PerformanceCategory::CUSTOM,
function () {
usleep(2000);
$this->tracker->measure(
'repository.find',
PerformanceCategory::DATABASE,
function () {
usleep(2000);
}
);
}
);
}
);
$flamegraph = $this->tracker->generateFlamegraph();
expect($flamegraph)->toBeArray();
expect($flamegraph)->toHaveCount(3);
// Verify stack traces
$stacks = array_column($flamegraph, 'stack_trace');
expect($stacks)->toContain('controller.action');
expect($stacks)->toContain('controller.action;service.process');
expect($stacks)->toContain('controller.action;service.process;repository.find');
});
it('excludes operations with zero self-time', function () {
$this->tracker->measure(
'parent',
PerformanceCategory::CUSTOM,
function () {
// No self-time, only child time
$this->tracker->measure(
'child',
PerformanceCategory::CUSTOM,
function () {
usleep(5000);
}
);
}
);
$flamegraph = $this->tracker->generateFlamegraph();
expect($flamegraph)->toBeArray();
// Should only have child (parent has 0 self-time)
expect($flamegraph)->toHaveCount(1);
expect($flamegraph[0]['stack_trace'])->toBe('parent;child');
});
it('handles multiple parallel operations', function () {
// First operation
$this->tracker->measure(
'operation.a',
PerformanceCategory::CUSTOM,
function () {
usleep(3000);
}
);
// Second operation
$this->tracker->measure(
'operation.b',
PerformanceCategory::CUSTOM,
function () {
usleep(2000);
}
);
$flamegraph = $this->tracker->generateFlamegraph();
expect($flamegraph)->toBeArray();
expect($flamegraph)->toHaveCount(2);
$stacks = array_column($flamegraph, 'stack_trace');
expect($stacks)->toContain('operation.a');
expect($stacks)->toContain('operation.b');
});
it('returns empty array when no operations completed', function () {
$flamegraph = $this->tracker->generateFlamegraph();
expect($flamegraph)->toBeArray();
expect($flamegraph)->toHaveCount(0);
});
it('generates flamegraph with realistic component scenario', function () {
$this->tracker->measure(
'livecomponent.lifecycle',
PerformanceCategory::CUSTOM,
function () {
usleep(1000);
$this->tracker->measure(
'livecomponent.resolve',
PerformanceCategory::CUSTOM,
function () {
usleep(2000);
}
);
$this->tracker->measure(
'livecomponent.render',
PerformanceCategory::CUSTOM,
function () {
usleep(3000);
$this->tracker->measure(
'template.process',
PerformanceCategory::VIEW,
function () {
usleep(1500);
}
);
}
);
usleep(500);
}
);
$flamegraph = $this->tracker->generateFlamegraph();
expect($flamegraph)->toBeArray();
expect($flamegraph)->toHaveCount(4);
$stacks = array_column($flamegraph, 'stack_trace');
expect($stacks)->toContain('livecomponent.lifecycle');
expect($stacks)->toContain('livecomponent.lifecycle;livecomponent.resolve');
expect($stacks)->toContain('livecomponent.lifecycle;livecomponent.render');
expect($stacks)->toContain('livecomponent.lifecycle;livecomponent.render;template.process');
});
it('samples reflect actual duration', function () {
$this->tracker->measure(
'fast.operation',
PerformanceCategory::CUSTOM,
function () {
usleep(1000); // ~1ms
}
);
$this->tracker->measure(
'slow.operation',
PerformanceCategory::CUSTOM,
function () {
usleep(10000); // ~10ms
}
);
$flamegraph = $this->tracker->generateFlamegraph();
expect($flamegraph)->toHaveCount(2);
$fast = array_values(array_filter(
$flamegraph,
fn($entry) => $entry['stack_trace'] === 'fast.operation'
))[0];
$slow = array_values(array_filter(
$flamegraph,
fn($entry) => $entry['stack_trace'] === 'slow.operation'
))[0];
// Slow operation should have more samples
expect($slow['samples'])->toBeGreaterThan($fast['samples']);
});
});
describe('NestedPerformanceTracker - Timeline Generation', function () {
beforeEach(function () {
$this->clock = new SystemClock();
$this->highResClock = new SystemHighResolutionClock();
$this->memoryMonitor = new MemoryMonitor();
$this->tracker = new NestedPerformanceTracker(
$this->clock,
$this->highResClock,
$this->memoryMonitor
);
});
it('generates timeline for single operation', function () {
$this->tracker->measure(
'test.operation',
PerformanceCategory::CUSTOM,
function () {
usleep(5000);
}
);
$timeline = $this->tracker->generateTimeline();
expect($timeline)->toBeArray();
expect($timeline)->toHaveCount(1);
$event = $timeline[0];
expect($event)->toHaveKeys([
'name',
'category',
'start_time',
'end_time',
'duration_ms',
'depth',
'operation_id',
'parent_id',
'self_time_ms',
'memory_delta_mb',
'context'
]);
expect($event['name'])->toBe('test.operation');
expect($event['category'])->toBe('custom');
expect($event['depth'])->toBe(0);
expect($event['parent_id'])->toBeNull();
expect($event['duration_ms'])->toBeGreaterThan(0);
});
it('generates timeline for nested operations', function () {
$this->tracker->measure(
'parent',
PerformanceCategory::CONTROLLER,
function () {
usleep(2000);
$this->tracker->measure(
'child',
PerformanceCategory::DATABASE,
function () {
usleep(3000);
}
);
}
);
$timeline = $this->tracker->generateTimeline();
expect($timeline)->toBeArray();
expect($timeline)->toHaveCount(2);
// Check parent event
$parent = $timeline[0];
expect($parent['name'])->toBe('parent');
expect($parent['depth'])->toBe(0);
expect($parent['parent_id'])->toBeNull();
// Check child event
$child = $timeline[1];
expect($child['name'])->toBe('child');
expect($child['depth'])->toBe(1);
expect($child['parent_id'])->not->toBeNull();
expect($child['parent_id'])->toBe($parent['operation_id']);
});
it('sorts timeline events by start time', function () {
// Create operations in specific order
$this->tracker->measure(
'operation.1',
PerformanceCategory::CUSTOM,
function () {
usleep(1000);
}
);
usleep(500); // Small gap
$this->tracker->measure(
'operation.2',
PerformanceCategory::CUSTOM,
function () {
usleep(1000);
}
);
$timeline = $this->tracker->generateTimeline();
expect($timeline)->toHaveCount(2);
// Verify chronological order
expect($timeline[0]['name'])->toBe('operation.1');
expect($timeline[1]['name'])->toBe('operation.2');
expect($timeline[0]['start_time'])->toBeLessThan($timeline[1]['start_time']);
});
it('includes memory delta in timeline', function () {
$this->tracker->measure(
'memory.test',
PerformanceCategory::CUSTOM,
function () {
// Allocate some memory
$data = array_fill(0, 10000, 'test');
usleep(1000);
}
);
$timeline = $this->tracker->generateTimeline();
expect($timeline)->toHaveCount(1);
expect($timeline[0]['memory_delta_mb'])->toBeFloat();
});
it('includes operation context in timeline', function () {
$this->tracker->measure(
'contextual.operation',
PerformanceCategory::CUSTOM,
function () {
usleep(1000);
},
['user_id' => 123, 'action' => 'test']
);
$timeline = $this->tracker->generateTimeline();
expect($timeline)->toHaveCount(1);
expect($timeline[0]['context'])->toHaveKeys(['user_id', 'action']);
expect($timeline[0]['context']['user_id'])->toBe(123);
expect($timeline[0]['context']['action'])->toBe('test');
});
it('calculates self-time correctly', function () {
$this->tracker->measure(
'parent',
PerformanceCategory::CUSTOM,
function () {
usleep(5000); // Self time: ~5ms
$this->tracker->measure(
'child',
PerformanceCategory::CUSTOM,
function () {
usleep(3000); // Child time: ~3ms
}
);
}
);
$timeline = $this->tracker->generateTimeline();
$parent = $timeline[0];
$child = $timeline[1];
// Parent total duration should be > child duration
expect($parent['duration_ms'])->toBeGreaterThan($child['duration_ms']);
// Parent self-time should be less than total duration
expect($parent['self_time_ms'])->toBeLessThan($parent['duration_ms']);
// Child self-time should equal its duration (no sub-operations)
expect($child['self_time_ms'])->toBe($child['duration_ms']);
});
it('handles empty tracker gracefully', function () {
$timeline = $this->tracker->generateTimeline();
expect($timeline)->toBeArray();
expect($timeline)->toHaveCount(0);
});
it('generates complete timeline for component lifecycle', function () {
$this->tracker->measure(
'livecomponent.lifecycle',
PerformanceCategory::CUSTOM,
function () {
$this->tracker->measure(
'livecomponent.resolve',
PerformanceCategory::CUSTOM,
function () {
usleep(2000);
},
['phase' => 'initialization']
);
$this->tracker->measure(
'livecomponent.render',
PerformanceCategory::CUSTOM,
function () {
usleep(3000);
},
['cached' => false]
);
$this->tracker->measure(
'livecomponent.handle',
PerformanceCategory::CUSTOM,
function () {
usleep(1500);
},
['action' => 'submit']
);
},
['component_id' => 'user-profile']
);
$timeline = $this->tracker->generateTimeline();
expect($timeline)->toHaveCount(4);
// Verify all phases present
$names = array_column($timeline, 'name');
expect($names)->toContain('livecomponent.lifecycle');
expect($names)->toContain('livecomponent.resolve');
expect($names)->toContain('livecomponent.render');
expect($names)->toContain('livecomponent.handle');
// Verify context propagation
$lifecycle = $timeline[0];
expect($lifecycle['context'])->toHaveKey('component_id');
expect($lifecycle['context']['component_id'])->toBe('user-profile');
});
});