UNPKG

lightweight-browser-load-tester

Version:

A lightweight load testing tool using real browsers for streaming applications with DRM support

486 lines (483 loc) 18.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ResultsAggregator = void 0; const prometheus_exporter_1 = require("../exporters/prometheus-exporter"); /** * Aggregates test results from multiple browser instances and generates reports */ class ResultsAggregator { constructor(prometheusConfig) { this.testResults = { summary: { totalRequests: 0, successfulRequests: 0, failedRequests: 0, averageResponseTime: 0, peakConcurrentUsers: 0, testDuration: 0 }, browserMetrics: [], drmMetrics: [], networkMetrics: [], errors: [] }; this.startTime = new Date(); // Initialize Prometheus exporter if configuration is provided if (prometheusConfig) { this.prometheusExporter = new prometheus_exporter_1.PrometheusExporter(prometheusConfig); } } /** * Add browser metrics from a single instance */ addBrowserMetrics(metrics) { this.testResults.browserMetrics.push(metrics); // Export to Prometheus in real-time if configured if (this.prometheusExporter) { this.prometheusExporter.exportBrowserMetrics(metrics).catch(error => { console.error('Failed to export browser metrics to Prometheus:', error); }); } } /** * Add DRM metrics */ addDRMMetrics(metrics) { this.testResults.drmMetrics.push(metrics); // Export to Prometheus in real-time if configured if (this.prometheusExporter) { this.prometheusExporter.exportDRMMetrics(metrics).catch(error => { console.error('Failed to export DRM metrics to Prometheus:', error); }); } } /** * Add network metrics */ addNetworkMetrics(metrics) { this.testResults.networkMetrics.push(...metrics); // Export to Prometheus in real-time if configured if (this.prometheusExporter && metrics.length > 0) { this.prometheusExporter.exportNetworkMetrics(metrics).catch(error => { console.error('Failed to export network metrics to Prometheus:', error); }); } } /** * Add error log entry */ addError(error) { this.testResults.errors.push(error); // Export to Prometheus in real-time if configured if (this.prometheusExporter) { this.prometheusExporter.exportErrorMetrics([error]).catch(error => { console.error('Failed to export error metrics to Prometheus:', error); }); } } /** * Finalize test results and calculate summary statistics */ finalize() { this.endTime = new Date(); this.calculateSummary(); // Export final summary to Prometheus if configured if (this.prometheusExporter) { this.prometheusExporter.exportTestSummary(this.testResults.summary).catch(error => { console.error('Failed to export test summary to Prometheus:', error); }); } } /** * Get the aggregated test results */ getResults() { return { ...this.testResults }; } /** * Shutdown the aggregator and flush any remaining Prometheus metrics */ async shutdown() { if (this.prometheusExporter) { await this.prometheusExporter.shutdown(); } } /** * Generate reports in specified formats */ async generateReports(config) { if (!this.endTime) { this.finalize(); } for (const format of config.formats) { switch (format) { case 'json': await this.generateJSONReport(config); break; case 'html': await this.generateHTMLReport(config); break; case 'csv': await this.generateCSVReport(config); break; } } } /** * Calculate summary statistics from collected metrics */ calculateSummary() { const networkMetrics = this.testResults.networkMetrics; const browserMetrics = this.testResults.browserMetrics; // Calculate request statistics this.testResults.summary.totalRequests = networkMetrics.length; this.testResults.summary.successfulRequests = networkMetrics.filter(m => m.statusCode >= 200 && m.statusCode < 400).length; this.testResults.summary.failedRequests = this.testResults.summary.totalRequests - this.testResults.summary.successfulRequests; // Calculate average response time if (networkMetrics.length > 0) { this.testResults.summary.averageResponseTime = networkMetrics.reduce((sum, m) => sum + m.responseTime, 0) / networkMetrics.length; } // Calculate peak concurrent users (max active browser instances) this.testResults.summary.peakConcurrentUsers = browserMetrics.length; // Calculate test duration if (this.endTime) { this.testResults.summary.testDuration = (this.endTime.getTime() - this.startTime.getTime()) / 1000; } } /** * Generate JSON report */ async generateJSONReport(config) { const fs = await Promise.resolve().then(() => __importStar(require('fs/promises'))); const path = await Promise.resolve().then(() => __importStar(require('path'))); const outputPath = config.outputPath || './test-results'; const filename = path.join(outputPath, `load-test-results-${Date.now()}.json`); await fs.mkdir(outputPath, { recursive: true }); await fs.writeFile(filename, JSON.stringify(this.testResults, null, 2)); } /** * Generate HTML report */ async generateHTMLReport(config) { const fs = await Promise.resolve().then(() => __importStar(require('fs/promises'))); const path = await Promise.resolve().then(() => __importStar(require('path'))); const outputPath = config.outputPath || './test-results'; const filename = path.join(outputPath, `load-test-results-${Date.now()}.html`); const html = this.generateHTMLContent(config); await fs.mkdir(outputPath, { recursive: true }); await fs.writeFile(filename, html); } /** * Generate CSV report */ async generateCSVReport(config) { const fs = await Promise.resolve().then(() => __importStar(require('fs/promises'))); const path = await Promise.resolve().then(() => __importStar(require('path'))); const outputPath = config.outputPath || './test-results'; const timestamp = Date.now(); await fs.mkdir(outputPath, { recursive: true }); // Generate summary CSV const summaryCSV = this.generateSummaryCSV(); await fs.writeFile(path.join(outputPath, `load-test-summary-${timestamp}.csv`), summaryCSV); // Generate network metrics CSV const networkCSV = this.generateNetworkMetricsCSV(); await fs.writeFile(path.join(outputPath, `load-test-network-${timestamp}.csv`), networkCSV); // Generate browser metrics CSV const browserCSV = this.generateBrowserMetricsCSV(); await fs.writeFile(path.join(outputPath, `load-test-browser-${timestamp}.csv`), browserCSV); } /** * Generate HTML content for the report */ generateHTMLContent(config) { const summary = this.testResults.summary; const errors = this.testResults.errors; const drmMetrics = this.testResults.drmMetrics; return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Load Test Results</title> <style> body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; } .container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .header { text-align: center; margin-bottom: 30px; } .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px; } .metric-card { background: #f8f9fa; padding: 15px; border-radius: 6px; text-align: center; } .metric-value { font-size: 2em; font-weight: bold; color: #007bff; } .metric-label { color: #666; margin-top: 5px; } .section { margin-bottom: 30px; } .section h2 { border-bottom: 2px solid #007bff; padding-bottom: 10px; } table { width: 100%; border-collapse: collapse; margin-top: 15px; } th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; } th { background-color: #f8f9fa; font-weight: bold; } .error { color: #dc3545; } .success { color: #28a745; } .warning { color: #ffc107; } .chart-placeholder { background: #f8f9fa; height: 200px; display: flex; align-items: center; justify-content: center; border-radius: 6px; margin: 15px 0; } </style> </head> <body> <div class="container"> <div class="header"> <h1>Load Test Results</h1> <p>Generated on ${new Date().toLocaleString()}</p> <p>Test Duration: ${summary.testDuration.toFixed(2)} seconds</p> </div> <div class="section"> <h2>Summary</h2> <div class="summary"> <div class="metric-card"> <div class="metric-value">${summary.totalRequests}</div> <div class="metric-label">Total Requests</div> </div> <div class="metric-card"> <div class="metric-value ${summary.successfulRequests > 0 ? 'success' : ''}">${summary.successfulRequests}</div> <div class="metric-label">Successful Requests</div> </div> <div class="metric-card"> <div class="metric-value ${summary.failedRequests > 0 ? 'error' : ''}">${summary.failedRequests}</div> <div class="metric-label">Failed Requests</div> </div> <div class="metric-card"> <div class="metric-value">${summary.averageResponseTime.toFixed(2)}ms</div> <div class="metric-label">Avg Response Time</div> </div> <div class="metric-card"> <div class="metric-value">${summary.peakConcurrentUsers}</div> <div class="metric-label">Peak Concurrent Users</div> </div> <div class="metric-card"> <div class="metric-value">${((summary.successfulRequests / summary.totalRequests) * 100).toFixed(1)}%</div> <div class="metric-label">Success Rate</div> </div> </div> </div> ${config.includeDetailedMetrics ? this.generateBrowserMetricsHTML() : ''} ${drmMetrics.length > 0 ? this.generateDRMMetricsHTML() : ''} ${config.includeErrorAnalysis && errors.length > 0 ? this.generateErrorAnalysisHTML() : ''} </div> </body> </html>`; } /** * Generate browser metrics section for HTML report */ generateBrowserMetricsHTML() { const browserMetrics = this.testResults.browserMetrics; if (browserMetrics.length === 0) { return ''; } const rows = browserMetrics.map(metrics => ` <tr> <td>${metrics.instanceId}</td> <td>${metrics.memoryUsage.toFixed(2)} MB</td> <td>${metrics.cpuUsage.toFixed(1)}%</td> <td>${metrics.requestCount}</td> <td>${metrics.errorCount}</td> <td>${metrics.uptime.toFixed(2)}s</td> </tr> `).join(''); return ` <div class="section"> <h2>Browser Instance Metrics</h2> <table> <thead> <tr> <th>Instance ID</th> <th>Memory Usage</th> <th>CPU Usage</th> <th>Requests</th> <th>Errors</th> <th>Uptime</th> </tr> </thead> <tbody> ${rows} </tbody> </table> </div> `; } /** * Generate DRM metrics section for HTML report */ generateDRMMetricsHTML() { const drmMetrics = this.testResults.drmMetrics; const rows = drmMetrics.map(metrics => ` <tr> <td>${metrics.drmType}</td> <td>${metrics.licenseRequestCount}</td> <td>${metrics.averageLicenseTime.toFixed(2)}ms</td> <td>${(metrics.licenseSuccessRate * 100).toFixed(1)}%</td> <td>${metrics.errors.length}</td> </tr> `).join(''); return ` <div class="section"> <h2>DRM Metrics</h2> <table> <thead> <tr> <th>DRM Type</th> <th>License Requests</th> <th>Avg License Time</th> <th>Success Rate</th> <th>Errors</th> </tr> </thead> <tbody> ${rows} </tbody> </table> </div> `; } /** * Generate error analysis section for HTML report */ generateErrorAnalysisHTML() { const errors = this.testResults.errors; const errorRows = errors.slice(0, 50).map(error => ` <tr> <td>${error.timestamp.toLocaleString()}</td> <td><span class="${error.level}">${error.level.toUpperCase()}</span></td> <td>${error.message}</td> <td>${error.context ? JSON.stringify(error.context) : '-'}</td> </tr> `).join(''); return ` <div class="section"> <h2>Error Analysis</h2> <p>Showing ${Math.min(50, errors.length)} of ${errors.length} total errors</p> <table> <thead> <tr> <th>Timestamp</th> <th>Level</th> <th>Message</th> <th>Context</th> </tr> </thead> <tbody> ${errorRows} </tbody> </table> </div> `; } /** * Generate summary CSV content */ generateSummaryCSV() { const summary = this.testResults.summary; const headers = [ 'Total Requests', 'Successful Requests', 'Failed Requests', 'Average Response Time (ms)', 'Peak Concurrent Users', 'Test Duration (s)', 'Success Rate (%)' ]; const values = [ summary.totalRequests, summary.successfulRequests, summary.failedRequests, summary.averageResponseTime.toFixed(2), summary.peakConcurrentUsers, summary.testDuration.toFixed(2), ((summary.successfulRequests / summary.totalRequests) * 100).toFixed(1) ]; return headers.join(',') + '\n' + values.join(','); } /** * Generate network metrics CSV content */ generateNetworkMetricsCSV() { const headers = [ 'URL', 'Method', 'Response Time (ms)', 'Status Code', 'Timestamp', 'Request Size (bytes)', 'Response Size (bytes)', 'Streaming Related', 'Streaming Type' ]; const rows = this.testResults.networkMetrics.map(metric => [ `"${metric.url}"`, metric.method, metric.responseTime, metric.statusCode, metric.timestamp.toISOString(), metric.requestSize, metric.responseSize, metric.isStreamingRelated || false, metric.streamingType || '' ].join(',')); return headers.join(',') + '\n' + rows.join('\n'); } /** * Generate browser metrics CSV content */ generateBrowserMetricsCSV() { const headers = [ 'Instance ID', 'Memory Usage (MB)', 'CPU Usage (%)', 'Request Count', 'Error Count', 'Uptime (s)' ]; const rows = this.testResults.browserMetrics.map(metric => [ metric.instanceId, metric.memoryUsage.toFixed(2), metric.cpuUsage.toFixed(1), metric.requestCount, metric.errorCount, metric.uptime.toFixed(2) ].join(',')); return headers.join(',') + '\n' + rows.join('\n'); } } exports.ResultsAggregator = ResultsAggregator; //# sourceMappingURL=results-aggregator.js.map