- 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.
858 lines
24 KiB
Markdown
858 lines
24 KiB
Markdown
# Performance Profiling
|
|
|
|
Comprehensive Performance Profiling System mit Flamegraph-Visualisierung und Timeline-Analyse.
|
|
|
|
## Übersicht
|
|
|
|
Das Framework bietet ein leistungsstarkes Performance Profiling System mit:
|
|
- **Nested Performance Tracking**: Hierarchische Performance-Messungen
|
|
- **Flamegraph Visualization**: Brendan Gregg Flamegraph Format
|
|
- **Timeline Analysis**: Chronologische Event-Visualisierung
|
|
- **Memory Tracking**: Speicherverbrauch-Überwachung
|
|
- **LiveComponent Integration**: Spezialisiertes Component-Profiling
|
|
|
|
## NestedPerformanceTracker
|
|
|
|
### Grundlegende Verwendung
|
|
|
|
```php
|
|
use App\Framework\Performance\NestedPerformanceTracker;
|
|
use App\Framework\Performance\PerformanceCategory;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
|
|
// Tracker initialisieren
|
|
$tracker = $container->get(NestedPerformanceTracker::class);
|
|
|
|
// Einfache Operation messen
|
|
$result = $tracker->measure(
|
|
'database.query',
|
|
PerformanceCategory::DATABASE,
|
|
function () use ($userRepository) {
|
|
return $userRepository->findAll();
|
|
}
|
|
);
|
|
|
|
// Mit Context-Informationen
|
|
$result = $tracker->measure(
|
|
'api.request',
|
|
PerformanceCategory::API,
|
|
function () {
|
|
return $this->apiClient->fetch('/users');
|
|
},
|
|
context: ['endpoint' => '/users', 'method' => 'GET']
|
|
);
|
|
```
|
|
|
|
### Verschachtelte Operationen
|
|
|
|
```php
|
|
// Nested Performance Tracking
|
|
$tracker->measure(
|
|
'http.request',
|
|
PerformanceCategory::CONTROLLER,
|
|
function () use ($tracker) {
|
|
// Controller-Logik
|
|
usleep(5000);
|
|
|
|
// Nested Database Query
|
|
$tracker->measure(
|
|
'database.query',
|
|
PerformanceCategory::DATABASE,
|
|
function () {
|
|
usleep(3000);
|
|
return ['user_id' => 123];
|
|
}
|
|
);
|
|
|
|
// Nested Cache Operation
|
|
$tracker->measure(
|
|
'cache.get',
|
|
PerformanceCategory::CACHE,
|
|
function () {
|
|
usleep(1000);
|
|
return ['cached' => true];
|
|
}
|
|
);
|
|
|
|
return ['status' => 'success'];
|
|
}
|
|
);
|
|
```
|
|
|
|
### Manuelle Start/Stop
|
|
|
|
```php
|
|
// Für komplexere Szenarien
|
|
$operationId = $tracker->startOperation(
|
|
'complex.operation',
|
|
PerformanceCategory::CUSTOM,
|
|
context: ['user_id' => 123]
|
|
);
|
|
|
|
try {
|
|
// Business Logic
|
|
$this->processData();
|
|
$this->saveResults();
|
|
} finally {
|
|
$measurement = $tracker->endOperation($operationId);
|
|
}
|
|
```
|
|
|
|
## Flamegraph Visualisierung
|
|
|
|
### Flamegraph Daten Generieren
|
|
|
|
```php
|
|
// Nach Performance-Messungen
|
|
$tracker->measure('parent.operation', PerformanceCategory::CONTROLLER, function () {
|
|
$this->tracker->measure('child.operation.1', PerformanceCategory::DATABASE, fn() => sleep(1));
|
|
$this->tracker->measure('child.operation.2', PerformanceCategory::CACHE, fn() => sleep(1));
|
|
});
|
|
|
|
// Flamegraph-Daten exportieren
|
|
$flamegraphData = $tracker->generateFlamegraph();
|
|
|
|
/*
|
|
Ausgabe-Format:
|
|
[
|
|
['stack_trace' => 'parent.operation', 'samples' => 50.5],
|
|
['stack_trace' => 'parent.operation;child.operation.1', 'samples' => 1000.2],
|
|
['stack_trace' => 'parent.operation;child.operation.2', 'samples' => 1000.1]
|
|
]
|
|
*/
|
|
```
|
|
|
|
### Flamegraph Visualisierung mit flamegraph.pl
|
|
|
|
```php
|
|
// Flamegraph-Format für Brendan Gregg's flamegraph.pl Tool
|
|
$flamegraphData = $tracker->generateFlamegraph();
|
|
|
|
// In Brendan Gregg Format konvertieren
|
|
$output = [];
|
|
foreach ($flamegraphData as $entry) {
|
|
$output[] = sprintf(
|
|
"%s %d",
|
|
$entry['stack_trace'],
|
|
(int) ceil($entry['samples'])
|
|
);
|
|
}
|
|
|
|
// In Datei schreiben
|
|
file_put_contents('/tmp/flamegraph.txt', implode("\n", $output));
|
|
|
|
// SVG generieren mit flamegraph.pl
|
|
// flamegraph.pl /tmp/flamegraph.txt > /tmp/flamegraph.svg
|
|
```
|
|
|
|
### Flamegraph in HTML einbetten
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Performance Flamegraph</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/d3-flame-graph@4"></script>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/d3-flame-graph@4/dist/d3-flamegraph.css">
|
|
</head>
|
|
<body>
|
|
<div id="chart"></div>
|
|
<script>
|
|
// Flamegraph-Daten vom Server laden
|
|
fetch('/api/performance/flamegraph')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// Daten in d3-flame-graph Format konvertieren
|
|
const hierarchyData = convertToHierarchy(data);
|
|
|
|
const flamegraph = d3.flamegraph()
|
|
.width(1200)
|
|
.cellHeight(18)
|
|
.transitionDuration(750)
|
|
.minFrameSize(5)
|
|
.transitionEase(d3.easeCubic)
|
|
.sort(true)
|
|
.title("")
|
|
.onClick(d => console.log("Clicked:", d.data.name));
|
|
|
|
d3.select("#chart")
|
|
.datum(hierarchyData)
|
|
.call(flamegraph);
|
|
});
|
|
|
|
function convertToHierarchy(flamegraphData) {
|
|
// Konvertierung von Flamegraph-Format zu d3-hierarchy
|
|
const root = { name: "root", value: 0, children: [] };
|
|
|
|
flamegraphData.forEach(entry => {
|
|
const parts = entry.stack_trace.split(';');
|
|
let current = root;
|
|
|
|
parts.forEach((part, index) => {
|
|
let child = current.children?.find(c => c.name === part);
|
|
if (!child) {
|
|
child = {
|
|
name: part,
|
|
value: entry.samples,
|
|
children: index < parts.length - 1 ? [] : undefined
|
|
};
|
|
if (!current.children) current.children = [];
|
|
current.children.push(child);
|
|
}
|
|
current = child;
|
|
});
|
|
});
|
|
|
|
return root;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
## Timeline Visualisierung
|
|
|
|
### Timeline Daten Generieren
|
|
|
|
```php
|
|
// Nach Performance-Messungen
|
|
$timeline = $tracker->generateTimeline();
|
|
|
|
/*
|
|
Ausgabe-Format:
|
|
[
|
|
[
|
|
'name' => 'database.query',
|
|
'category' => 'database',
|
|
'start_time' => 1704067200.123456,
|
|
'end_time' => 1704067200.234567,
|
|
'duration_ms' => 111.111,
|
|
'depth' => 0,
|
|
'operation_id' => 'database_query_1_abc123',
|
|
'parent_id' => null,
|
|
'self_time_ms' => 111.111,
|
|
'memory_delta_mb' => 2.5,
|
|
'context' => ['query' => 'SELECT * FROM users']
|
|
],
|
|
// ... weitere Events
|
|
]
|
|
*/
|
|
```
|
|
|
|
### Chrome DevTools Timeline Format
|
|
|
|
```php
|
|
// Timeline für Chrome DevTools Performance Tab
|
|
$timeline = $tracker->generateTimeline();
|
|
|
|
// In Chrome Trace Event Format konvertieren
|
|
$traceEvents = [];
|
|
|
|
foreach ($timeline as $event) {
|
|
// Begin Event
|
|
$traceEvents[] = [
|
|
'name' => $event['name'],
|
|
'cat' => $event['category'],
|
|
'ph' => 'B', // Begin
|
|
'ts' => (int)($event['start_time'] * 1_000_000), // Microseconds
|
|
'pid' => 1,
|
|
'tid' => 1,
|
|
'args' => $event['context']
|
|
];
|
|
|
|
// End Event
|
|
$traceEvents[] = [
|
|
'name' => $event['name'],
|
|
'cat' => $event['category'],
|
|
'ph' => 'E', // End
|
|
'ts' => (int)($event['end_time'] * 1_000_000),
|
|
'pid' => 1,
|
|
'tid' => 1
|
|
];
|
|
}
|
|
|
|
$chromeTrace = [
|
|
'traceEvents' => $traceEvents,
|
|
'displayTimeUnit' => 'ms',
|
|
'otherData' => [
|
|
'version' => '1.0'
|
|
]
|
|
];
|
|
|
|
// Als JSON speichern
|
|
file_put_contents('/tmp/chrome-trace.json', json_encode($chromeTrace));
|
|
|
|
// In Chrome öffnen: chrome://tracing → Load → chrome-trace.json
|
|
```
|
|
|
|
### Waterfall Visualisierung mit HTML/CSS
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<style>
|
|
.timeline-container {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 600px;
|
|
background: #f5f5f5;
|
|
padding: 20px;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.timeline-event {
|
|
position: absolute;
|
|
height: 30px;
|
|
border-radius: 4px;
|
|
padding: 4px 8px;
|
|
font-size: 12px;
|
|
color: white;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
border: 1px solid rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.category-database { background-color: #3b82f6; }
|
|
.category-cache { background-color: #10b981; }
|
|
.category-controller { background-color: #f59e0b; }
|
|
.category-view { background-color: #8b5cf6; }
|
|
.category-api { background-color: #ec4899; }
|
|
|
|
.timeline-axis {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
border-left: 1px solid #ccc;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="timeline-container" class="timeline-container"></div>
|
|
|
|
<script>
|
|
fetch('/api/performance/timeline')
|
|
.then(response => response.json())
|
|
.then(timeline => {
|
|
renderTimeline(timeline);
|
|
});
|
|
|
|
function renderTimeline(timeline) {
|
|
const container = document.getElementById('timeline-container');
|
|
|
|
// Berechne Zeitrahmen
|
|
const startTime = Math.min(...timeline.map(e => e.start_time));
|
|
const endTime = Math.max(...timeline.map(e => e.end_time));
|
|
const totalDuration = (endTime - startTime) * 1000; // ms
|
|
|
|
const containerWidth = 1200;
|
|
const rowHeight = 40;
|
|
|
|
timeline.forEach((event, index) => {
|
|
const eventDiv = document.createElement('div');
|
|
eventDiv.className = `timeline-event category-${event.category}`;
|
|
|
|
// Position berechnen
|
|
const left = ((event.start_time - startTime) * 1000 / totalDuration) * containerWidth;
|
|
const width = (event.duration_ms / totalDuration) * containerWidth;
|
|
const top = event.depth * rowHeight + 10;
|
|
|
|
eventDiv.style.left = `${left}px`;
|
|
eventDiv.style.width = `${width}px`;
|
|
eventDiv.style.top = `${top}px`;
|
|
|
|
eventDiv.innerHTML = `
|
|
${event.name}
|
|
<small>(${event.duration_ms.toFixed(2)}ms)</small>
|
|
`;
|
|
|
|
eventDiv.title = `
|
|
${event.name}
|
|
Duration: ${event.duration_ms.toFixed(2)}ms
|
|
Self Time: ${event.self_time_ms.toFixed(2)}ms
|
|
Memory: ${event.memory_delta_mb.toFixed(2)}MB
|
|
`;
|
|
|
|
container.appendChild(eventDiv);
|
|
});
|
|
|
|
// Set container height
|
|
const maxDepth = Math.max(...timeline.map(e => e.depth));
|
|
container.style.height = `${(maxDepth + 2) * rowHeight}px`;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### Gantt Chart Visualisierung
|
|
|
|
```php
|
|
// Gantt Chart Daten vorbereiten
|
|
$timeline = $tracker->generateTimeline();
|
|
|
|
$ganttData = array_map(function($event) {
|
|
return [
|
|
'id' => $event['operation_id'],
|
|
'name' => $event['name'],
|
|
'start' => date('Y-m-d H:i:s', (int)$event['start_time']),
|
|
'end' => date('Y-m-d H:i:s', (int)$event['end_time']),
|
|
'duration' => $event['duration_ms'],
|
|
'category' => $event['category'],
|
|
'parent' => $event['parent_id'],
|
|
'metadata' => [
|
|
'self_time' => $event['self_time_ms'],
|
|
'memory' => $event['memory_delta_mb'],
|
|
'context' => $event['context']
|
|
]
|
|
];
|
|
}, $timeline);
|
|
|
|
// JSON für Frontend
|
|
header('Content-Type: application/json');
|
|
echo json_encode($ganttData);
|
|
```
|
|
|
|
## API Controller Integration
|
|
|
|
### Performance Profiling Endpoint
|
|
|
|
```php
|
|
use App\Framework\Attributes\Route;
|
|
use App\Framework\Http\Method;
|
|
use App\Framework\Http\Responses\JsonResult;
|
|
|
|
final readonly class PerformanceProfilingController
|
|
{
|
|
public function __construct(
|
|
private NestedPerformanceTracker $tracker
|
|
) {}
|
|
|
|
#[Route(path: '/api/performance/flamegraph', method: Method::GET)]
|
|
public function getFlamegraph(): JsonResult
|
|
{
|
|
$flamegraphData = $this->tracker->generateFlamegraph();
|
|
|
|
return new JsonResult([
|
|
'data' => $flamegraphData,
|
|
'total_operations' => count($flamegraphData),
|
|
'generated_at' => date('Y-m-d H:i:s')
|
|
]);
|
|
}
|
|
|
|
#[Route(path: '/api/performance/timeline', method: Method::GET)]
|
|
public function getTimeline(): JsonResult
|
|
{
|
|
$timeline = $this->tracker->generateTimeline();
|
|
|
|
return new JsonResult([
|
|
'events' => $timeline,
|
|
'total_events' => count($timeline),
|
|
'generated_at' => date('Y-m-d H:i:s')
|
|
]);
|
|
}
|
|
|
|
#[Route(path: '/api/performance/chrome-trace', method: Method::GET)]
|
|
public function getChromeTrace(): JsonResult
|
|
{
|
|
$timeline = $this->tracker->generateTimeline();
|
|
|
|
// In Chrome Trace Event Format konvertieren
|
|
$traceEvents = $this->convertToChromeTrace($timeline);
|
|
|
|
return new JsonResult([
|
|
'traceEvents' => $traceEvents,
|
|
'displayTimeUnit' => 'ms',
|
|
'otherData' => [
|
|
'version' => '1.0',
|
|
'framework' => 'Custom PHP Framework'
|
|
]
|
|
]);
|
|
}
|
|
|
|
#[Route(path: '/api/performance/summary', method: Method::GET)]
|
|
public function getSummary(): JsonResult
|
|
{
|
|
$summary = $this->tracker->getSummary();
|
|
|
|
return new JsonResult($summary);
|
|
}
|
|
|
|
#[Route(path: '/api/performance/reset', method: Method::POST)]
|
|
public function reset(): JsonResult
|
|
{
|
|
$this->tracker->reset();
|
|
|
|
return new JsonResult([
|
|
'status' => 'reset',
|
|
'message' => 'Performance tracker reset successfully'
|
|
]);
|
|
}
|
|
|
|
private function convertToChromeTrace(array $timeline): array
|
|
{
|
|
$traceEvents = [];
|
|
|
|
foreach ($timeline as $event) {
|
|
// Begin Event
|
|
$traceEvents[] = [
|
|
'name' => $event['name'],
|
|
'cat' => $event['category'],
|
|
'ph' => 'B',
|
|
'ts' => (int)($event['start_time'] * 1_000_000),
|
|
'pid' => 1,
|
|
'tid' => 1,
|
|
'args' => $event['context']
|
|
];
|
|
|
|
// End Event
|
|
$traceEvents[] = [
|
|
'name' => $event['name'],
|
|
'cat' => $event['category'],
|
|
'ph' => 'E',
|
|
'ts' => (int)($event['end_time'] * 1_000_000),
|
|
'pid' => 1,
|
|
'tid' => 1
|
|
];
|
|
}
|
|
|
|
return $traceEvents;
|
|
}
|
|
}
|
|
```
|
|
|
|
## LiveComponent Profiling
|
|
|
|
### Component-spezifisches Profiling
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Profiling\LiveComponentProfiler;
|
|
|
|
final readonly class UserProfileComponent
|
|
{
|
|
public function __construct(
|
|
private LiveComponentProfiler $profiler
|
|
) {}
|
|
|
|
public function resolve(array $data): array
|
|
{
|
|
// Session-basiertes Profiling starten
|
|
$sessionId = $this->profiler->startSession('user-profile');
|
|
|
|
// Resolve Phase profilen
|
|
$userData = $this->profiler->profileResolve(
|
|
'user-profile',
|
|
fn() => $this->userRepository->find($data['user_id'])
|
|
);
|
|
|
|
// Render Phase profilen
|
|
$html = $this->profiler->profileRender(
|
|
'user-profile',
|
|
fn() => $this->renderTemplate($userData)
|
|
);
|
|
|
|
// Session beenden und Ergebnisse erhalten
|
|
$result = $this->profiler->endSession($sessionId);
|
|
|
|
// Flamegraph exportieren
|
|
$flamegraph = $result->exportFlamegraph();
|
|
|
|
// Timeline exportieren
|
|
$timeline = $result->exportTimeline();
|
|
|
|
return [
|
|
'html' => $html,
|
|
'profiling' => [
|
|
'flamegraph' => $flamegraph,
|
|
'timeline' => $timeline,
|
|
'total_duration' => $result->getTotalDuration()->toMilliseconds()
|
|
]
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
## Performance-Metriken Sammeln
|
|
|
|
### Metriken zu Prometheus exportieren
|
|
|
|
```php
|
|
use App\Framework\Performance\NestedPerformanceTracker;
|
|
|
|
final readonly class PerformanceMetricsExporter
|
|
{
|
|
public function __construct(
|
|
private NestedPerformanceTracker $tracker
|
|
) {}
|
|
|
|
public function exportToPrometheus(): string
|
|
{
|
|
$summary = $this->tracker->getSummary();
|
|
$timeline = $this->tracker->generateTimeline();
|
|
|
|
$metrics = [];
|
|
|
|
// Total operations counter
|
|
$metrics[] = sprintf(
|
|
"# HELP performance_operations_total Total number of operations\n" .
|
|
"# TYPE performance_operations_total counter\n" .
|
|
"performance_operations_total %d",
|
|
$summary['total_operations']
|
|
);
|
|
|
|
// Average duration gauge
|
|
$metrics[] = sprintf(
|
|
"# HELP performance_average_duration_ms Average operation duration in milliseconds\n" .
|
|
"# TYPE performance_average_duration_ms gauge\n" .
|
|
"performance_average_duration_ms %.2f",
|
|
$summary['average_duration_ms']
|
|
);
|
|
|
|
// Per-category metrics
|
|
$byCategory = $this->groupByCategory($timeline);
|
|
|
|
foreach ($byCategory as $category => $events) {
|
|
$avgDuration = array_sum(array_column($events, 'duration_ms')) / count($events);
|
|
|
|
$metrics[] = sprintf(
|
|
"performance_category_duration_ms{category=\"%s\"} %.2f",
|
|
$category,
|
|
$avgDuration
|
|
);
|
|
}
|
|
|
|
return implode("\n\n", $metrics) . "\n";
|
|
}
|
|
|
|
private function groupByCategory(array $timeline): array
|
|
{
|
|
$grouped = [];
|
|
|
|
foreach ($timeline as $event) {
|
|
$category = $event['category'];
|
|
if (!isset($grouped[$category])) {
|
|
$grouped[$category] = [];
|
|
}
|
|
$grouped[$category][] = $event;
|
|
}
|
|
|
|
return $grouped;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Performance Budget Monitoring
|
|
|
|
```php
|
|
// Performance Budget definieren
|
|
$budgets = [
|
|
'database.query' => 100, // max 100ms
|
|
'api.request' => 500, // max 500ms
|
|
'cache.get' => 10, // max 10ms
|
|
];
|
|
|
|
// Nach Messung validieren
|
|
$timeline = $tracker->generateTimeline();
|
|
|
|
foreach ($timeline as $event) {
|
|
$budget = $budgets[$event['name']] ?? null;
|
|
|
|
if ($budget && $event['duration_ms'] > $budget) {
|
|
$this->logger->warning('Performance budget exceeded', [
|
|
'operation' => $event['name'],
|
|
'duration' => $event['duration_ms'],
|
|
'budget' => $budget,
|
|
'exceeded_by' => $event['duration_ms'] - $budget
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Production Performance Monitoring
|
|
|
|
```php
|
|
// Nur bei langsamen Requests profilen
|
|
final readonly class PerformanceMiddleware implements Middleware
|
|
{
|
|
public function process(Request $request, callable $next): Response
|
|
{
|
|
$startTime = microtime(true);
|
|
|
|
$response = $next($request);
|
|
|
|
$duration = (microtime(true) - $startTime) * 1000;
|
|
|
|
// Nur bei langsamen Requests (>500ms) Profiling-Daten speichern
|
|
if ($duration > 500) {
|
|
$flamegraph = $this->tracker->generateFlamegraph();
|
|
$timeline = $this->tracker->generateTimeline();
|
|
|
|
$this->performanceLogger->logSlowRequest([
|
|
'url' => (string) $request->uri,
|
|
'duration_ms' => $duration,
|
|
'flamegraph' => $flamegraph,
|
|
'timeline' => $timeline
|
|
]);
|
|
}
|
|
|
|
$this->tracker->reset();
|
|
|
|
return $response;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Automatische Bottleneck-Erkennung
|
|
|
|
```php
|
|
// Bottlenecks automatisch identifizieren
|
|
$timeline = $tracker->generateTimeline();
|
|
|
|
// Sortiere nach self_time_ms (Zeit ohne Children)
|
|
usort($timeline, fn($a, $b) => $b['self_time_ms'] <=> $a['self_time_ms']);
|
|
|
|
// Top 5 Bottlenecks
|
|
$bottlenecks = array_slice($timeline, 0, 5);
|
|
|
|
foreach ($bottlenecks as $bottleneck) {
|
|
$this->logger->info('Performance bottleneck detected', [
|
|
'operation' => $bottleneck['name'],
|
|
'category' => $bottleneck['category'],
|
|
'self_time_ms' => $bottleneck['self_time_ms'],
|
|
'total_time_ms' => $bottleneck['duration_ms'],
|
|
'percentage' => ($bottleneck['self_time_ms'] / $bottleneck['duration_ms']) * 100
|
|
]);
|
|
}
|
|
```
|
|
|
|
### 4. Memory Leak Detection
|
|
|
|
```php
|
|
// Memory Leaks über Timeline identifizieren
|
|
$timeline = $tracker->generateTimeline();
|
|
|
|
$totalMemoryDelta = 0;
|
|
$suspiciousOperations = [];
|
|
|
|
foreach ($timeline as $event) {
|
|
$totalMemoryDelta += $event['memory_delta_mb'];
|
|
|
|
// Operationen mit hohem Memory-Verbrauch markieren
|
|
if ($event['memory_delta_mb'] > 10) { // > 10MB
|
|
$suspiciousOperations[] = [
|
|
'operation' => $event['name'],
|
|
'memory_mb' => $event['memory_delta_mb'],
|
|
'context' => $event['context']
|
|
];
|
|
}
|
|
}
|
|
|
|
if (!empty($suspiciousOperations)) {
|
|
$this->logger->warning('High memory usage detected', [
|
|
'total_memory_delta_mb' => $totalMemoryDelta,
|
|
'suspicious_operations' => $suspiciousOperations
|
|
]);
|
|
}
|
|
```
|
|
|
|
## Testing Performance Profiling
|
|
|
|
```php
|
|
// Performance Profiling in Tests
|
|
it('profiles nested operations correctly', function () {
|
|
$tracker = new NestedPerformanceTracker(
|
|
new SystemClock(),
|
|
new SystemHighResolutionClock(),
|
|
new MemoryMonitor()
|
|
);
|
|
|
|
// Perform tracked operations
|
|
$tracker->measure('parent', PerformanceCategory::CONTROLLER, function () use ($tracker) {
|
|
usleep(5000);
|
|
|
|
$tracker->measure('child', PerformanceCategory::DATABASE, function () {
|
|
usleep(3000);
|
|
});
|
|
});
|
|
|
|
// Validate flamegraph
|
|
$flamegraph = $tracker->generateFlamegraph();
|
|
expect($flamegraph)->toHaveCount(2);
|
|
expect($flamegraph[0]['stack_trace'])->toBe('parent');
|
|
expect($flamegraph[1]['stack_trace'])->toBe('parent;child');
|
|
|
|
// Validate timeline
|
|
$timeline = $tracker->generateTimeline();
|
|
expect($timeline)->toHaveCount(2);
|
|
expect($timeline[0]['name'])->toBe('parent');
|
|
expect($timeline[1]['name'])->toBe('child');
|
|
expect($timeline[1]['parent_id'])->toBe($timeline[0]['operation_id']);
|
|
});
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Flamegraph zeigt keine Daten
|
|
|
|
**Problem**: `generateFlamegraph()` gibt leeres Array zurück
|
|
|
|
**Lösung**:
|
|
```php
|
|
// Überprüfe ob Operationen completed sind
|
|
$summary = $tracker->getSummary();
|
|
dd($summary);
|
|
|
|
// Stelle sicher dass measure() verwendet wird
|
|
$tracker->measure('operation', PerformanceCategory::CUSTOM, fn() => doWork());
|
|
|
|
// Nicht: startOperation() ohne endOperation()
|
|
```
|
|
|
|
### Timeline Events haben 0ms Duration
|
|
|
|
**Problem**: Events zeigen `duration_ms: 0`
|
|
|
|
**Lösung**:
|
|
```php
|
|
// usleep() verwenden für Tests (mindestens 1000 microseconds)
|
|
usleep(1000); // 1ms
|
|
|
|
// Oder echte Operationen durchführen
|
|
$data = $repository->findAll(); // Reale Database Query
|
|
```
|
|
|
|
### Memory Delta ist immer 0
|
|
|
|
**Problem**: `memory_delta_mb` zeigt 0.00
|
|
|
|
**Lösung**:
|
|
```php
|
|
// Memory-intensive Operationen durchführen
|
|
$tracker->measure('memory-test', PerformanceCategory::CUSTOM, function () {
|
|
$data = array_fill(0, 10000, 'test'); // Allocate memory
|
|
return $data;
|
|
});
|
|
```
|
|
|
|
## Zusammenfassung
|
|
|
|
Das Performance Profiling System bietet:
|
|
- ✅ **Nested Performance Tracking** mit hierarchischen Messungen
|
|
- ✅ **Flamegraph Export** im Brendan Gregg Format
|
|
- ✅ **Timeline Visualisierung** mit Chrome DevTools Support
|
|
- ✅ **Memory Tracking** für Leak Detection
|
|
- ✅ **LiveComponent Integration** für Component-spezifisches Profiling
|
|
- ✅ **Production Monitoring** mit Performance Budget Validation
|
|
- ✅ **Automatische Bottleneck Detection** und Alerting
|
|
|
|
**Verwendung**:
|
|
1. Operations mit `measure()` tracken
|
|
2. `generateFlamegraph()` für Flamegraph-Visualisierung
|
|
3. `generateTimeline()` für Timeline-Analyse
|
|
4. In Visualisierungs-Tools integrieren (Chrome DevTools, flamegraph.pl, D3.js)
|
|
5. Performance Budgets definieren und monitoren
|