@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
JavaScript
"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();
}