Files
michaelschiemer/tests/Performance/Reports/PerformanceReportGenerator.php
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

276 lines
8.9 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Performance\Reports;
use Tests\Performance\PerformanceBenchmarkResult;
use Tests\Performance\LoadTests\LoadTestResult;
/**
* Generates performance test reports in various formats
*/
final readonly class PerformanceReportGenerator
{
/**
* Generate HTML report from benchmark results
*
* @param array<string, PerformanceBenchmarkResult> $benchmarkResults
* @param LoadTestResult|null $loadTestResult
* @return string HTML content
*/
public function generateHtmlReport(
array $benchmarkResults,
?LoadTestResult $loadTestResult = null
): string {
$html = <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Performance Test Report</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
h1 {
color: #333;
border-bottom: 3px solid #4CAF50;
padding-bottom: 10px;
}
h2 {
color: #555;
margin-top: 30px;
}
.summary {
background: white;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
table {
width: 100%;
border-collapse: collapse;
background: white;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background: #4CAF50;
color: white;
font-weight: 600;
}
tr:hover {
background: #f5f5f5;
}
.metric {
font-weight: 600;
color: #333;
}
.good {
color: #4CAF50;
}
.warning {
color: #FF9800;
}
.bad {
color: #F44336;
}
.timestamp {
color: #999;
font-size: 0.9em;
}
</style>
</head>
<body>
<h1>Performance Test Report</h1>
<div class="summary">
<p class="timestamp">Generated: {$this->getCurrentTimestamp()}</p>
<p><strong>Total Benchmarks:</strong> {$this->countBenchmarks($benchmarkResults)}</p>
</div>
HTML;
// Add benchmark results
if (!empty($benchmarkResults)) {
$html .= $this->generateBenchmarkSection($benchmarkResults);
}
// Add load test results
if ($loadTestResult) {
$html .= $this->generateLoadTestSection($loadTestResult);
}
$html .= <<<HTML
</body>
</html>
HTML;
return $html;
}
/**
* Generate JSON report
*/
public function generateJsonReport(
array $benchmarkResults,
?LoadTestResult $loadTestResult = null
): string {
$report = [
'timestamp' => date('Y-m-d H:i:s'),
'benchmarks' => []
];
foreach ($benchmarkResults as $category => $result) {
$report['benchmarks'][$category] = $result->toArray();
}
if ($loadTestResult) {
$report['load_test'] = $loadTestResult->toArray();
}
return json_encode($report, JSON_PRETTY_PRINT);
}
/**
* Generate markdown report
*/
public function generateMarkdownReport(
array $benchmarkResults,
?LoadTestResult $loadTestResult = null
): string {
$markdown = "# Performance Test Report\n\n";
$markdown .= "_Generated: " . $this->getCurrentTimestamp() . "_\n\n";
// Benchmark results
if (!empty($benchmarkResults)) {
$markdown .= "## Benchmarks\n\n";
foreach ($benchmarkResults as $category => $result) {
$markdown .= "### {$result->name}\n\n";
$markdown .= "| Metric | Value |\n";
$markdown .= "|--------|-------|\n";
$markdown .= "| Iterations | {$result->iterations} |\n";
$markdown .= "| Avg Time | {$result->avgTimeMs}ms |\n";
$markdown .= "| Min Time | {$result->minTimeMs}ms |\n";
$markdown .= "| Max Time | {$result->maxTimeMs}ms |\n";
$markdown .= "| Median Time | {$result->medianTimeMs}ms |\n";
$markdown .= "| P95 Time | {$result->p95TimeMs}ms |\n";
$markdown .= "| P99 Time | {$result->p99TimeMs}ms |\n";
$markdown .= "| Ops/Sec | " . round($result->operationsPerSecond, 2) . " |\n";
$markdown .= "\n";
}
}
// Load test results
if ($loadTestResult) {
$markdown .= "## Load Test Results\n\n";
$markdown .= "| Metric | Value |\n";
$markdown .= "|--------|-------|\n";
$markdown .= "| Total Requests | {$loadTestResult->totalRequests} |\n";
$markdown .= "| Successful | {$loadTestResult->successfulRequests} |\n";
$markdown .= "| Error Rate | " . round($loadTestResult->errorRate, 2) . "% |\n";
$markdown .= "| Avg Response Time | " . round($loadTestResult->avgResponseTimeMs, 2) . "ms |\n";
$markdown .= "| P95 Response Time | " . round($loadTestResult->p95ResponseTimeMs, 2) . "ms |\n";
$markdown .= "| Throughput | " . round($loadTestResult->requestsPerSecond, 2) . " req/sec |\n";
}
return $markdown;
}
/**
* Save report to file
*/
public function saveReport(string $content, string $filename): void
{
$reportsDir = __DIR__ . '/../Reports';
if (!is_dir($reportsDir)) {
mkdir($reportsDir, 0755, true);
}
$filepath = $reportsDir . '/' . $filename;
file_put_contents($filepath, $content);
}
private function generateBenchmarkSection(array $benchmarkResults): string
{
$html = "<h2>Benchmark Results</h2>\n";
$html .= "<table>\n";
$html .= "<thead>\n";
$html .= "<tr><th>Benchmark</th><th>Iterations</th><th>Avg Time</th><th>Min</th><th>Max</th><th>P95</th><th>Ops/Sec</th></tr>\n";
$html .= "</thead>\n";
$html .= "<tbody>\n";
foreach ($benchmarkResults as $category => $result) {
$colorClass = $this->getColorClass($result->avgTimeMs);
$html .= "<tr>\n";
$html .= "<td class='metric'>{$result->name}</td>\n";
$html .= "<td>{$result->iterations}</td>\n";
$html .= "<td class='{$colorClass}'>" . round($result->avgTimeMs, 2) . "ms</td>\n";
$html .= "<td>" . round($result->minTimeMs, 2) . "ms</td>\n";
$html .= "<td>" . round($result->maxTimeMs, 2) . "ms</td>\n";
$html .= "<td>" . round($result->p95TimeMs, 2) . "ms</td>\n";
$html .= "<td>" . round($result->operationsPerSecond, 2) . "</td>\n";
$html .= "</tr>\n";
}
$html .= "</tbody>\n";
$html .= "</table>\n";
return $html;
}
private function generateLoadTestSection(LoadTestResult $result): string
{
$html = "<h2>Load Test Results</h2>\n";
$html .= "<table>\n";
$html .= "<thead>\n";
$html .= "<tr><th>Metric</th><th>Value</th></tr>\n";
$html .= "</thead>\n";
$html .= "<tbody>\n";
$html .= "<tr><td class='metric'>Total Requests</td><td>{$result->totalRequests}</td></tr>\n";
$html .= "<tr><td class='metric'>Successful Requests</td><td>{$result->successfulRequests}</td></tr>\n";
$html .= "<tr><td class='metric'>Error Rate</td><td>" . round($result->errorRate, 2) . "%</td></tr>\n";
$html .= "<tr><td class='metric'>Avg Response Time</td><td>" . round($result->avgResponseTimeMs, 2) . "ms</td></tr>\n";
$html .= "<tr><td class='metric'>P95 Response Time</td><td>" . round($result->p95ResponseTimeMs, 2) . "ms</td></tr>\n";
$html .= "<tr><td class='metric'>P99 Response Time</td><td>" . round($result->p99ResponseTimeMs, 2) . "ms</td></tr>\n";
$html .= "<tr><td class='metric'>Throughput</td><td>" . round($result->requestsPerSecond, 2) . " req/sec</td></tr>\n";
$html .= "</tbody>\n";
$html .= "</table>\n";
return $html;
}
private function getColorClass(float $timeMs): string
{
if ($timeMs < 10) {
return 'good';
} elseif ($timeMs < 50) {
return 'warning';
}
return 'bad';
}
private function getCurrentTimestamp(): string
{
return date('Y-m-d H:i:s');
}
private function countBenchmarks(array $benchmarkResults): int
{
return count($benchmarkResults);
}
}