#!/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, '
$1')
.replace(/^- (.+)$/gm, '') .replace(/^---$/gm, '