UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

478 lines (477 loc) 17.7 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.PerformanceBenchmark = void 0; exports.createBenchmark = createBenchmark; exports.createSuite = createSuite; exports.runBenchmarks = runBenchmarks; const events_1 = require("events"); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const perf_hooks_1 = require("perf_hooks"); const os = __importStar(require("os")); const child_process_1 = require("child_process"); class PerformanceBenchmark extends events_1.EventEmitter { constructor(config) { super(); this.results = []; this.baseline = null; this.config = { iterations: 100, warmupRuns: 10, cooldownTime: 100, compareWithBaseline: true, generateReport: true, failOnRegression: true, regressionThreshold: 5, // 5% regression threshold outputPath: './benchmarks', includeSystemInfo: true, includeMemoryProfile: true, includeCpuProfile: true, ...config }; } async run() { this.emit('benchmark:start', { suites: this.config.suites.length }); const startTime = perf_hooks_1.performance.now(); try { // Load baseline if comparison is enabled if (this.config.compareWithBaseline) { await this.loadBaseline(); } // Collect system information const systemInfo = this.config.includeSystemInfo ? this.collectSystemInfo() : this.getMinimalSystemInfo(); // Run all benchmark suites for (const suite of this.config.suites) { await this.runSuite(suite); } // Generate report const report = this.generateReport(systemInfo, (perf_hooks_1.performance.now() - startTime) / 1000); // Save results as new baseline await this.saveBaseline(); // Check for regressions if (this.config.failOnRegression) { this.checkRegressions(report); } this.emit('benchmark:complete', report); return report; } catch (error) { this.emit('benchmark:error', error); throw error; } } async runSuite(suite) { this.emit('suite:start', suite); try { // Run setup if provided if (suite.setup) { await suite.setup(); } // Run each benchmark in the suite for (const benchmark of suite.benchmarks) { const result = await this.runBenchmark(suite, benchmark); this.results.push(result); } } finally { // Run teardown if provided if (suite.teardown) { await suite.teardown(); } } this.emit('suite:complete', suite); } async runBenchmark(suite, benchmark) { this.emit('benchmark:run', { suite: suite.name, benchmark: benchmark.name }); const options = { iterations: this.config.iterations, warmupRuns: this.config.warmupRuns, ...benchmark.options }; // Warmup runs for (let i = 0; i < options.warmupRuns; i++) { await this.executeBenchmark(benchmark); } // Cool down await this.wait(this.config.cooldownTime); // Force garbage collection if available if (global.gc) { global.gc(); } // Collect initial memory state const memoryBefore = this.config.includeMemoryProfile ? process.memoryUsage() : null; // Collect samples const samples = []; const cpuBefore = this.config.includeCpuProfile ? process.cpuUsage() : null; for (let i = 0; i < options.iterations; i++) { const duration = await this.executeBenchmark(benchmark); samples.push(duration); // Cool down between iterations if (i < options.iterations - 1) { await this.wait(10); } } const cpuAfter = this.config.includeCpuProfile ? process.cpuUsage(cpuBefore) : null; // Collect final memory state const memoryAfter = this.config.includeMemoryProfile ? process.memoryUsage() : null; // Calculate metrics const metrics = this.calculateMetrics(samples); // Check for regression const regression = this.checkRegression(suite.name, benchmark.name, metrics.mean); const result = { suite: suite.name, benchmark: benchmark.name, metrics, samples, regression, timestamp: new Date(), systemInfo: this.config.includeSystemInfo ? this.collectSystemInfo() : undefined, memoryProfile: this.createMemoryProfile(memoryBefore, memoryAfter), cpuProfile: this.createCpuProfile(cpuBefore, cpuAfter, samples.length) }; this.emit('benchmark:result', result); return result; } async executeBenchmark(benchmark) { const start = perf_hooks_1.performance.now(); try { const result = benchmark.fn(); if (result instanceof Promise) { await result; } } catch (error) { this.emit('benchmark:error', { benchmark: benchmark.name, error }); throw error; } return perf_hooks_1.performance.now() - start; } calculateMetrics(samples) { const sorted = [...samples].sort((a, b) => a - b); const n = sorted.length; const mean = samples.reduce((a, b) => a + b, 0) / n; const median = n % 2 === 0 ? (sorted[n / 2 - 1] + sorted[n / 2]) / 2 : sorted[Math.floor(n / 2)]; const min = sorted[0]; const max = sorted[n - 1]; // Calculate standard deviation const variance = samples.reduce((acc, val) => { return acc + Math.pow(val - mean, 2); }, 0) / n; const stdDev = Math.sqrt(variance); // Calculate margin of error (95% confidence interval) const marginOfError = 1.96 * (stdDev / Math.sqrt(n)); const relativeMarginOfError = (marginOfError / mean) * 100; // Operations per second const ops = 1000 / mean; return { mean, median, min, max, stdDev, marginOfError, relativeMarginOfError, samples: n, ops }; } checkRegression(suite, benchmark, current) { if (!this.baseline || !this.config.compareWithBaseline) { return undefined; } const key = `${suite}/${benchmark}`; const baselineResult = this.baseline.results.get(key); if (!baselineResult) { return undefined; } const baseline = baselineResult.metrics.mean; const difference = current - baseline; const percentage = (difference / baseline) * 100; return { detected: percentage > this.config.regressionThreshold, baseline, current, difference, percentage, threshold: this.config.regressionThreshold }; } collectSystemInfo() { const cpus = os.cpus(); return { platform: os.platform(), arch: os.arch(), cpus: cpus.length, cpuModel: cpus[0]?.model || 'Unknown', totalMemory: os.totalmem(), nodeVersion: process.version, v8Version: process.versions.v8 }; } getMinimalSystemInfo() { return { platform: os.platform(), arch: os.arch(), cpus: os.cpus().length, cpuModel: 'Not collected', totalMemory: 0, nodeVersion: process.version, v8Version: process.versions.v8 }; } createMemoryProfile(before, after) { if (!before || !after) return undefined; return { heapUsedBefore: before.heapUsed, heapUsedAfter: after.heapUsed, heapDelta: after.heapUsed - before.heapUsed, external: after.external, arrayBuffers: after.arrayBuffers || 0, gcRuns: 0 // Would need to track GC events }; } createCpuProfile(before, after, samples) { if (!before || !after) return undefined; const userTime = (after.user - before.user) / 1000; // Convert to ms const systemTime = (after.system - before.system) / 1000; const totalTime = userTime + systemTime; return { userTime, systemTime, totalTime, cpuUsage: (totalTime / samples) * 100 }; } async loadBaseline() { const baselinePath = path.join(this.config.outputPath, 'baseline.json'); if (await fs.pathExists(baselinePath)) { const data = await fs.readJson(baselinePath); this.baseline = { version: data.version, timestamp: new Date(data.timestamp), results: new Map(data.results) }; this.emit('baseline:loaded', this.baseline); } } async saveBaseline() { const baselinePath = path.join(this.config.outputPath, 'baseline.json'); await fs.ensureDir(this.config.outputPath); const results = []; for (const result of this.results) { const key = `${result.suite}/${result.benchmark}`; results.push([key, result]); } const baseline = { version: this.getVersion(), timestamp: new Date(), results }; await fs.writeJson(baselinePath, baseline, { spaces: 2 }); this.emit('baseline:saved', baselinePath); } generateReport(systemInfo, duration) { const summary = { totalBenchmarks: this.results.length, totalSuites: new Set(this.results.map(r => r.suite)).size, totalSamples: this.results.reduce((acc, r) => acc + r.samples.length, 0), totalDuration: duration, regressions: this.results.filter(r => r.regression?.detected).length, improvements: this.results.filter(r => r.regression && !r.regression.detected && r.regression.percentage < -5).length }; const comparisons = this.generateComparisons(); const report = { summary, results: this.results, comparisons, systemInfo, timestamp: new Date(), duration }; if (this.config.generateReport) { this.saveReport(report); } return report; } generateComparisons() { if (!this.baseline) return undefined; const comparisons = []; for (const result of this.results) { const key = `${result.suite}/${result.benchmark}`; const baselineResult = this.baseline.results.get(key); if (baselineResult) { const change = result.metrics.mean - baselineResult.metrics.mean; const percentage = (change / baselineResult.metrics.mean) * 100; comparisons.push({ suite: result.suite, benchmark: result.benchmark, baseline: baselineResult.metrics, current: result.metrics, change: { absolute: change, percentage, significant: Math.abs(percentage) > 5 } }); } } return comparisons; } async saveReport(report) { const reportPath = path.join(this.config.outputPath, `benchmark-${Date.now()}.json`); await fs.ensureDir(this.config.outputPath); await fs.writeJson(reportPath, report, { spaces: 2 }); // Also save a human-readable report const readablePath = reportPath.replace('.json', '.txt'); await fs.writeFile(readablePath, this.formatReport(report)); this.emit('report:saved', { json: reportPath, txt: readablePath }); } formatReport(report) { const lines = [ 'Performance Benchmark Report', '===========================', '', `Date: ${report.timestamp.toISOString()}`, `Duration: ${report.duration.toFixed(2)}s`, '', 'System Information:', ` Platform: ${report.systemInfo.platform} ${report.systemInfo.arch}`, ` CPUs: ${report.systemInfo.cpus} x ${report.systemInfo.cpuModel}`, ` Node: ${report.systemInfo.nodeVersion}`, '', 'Summary:', ` Total Benchmarks: ${report.summary.totalBenchmarks}`, ` Total Samples: ${report.summary.totalSamples}`, ` Regressions: ${report.summary.regressions}`, ` Improvements: ${report.summary.improvements}`, '', 'Results:', '' ]; // Group results by suite const suites = new Map(); for (const result of report.results) { if (!suites.has(result.suite)) { suites.set(result.suite, []); } suites.get(result.suite).push(result); } for (const [suite, results] of suites) { lines.push(`${suite}:`); for (const result of results) { const metrics = result.metrics; lines.push(` ${result.benchmark}:`); lines.push(` Mean: ${metrics.mean.toFixed(3)}ms (±${metrics.relativeMarginOfError.toFixed(1)}%)`); lines.push(` Ops/sec: ${metrics.ops.toFixed(0)}`); if (result.regression) { const r = result.regression; const symbol = r.detected ? '⚠️ ' : r.percentage < -5 ? '✅ ' : ''; lines.push(` ${symbol}Change: ${r.percentage > 0 ? '+' : ''}${r.percentage.toFixed(1)}%`); } if (result.memoryProfile) { const mb = (bytes) => (bytes / 1048576).toFixed(2); lines.push(` Memory: ${mb(result.memoryProfile.heapDelta)}MB`); } lines.push(''); } } return lines.join('\n'); } checkRegressions(report) { if (report.summary.regressions > 0) { const regressions = this.results .filter(r => r.regression?.detected) .map(r => `${r.suite}/${r.benchmark}: +${r.regression.percentage.toFixed(1)}%`); const error = new Error(`Performance regressions detected:\n${regressions.join('\n')}`); error.regressions = regressions; throw error; } } getVersion() { try { return (0, child_process_1.execSync)('git describe --tags --always', { encoding: 'utf-8' }).trim(); } catch { return 'unknown'; } } wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Public utility methods addSuite(suite) { this.config.suites.push(suite); } getResults() { return this.results; } compareResults(a, b) { const diff = a.metrics.mean - b.metrics.mean; const percentage = (diff / b.metrics.mean) * 100; return percentage; } } exports.PerformanceBenchmark = PerformanceBenchmark; // Export utility functions function createBenchmark(name, fn, options) { return { name, fn, options }; } function createSuite(name, benchmarks, options) { return { name, benchmarks, ...options }; } async function runBenchmarks(suites, config) { const benchmark = new PerformanceBenchmark({ suites, ...config }); return benchmark.run(); }