Files
michaelschiemer/docs/claude/performance-profiling.md
Michael Schiemer fc3d7e6357 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.
2025-10-25 19:18:37 +02:00

24 KiB

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

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

// 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

// 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

// 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

// 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

<!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

// 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

// 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

<!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

// 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

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

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

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

// 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

// 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

// 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

// 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

// 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:

// Ü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:

// 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:

// 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