Files
cfw-autumn/server/benchmarks/benchmark-utils.ts
2025-06-18 11:20:19 +03:00

216 lines
6.7 KiB
TypeScript

import chalk from "chalk";
export interface BenchmarkResult {
name: string;
iterations: number;
totalTime: number;
averageTime: number;
minTime: number;
maxTime: number;
standardDeviation: number;
operationsPerSecond: number;
}
export interface BenchmarkOptions {
iterations?: number;
warmupIterations?: number;
dryRun?: boolean;
verbose?: boolean;
}
export class BenchmarkRunner {
private results: BenchmarkResult[] = [];
constructor(private options: BenchmarkOptions = {}) {
this.options = {
iterations: 100,
warmupIterations: 10,
dryRun: true,
verbose: false,
...options,
};
}
async run(name: string, operation: () => Promise<any> | any): Promise<BenchmarkResult> {
const { iterations = 100, warmupIterations = 10, verbose } = this.options;
// Warmup phase (silent)
for (let i = 0; i < warmupIterations; i++) {
await operation();
}
// Actual benchmark with progress
const times: number[] = [];
const startTime = Date.now();
for (let i = 0; i < iterations; i++) {
const start = performance.now();
await operation();
const end = performance.now();
times.push(end - start);
}
const result = this.calculateStats(name, times, iterations);
this.results.push(result);
// Show immediate result with progress indicator
const progress = `${this.results.length}`.padStart(2, ' ');
const avgColor = result.averageTime < 5 ? chalk.green : result.averageTime < 20 ? chalk.yellow : chalk.red;
console.log(`${chalk.gray(`[${progress}]`)} ${chalk.cyan(name.padEnd(35))} ${avgColor(`${result.averageTime.toFixed(2)}ms`)}`);
return result;
}
private calculateStats(name: string, times: number[], iterations: number): BenchmarkResult {
const totalTime = times.reduce((sum, time) => sum + time, 0);
const averageTime = totalTime / iterations;
const minTime = Math.min(...times);
const maxTime = Math.max(...times);
// Calculate standard deviation
const variance = times.reduce((sum, time) => sum + Math.pow(time - averageTime, 2), 0) / iterations;
const standardDeviation = Math.sqrt(variance);
const operationsPerSecond = 1000 / averageTime;
return {
name,
iterations,
totalTime,
averageTime,
minTime,
maxTime,
standardDeviation,
operationsPerSecond,
};
}
private printResult(result: BenchmarkResult) {
const { name, averageTime, minTime, maxTime, operationsPerSecond } = result;
// Color code performance: green for fast, yellow for medium, red for slow
const avgColor = averageTime < 5 ? chalk.green : averageTime < 20 ? chalk.yellow : chalk.red;
const opsColor = operationsPerSecond > 200 ? chalk.green : operationsPerSecond > 50 ? chalk.yellow : chalk.red;
console.log(chalk.cyan(`\n📊 ${name}`));
console.log(` ${avgColor(`${averageTime.toFixed(2)}ms avg`)} | ${chalk.gray(`${minTime.toFixed(2)}-${maxTime.toFixed(2)}ms range`)} | ${opsColor(`${operationsPerSecond.toFixed(0)} ops/sec`)}`);
}
printSummary() {
if (this.results.length === 0) return;
console.log(chalk.yellow('\n🏁 Performance Summary'));
console.log(chalk.yellow('─'.repeat(50)));
// Sort results by average time for better readability
const sortedResults = [...this.results].sort((a, b) => a.averageTime - b.averageTime);
sortedResults.forEach((result, index) => {
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : ' ';
const { name, averageTime, operationsPerSecond } = result;
const avgColor = averageTime < 5 ? chalk.green : averageTime < 20 ? chalk.yellow : chalk.red;
console.log(`${medal} ${chalk.cyan(name.padEnd(30))} ${avgColor(`${averageTime.toFixed(2)}ms`)} ${chalk.gray(`(${operationsPerSecond.toFixed(0)} ops/sec)`)}`);
});
// Performance insights
const totalTests = this.results.length;
const avgPerformance = this.results.reduce((sum, r) => sum + r.averageTime, 0) / totalTests;
const fastTests = this.results.filter(r => r.averageTime < 5).length;
console.log(chalk.gray(`\n💡 ${fastTests}/${totalTests} operations under 5ms | Average: ${avgPerformance.toFixed(2)}ms`));
}
getResults(): BenchmarkResult[] {
return [...this.results];
}
exportResults(filename?: string): string {
const data = {
timestamp: new Date().toISOString(),
options: this.options,
results: this.results,
};
const json = JSON.stringify(data, null, 2);
if (filename) {
// In a real implementation, you'd write to file here
console.log(chalk.blue(`📄 Results would be exported to: ${filename}`));
}
return json;
}
}
// Dry run helpers
export class DryRunHelper {
private static mockDatabase = new Map();
private static mockStripe = {
customers: { create: () => ({ id: 'cus_mock' }) },
prices: { create: () => ({ id: 'price_mock' }) },
products: { create: () => ({ id: 'prod_mock' }) },
};
static mockDbOperation<T>(operation: string, data?: any): T {
// Simulate database latency
const latency = Math.random() * 5; // 0-5ms
const start = performance.now();
while (performance.now() - start < latency) {
// Busy wait to simulate actual work
}
// Store/retrieve mock data
if (data) {
this.mockDatabase.set(operation, data);
}
return this.mockDatabase.get(operation) || { id: 'mock_id', ...data };
}
static mockStripeOperation<T>(operation: string, data?: any): T {
// Simulate Stripe API latency (higher than DB)
const latency = Math.random() * 50 + 10; // 10-60ms
const start = performance.now();
while (performance.now() - start < latency) {
// Busy wait to simulate network call
}
return { id: `stripe_mock_${Date.now()}`, ...data } as T;
}
static mockComplexCalculation(iterations: number = 1000): number {
// Simulate CPU-intensive calculation
let result = 0;
for (let i = 0; i < iterations; i++) {
result += Math.sqrt(i) * Math.sin(i);
}
return result;
}
}
// Utility to create mock data similar to test fixtures
export const createMockCustomer = (customerId: string) => ({
id: customerId,
internal_id: `internal_${customerId}`,
email: `${customerId}@example.com`,
created_at: new Date(),
balance: 1000,
entities: [],
});
export const createMockProduct = (productId: string) => ({
id: productId,
name: `Product ${productId}`,
type: 'subscription',
prices: [],
entitlements: [],
});
export const createMockEvent = (customerId: string, featureId: string, usage: number = 1) => ({
customer_id: customerId,
feature_id: featureId,
usage,
properties: {},
timestamp: new Date(),
});