Files
michaelschiemer/tests/e2e/livecomponents/generate-performance-report.js
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

424 lines
13 KiB
JavaScript

#!/usr/bin/env node
/**
* Performance Benchmark Report Generator
*
* Generates HTML and Markdown reports from benchmark-results.json
*
* Usage:
* node tests/e2e/livecomponents/generate-performance-report.js
* node tests/e2e/livecomponents/generate-performance-report.js --format=html
* node tests/e2e/livecomponents/generate-performance-report.js --format=markdown
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Parse command line arguments
const args = process.argv.slice(2);
const format = args.find(arg => arg.startsWith('--format='))?.split('=')[1] || 'both';
// Load benchmark results
const resultsPath = path.join(process.cwd(), 'test-results', 'benchmark-results.json');
if (!fs.existsSync(resultsPath)) {
console.error('❌ No benchmark results found. Run benchmarks first:');
console.error(' npx playwright test performance-benchmarks.spec.js');
process.exit(1);
}
const data = JSON.parse(fs.readFileSync(resultsPath, 'utf-8'));
const { timestamp, results, summary } = data;
// Group results by scenario
const scenarios = {};
results.forEach(result => {
if (!scenarios[result.scenario]) {
scenarios[result.scenario] = [];
}
scenarios[result.scenario].push(result);
});
/**
* Generate Markdown Report
*/
function generateMarkdownReport() {
let markdown = `# LiveComponents Performance Benchmark Report
**Generated:** ${new Date(timestamp).toLocaleString()}
**Summary:**
- Total Benchmarks: ${summary.total}
- Passed: ${summary.passed}
- Failed: ${summary.failed} ${summary.failed > 0 ? '❌' : ''}
---
## Executive Summary
This report compares the performance characteristics of **Fragment-based rendering** vs **Full HTML rendering** in LiveComponents.
### Key Findings
`;
// Calculate overall speedups
const fragmentVsFull = [];
Object.values(scenarios).forEach(scenarioResults => {
const fragmentResult = scenarioResults.find(r => r.metric.includes('Fragment'));
const fullResult = scenarioResults.find(r => r.metric.includes('Full'));
if (fragmentResult && fullResult && fragmentResult.unit === 'ms' && fullResult.unit === 'ms') {
const speedup = ((fullResult.value - fragmentResult.value) / fullResult.value * 100);
fragmentVsFull.push({
scenario: fragmentResult.scenario,
speedup,
fragmentTime: fragmentResult.value,
fullTime: fullResult.value
});
}
});
if (fragmentVsFull.length > 0) {
const avgSpeedup = fragmentVsFull.reduce((sum, item) => sum + item.speedup, 0) / fragmentVsFull.length;
markdown += `**Average Performance Improvement:** ${avgSpeedup.toFixed(1)}% faster with fragments\n\n`;
const best = fragmentVsFull.reduce((best, item) => item.speedup > best.speedup ? item : best);
markdown += `**Best Case:** ${best.scenario} - ${best.speedup.toFixed(1)}% faster (${best.fragmentTime.toFixed(2)}ms vs ${best.fullTime.toFixed(2)}ms)\n\n`;
const worst = fragmentVsFull.reduce((worst, item) => item.speedup < worst.speedup ? item : worst);
markdown += `**Worst Case:** ${worst.scenario} - ${worst.speedup.toFixed(1)}% faster (${worst.fragmentTime.toFixed(2)}ms vs ${worst.fullTime.toFixed(2)}ms)\n\n`;
}
markdown += `---
## Detailed Results
`;
// Generate detailed results per scenario
Object.entries(scenarios).forEach(([scenarioName, scenarioResults]) => {
markdown += `### ${scenarioName}\n\n`;
markdown += `| Metric | Value | Threshold | Status |\n`;
markdown += `|--------|-------|-----------|--------|\n`;
scenarioResults.forEach(result => {
const value = result.unit === 'bytes'
? `${(result.value / 1024).toFixed(2)} KB`
: `${result.value.toFixed(2)} ${result.unit}`;
const threshold = result.unit === 'bytes'
? `${(result.threshold / 1024).toFixed(2)} KB`
: `${result.threshold} ${result.unit}`;
const status = result.passed ? '✅ Pass' : '❌ Fail';
markdown += `| ${result.metric} | ${value} | ${threshold} | ${status} |\n`;
});
markdown += `\n`;
});
markdown += `---
## Recommendations
Based on the benchmark results:
`;
// Generate recommendations
const recommendations = [];
// Check if fragments are faster
const avgFragmentTime = results
.filter(r => r.metric.includes('Fragment') && r.unit === 'ms')
.reduce((sum, r) => sum + r.value, 0) / results.filter(r => r.metric.includes('Fragment') && r.unit === 'ms').length;
const avgFullTime = results
.filter(r => r.metric.includes('Full') && r.unit === 'ms')
.reduce((sum, r) => sum + r.value, 0) / results.filter(r => r.metric.includes('Full') && r.unit === 'ms').length;
if (avgFragmentTime < avgFullTime) {
const improvement = ((avgFullTime - avgFragmentTime) / avgFullTime * 100).toFixed(1);
recommendations.push(`✅ **Use fragment rendering** for partial updates - Average ${improvement}% performance improvement`);
}
// Check payload sizes
const fragmentPayload = results.find(r => r.metric === 'Fragment Payload Size');
const fullPayload = results.find(r => r.metric === 'Full HTML Payload Size');
if (fragmentPayload && fullPayload) {
const reduction = ((fullPayload.value - fragmentPayload.value) / fullPayload.value * 100).toFixed(1);
recommendations.push(`✅ **Fragment updates reduce network payload** by ${reduction}% on average`);
}
// Check rapid updates
const rapidFragment = results.find(r => r.scenario === 'Rapid Updates (10x)' && r.metric.includes('Fragment'));
const rapidFull = results.find(r => r.scenario === 'Rapid Updates (10x)' && r.metric.includes('Full'));
if (rapidFragment && rapidFull) {
const multiplier = (rapidFull.value / rapidFragment.value).toFixed(1);
recommendations.push(`✅ **For rapid successive updates**, fragments are ${multiplier}x faster`);
}
// Check memory consumption
const memFragment = results.find(r => r.metric === 'Fragment Updates Memory Delta');
const memFull = results.find(r => r.metric === 'Full Renders Memory Delta');
if (memFragment && memFull && memFragment.value < memFull.value) {
const memReduction = ((memFull.value - memFragment.value) / memFull.value * 100).toFixed(1);
recommendations.push(`✅ **Lower memory consumption** with fragments - ${memReduction}% less memory used`);
}
recommendations.forEach(rec => {
markdown += `${rec}\n\n`;
});
markdown += `### When to Use Fragments
- ✅ **Small, frequent updates** (e.g., counters, notifications, status indicators)
- ✅ **Partial form updates** (e.g., validation errors, field suggestions)
- ✅ **List item modifications** (e.g., shopping cart items, task lists)
- ✅ **Real-time data updates** (e.g., live scores, stock prices)
- ✅ **Multi-section updates** (e.g., updating header + footer simultaneously)
### When to Use Full Render
- ⚠️ **Complete layout changes** (e.g., switching views, modal dialogs)
- ⚠️ **Initial page load** (no fragments to update yet)
- ⚠️ **Complex interdependent updates** (easier to re-render entire component)
- ⚠️ **Simple components** (overhead of fragment logic not worth it)
---
## Performance Metrics Glossary
- **Fragment Update Time:** Time to fetch and apply fragment-specific updates
- **Full Render Time:** Time to fetch and replace entire component HTML
- **Network Payload:** Size of data transferred from server to client
- **DOM Update:** Time spent manipulating the Document Object Model
- **Memory Delta:** Change in JavaScript heap memory usage
---
*Generated by LiveComponents Performance Benchmark Suite*
`;
return markdown;
}
/**
* Generate HTML Report
*/
function generateHTMLReport() {
const markdown = generateMarkdownReport();
// Simple markdown to HTML conversion
let html = markdown
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/`(.+?)`/g, '<code>$1</code>')
.replace(/^- (.+)$/gm, '<li>$1</li>')
.replace(/\n\n/g, '</p><p>')
.replace(/^---$/gm, '<hr>');
// Wrap in HTML template
const htmlDoc = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LiveComponents Performance Benchmark Report</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
background: #f5f5f5;
padding: 2rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 3rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
color: #2c3e50;
border-bottom: 3px solid #3498db;
padding-bottom: 1rem;
margin-bottom: 2rem;
}
h2 {
color: #34495e;
margin-top: 3rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid #ecf0f1;
}
h3 {
color: #7f8c8d;
margin-top: 2rem;
margin-bottom: 1rem;
}
p {
margin-bottom: 1rem;
}
table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
background: white;
}
th {
background: #3498db;
color: white;
padding: 1rem;
text-align: left;
font-weight: 600;
}
td {
padding: 0.75rem 1rem;
border-bottom: 1px solid #ecf0f1;
}
tr:hover {
background: #f8f9fa;
}
ul {
list-style-position: inside;
margin: 1rem 0;
}
li {
margin: 0.5rem 0;
padding-left: 1rem;
}
code {
background: #f1f3f4;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
}
strong {
color: #2c3e50;
font-weight: 600;
}
hr {
border: none;
border-top: 2px solid #ecf0f1;
margin: 2rem 0;
}
.summary-box {
background: #e8f4f8;
border-left: 4px solid #3498db;
padding: 1.5rem;
margin: 2rem 0;
border-radius: 4px;
}
.recommendation {
background: #d4edda;
border-left: 4px solid #28a745;
padding: 1rem;
margin: 1rem 0;
border-radius: 4px;
}
.warning {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 1rem;
margin: 1rem 0;
border-radius: 4px;
}
@media print {
body {
background: white;
padding: 0;
}
.container {
box-shadow: none;
padding: 1rem;
}
}
</style>
</head>
<body>
<div class="container">
${html}
</div>
</body>
</html>`;
return htmlDoc;
}
/**
* Main execution
*/
function main() {
console.log('📊 Generating Performance Benchmark Report...\n');
const outputDir = path.join(process.cwd(), 'test-results');
if (format === 'markdown' || format === 'both') {
const markdown = generateMarkdownReport();
const mdPath = path.join(outputDir, 'performance-report.md');
fs.writeFileSync(mdPath, markdown);
console.log(`✅ Markdown report: ${mdPath}`);
}
if (format === 'html' || format === 'both') {
const html = generateHTMLReport();
const htmlPath = path.join(outputDir, 'performance-report.html');
fs.writeFileSync(htmlPath, html);
console.log(`✅ HTML report: ${htmlPath}`);
}
console.log('\n📈 Report Generation Complete!\n');
console.log('Summary:');
console.log(` Total Benchmarks: ${summary.total}`);
console.log(` Passed: ${summary.passed}`);
console.log(` Failed: ${summary.failed} ${summary.failed > 0 ? '❌' : ''}`);
if (format === 'html' || format === 'both') {
console.log(`\n🌐 Open HTML report in browser:`);
console.log(` file://${path.join(outputDir, 'performance-report.html')}`);
}
}
main();