- 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.
276 lines
8.9 KiB
PHP
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);
|
|
}
|
|
}
|