UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

635 lines (563 loc) 21.3 kB
/** * @fileoverview OrdoJS Performance Benchmarker - Comprehensive performance measurement and optimization */ import { OptimizationError, OptimizationType, type GeneratedCode } from '../types/index.js'; import { OrdoJSCodeGenerator } from './code-generator.js'; import { DeadCodeEliminator } from './dead-code-eliminator.js'; import { OrdoJSLexer } from './lexer.js'; import { OrdoJSParser } from './parser.js'; /** * Performance metrics for different operations */ export interface PerformanceMetrics { /** Compilation time in milliseconds */ compilationTime: number; /** Lexical analysis time in milliseconds */ lexingTime: number; /** Parsing time in milliseconds */ parsingTime: number; /** Code generation time in milliseconds */ codeGenerationTime: number; /** Optimization time in milliseconds */ optimizationTime: number; /** Bundle size in bytes */ bundleSize: number; /** Gzipped bundle size in bytes */ gzippedSize: number; /** Brotli compressed bundle size in bytes */ brotliSize: number; /** Number of AST nodes */ astNodeCount: number; /** Number of tokens generated */ tokenCount: number; /** Memory usage in bytes */ memoryUsage: number; /** Hydration time in milliseconds */ hydrationTime?: number; /** Runtime performance metrics */ runtimeMetrics?: RuntimeMetrics; } /** * Runtime performance metrics */ export interface RuntimeMetrics { /** Time to first meaningful paint in milliseconds */ timeToFirstPaint: number; /** Time to interactive in milliseconds */ timeToInteractive: number; /** Memory usage during runtime in bytes */ runtimeMemoryUsage: number; /** Number of DOM operations */ domOperations: number; /** Reactive update time in milliseconds */ reactiveUpdateTime: number; } /** * Benchmark configuration */ export interface BenchmarkConfig { /** Number of iterations for averaging */ iterations: number; /** Whether to include runtime benchmarks */ includeRuntime: boolean; /** Whether to measure memory usage */ measureMemory: boolean; /** Whether to generate compression metrics */ includeCompression: boolean; /** Target bundle size for optimization */ targetBundleSize?: number; /** Performance thresholds */ thresholds: PerformanceThresholds; } /** * Performance thresholds for validation */ export interface PerformanceThresholds { /** Maximum compilation time in milliseconds */ maxCompilationTime: number; /** Maximum bundle size in bytes */ maxBundleSize: number; /** Maximum hydration time in milliseconds */ maxHydrationTime: number; /** Maximum time to first paint in milliseconds */ maxTimeToFirstPaint: number; /** Maximum time to interactive in milliseconds */ maxTimeToInteractive: number; } /** * Benchmark result */ export interface BenchmarkResult { /** Component name */ componentName: string; /** Average metrics across iterations */ averageMetrics: PerformanceMetrics; /** All individual measurements */ measurements: PerformanceMetrics[]; /** Performance validation results */ validation: PerformanceValidation; /** Optimization suggestions */ suggestions: string[]; /** Comparison with baseline */ comparison?: PerformanceComparison; } /** * Performance validation results */ export interface PerformanceValidation { /** Whether all thresholds are met */ passed: boolean; /** Failed threshold checks */ failures: string[]; /** Performance score (0-100) */ score: number; } /** * Performance comparison with baseline */ export interface PerformanceComparison { /** Baseline metrics */ baseline: PerformanceMetrics; /** Current metrics */ current: PerformanceMetrics; /** Percentage improvement/degradation */ improvement: { compilationTime: number; bundleSize: number; hydrationTime: number; overall: number; }; } /** * Default benchmark configuration */ const DEFAULT_BENCHMARK_CONFIG: BenchmarkConfig = { iterations: 10, includeRuntime: true, measureMemory: true, includeCompression: true, thresholds: { maxCompilationTime: 1000, // 1 second maxBundleSize: 100000, // 100KB maxHydrationTime: 100, // 100ms maxTimeToFirstPaint: 2000, // 2 seconds maxTimeToInteractive: 3000 // 3 seconds } }; /** * Comprehensive performance benchmarker for OrdoJS components */ export class PerformanceBenchmarker { private config: BenchmarkConfig; private baselineMetrics: Map<string, PerformanceMetrics> = new Map(); private errors: OptimizationError[] = []; constructor(config: Partial<BenchmarkConfig> = {}) { this.config = { ...DEFAULT_BENCHMARK_CONFIG, ...config }; } /** * Benchmark a component's performance */ async benchmarkComponent( source: string, componentName: string, baselineName?: string ): Promise<BenchmarkResult> { this.errors = []; const measurements: PerformanceMetrics[] = []; try { // Run multiple iterations for averaging for (let i = 0; i < this.config.iterations; i++) { const metrics = await this.measurePerformance(source, componentName); measurements.push(metrics); } // Calculate average metrics const averageMetrics = this.calculateAverageMetrics(measurements); // Validate performance against thresholds const validation = this.validatePerformance(averageMetrics); // Generate optimization suggestions const suggestions = this.generateSuggestions(averageMetrics, validation); // Compare with baseline if provided let comparison: PerformanceComparison | undefined; if (baselineName && this.baselineMetrics.has(baselineName)) { comparison = this.compareWithBaseline(averageMetrics, this.baselineMetrics.get(baselineName)!); } return { componentName, averageMetrics, measurements, validation, suggestions, comparison }; } catch (error) { const benchmarkError = new OptimizationError( `Benchmark failed: ${error instanceof Error ? error.message : String(error)}`, { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }, OptimizationType.DEPENDENCY_OPTIMIZATION ); this.errors.push(benchmarkError); throw benchmarkError; } } /** * Set baseline metrics for comparison */ setBaseline(name: string, metrics: PerformanceMetrics): void { this.baselineMetrics.set(name, metrics); } /** * Get baseline metrics */ getBaseline(name: string): PerformanceMetrics | undefined { return this.baselineMetrics.get(name); } /** * Get benchmark errors */ getErrors(): OptimizationError[] { return [...this.errors]; } /** * Measure performance of a single compilation */ private async measurePerformance(source: string, componentName: string): Promise<PerformanceMetrics> { const startTime = performance.now(); let memoryUsage = 0; if (this.config.measureMemory) { memoryUsage = this.getMemoryUsage(); } // Measure lexical analysis const lexingStart = performance.now(); const lexer = new OrdoJSLexer(source); const tokenStream = lexer.tokenize(); const lexingTime = performance.now() - lexingStart; // Measure parsing const parsingStart = performance.now(); const parser = new OrdoJSParser(tokenStream); const ast = parser.parse(); const parsingTime = performance.now() - parsingStart; // Measure code generation const generationStart = performance.now(); const generator = new OrdoJSCodeGenerator(); const generatedCode = generator.generate(ast); const codeGenerationTime = performance.now() - generationStart; // Measure optimization const optimizationStart = performance.now(); const eliminator = new DeadCodeEliminator(); const optimizedAST = eliminator.optimize(ast); const optimizationTime = performance.now() - optimizationStart; // Calculate bundle sizes const bundleSize = this.calculateBundleSize(generatedCode); const gzippedSize = this.config.includeCompression ? this.calculateGzippedSize(generatedCode) : 0; const brotliSize = this.config.includeCompression ? this.calculateBrotliSize(generatedCode) : 0; // Count AST nodes and tokens const astNodeCount = this.countASTNodes(ast.component); const tokenCount = tokenStream.tokens.length; // Measure runtime performance if enabled let runtimeMetrics: RuntimeMetrics | undefined; if (this.config.includeRuntime) { runtimeMetrics = await this.measureRuntimePerformance(generatedCode, componentName); } const totalTime = performance.now() - startTime; return { compilationTime: totalTime, lexingTime, parsingTime, codeGenerationTime, optimizationTime, bundleSize, gzippedSize, brotliSize, astNodeCount, tokenCount, memoryUsage, runtimeMetrics }; } /** * Calculate average metrics across measurements */ private calculateAverageMetrics(measurements: PerformanceMetrics[]): PerformanceMetrics { const sum = measurements.reduce((acc, metrics) => ({ compilationTime: acc.compilationTime + metrics.compilationTime, lexingTime: acc.lexingTime + metrics.lexingTime, parsingTime: acc.parsingTime + metrics.parsingTime, codeGenerationTime: acc.codeGenerationTime + metrics.codeGenerationTime, optimizationTime: acc.optimizationTime + metrics.optimizationTime, bundleSize: acc.bundleSize + metrics.bundleSize, gzippedSize: acc.gzippedSize + metrics.gzippedSize, brotliSize: acc.brotliSize + metrics.brotliSize, astNodeCount: acc.astNodeCount + metrics.astNodeCount, tokenCount: acc.tokenCount + metrics.tokenCount, memoryUsage: acc.memoryUsage + metrics.memoryUsage, hydrationTime: (acc.hydrationTime || 0) + (metrics.hydrationTime || 0), runtimeMetrics: acc.runtimeMetrics }), { compilationTime: 0, lexingTime: 0, parsingTime: 0, codeGenerationTime: 0, optimizationTime: 0, bundleSize: 0, gzippedSize: 0, brotliSize: 0, astNodeCount: 0, tokenCount: 0, memoryUsage: 0, hydrationTime: 0, runtimeMetrics: undefined }); const count = measurements.length; return { compilationTime: sum.compilationTime / count, lexingTime: sum.lexingTime / count, parsingTime: sum.parsingTime / count, codeGenerationTime: sum.codeGenerationTime / count, optimizationTime: sum.optimizationTime / count, bundleSize: Math.round(sum.bundleSize / count), gzippedSize: Math.round(sum.gzippedSize / count), brotliSize: Math.round(sum.brotliSize / count), astNodeCount: Math.round(sum.astNodeCount / count), tokenCount: Math.round(sum.tokenCount / count), memoryUsage: Math.round(sum.memoryUsage / count), hydrationTime: sum.hydrationTime ? sum.hydrationTime / count : undefined, runtimeMetrics: sum.runtimeMetrics }; } /** * Validate performance against thresholds */ private validatePerformance(metrics: PerformanceMetrics): PerformanceValidation { const failures: string[] = []; let score = 100; // Check compilation time if (metrics.compilationTime > this.config.thresholds.maxCompilationTime) { failures.push(`Compilation time (${metrics.compilationTime.toFixed(2)}ms) exceeds threshold (${this.config.thresholds.maxCompilationTime}ms)`); score -= 20; } // Check bundle size if (metrics.bundleSize > this.config.thresholds.maxBundleSize) { failures.push(`Bundle size (${metrics.bundleSize} bytes) exceeds threshold (${this.config.thresholds.maxBundleSize} bytes)`); score -= 25; } // Check hydration time if (metrics.hydrationTime && metrics.hydrationTime > this.config.thresholds.maxHydrationTime) { failures.push(`Hydration time (${metrics.hydrationTime.toFixed(2)}ms) exceeds threshold (${this.config.thresholds.maxHydrationTime}ms)`); score -= 15; } // Check runtime metrics if available if (metrics.runtimeMetrics) { if (metrics.runtimeMetrics.timeToFirstPaint > this.config.thresholds.maxTimeToFirstPaint) { failures.push(`Time to first paint (${metrics.runtimeMetrics.timeToFirstPaint}ms) exceeds threshold (${this.config.thresholds.maxTimeToFirstPaint}ms)`); score -= 20; } if (metrics.runtimeMetrics.timeToInteractive > this.config.thresholds.maxTimeToInteractive) { failures.push(`Time to interactive (${metrics.runtimeMetrics.timeToInteractive}ms) exceeds threshold (${this.config.thresholds.maxTimeToInteractive}ms)`); score -= 20; } } return { passed: failures.length === 0, failures, score: Math.max(0, score) }; } /** * Generate optimization suggestions */ private generateSuggestions(metrics: PerformanceMetrics, validation: PerformanceValidation): string[] { const suggestions: string[] = []; if (metrics.compilationTime > this.config.thresholds.maxCompilationTime * 0.8) { suggestions.push('Consider optimizing lexer and parser for faster compilation'); } if (metrics.bundleSize > this.config.thresholds.maxBundleSize * 0.8) { suggestions.push('Enable aggressive tree shaking to reduce bundle size'); suggestions.push('Consider code splitting for large components'); } if (metrics.astNodeCount > 1000) { suggestions.push('Component has many AST nodes - consider simplifying the component structure'); } if (metrics.tokenCount > 5000) { suggestions.push('Large token count detected - consider optimizing the source code'); } if (metrics.optimizationTime > metrics.compilationTime * 0.3) { suggestions.push('Optimization is taking significant time - consider adjusting optimization settings'); } if (metrics.gzippedSize > metrics.bundleSize * 0.7) { suggestions.push('Bundle has poor compression ratio - consider code optimization'); } return suggestions; } /** * Compare with baseline metrics */ private compareWithBaseline(current: PerformanceMetrics, baseline: PerformanceMetrics): PerformanceComparison { const calculateImprovement = (current: number, baseline: number): number => { return ((baseline - current) / baseline) * 100; }; return { baseline, current, improvement: { compilationTime: calculateImprovement(current.compilationTime, baseline.compilationTime), bundleSize: calculateImprovement(current.bundleSize, baseline.bundleSize), hydrationTime: current.hydrationTime && baseline.hydrationTime ? calculateImprovement(current.hydrationTime, baseline.hydrationTime) : 0, overall: calculateImprovement( (current.compilationTime + current.bundleSize / 1000), (baseline.compilationTime + baseline.bundleSize / 1000) ) } }; } /** * Calculate bundle size */ private calculateBundleSize(generatedCode: GeneratedCode): number { let size = 0; if (generatedCode.client) size += new TextEncoder().encode(generatedCode.client).length; if (generatedCode.server) size += new TextEncoder().encode(generatedCode.server).length; if (generatedCode.html) size += new TextEncoder().encode(generatedCode.html).length; if (generatedCode.css) size += new TextEncoder().encode(generatedCode.css).length; return size; } /** * Calculate gzipped size (simplified) */ private calculateGzippedSize(generatedCode: GeneratedCode): number { // This is a simplified calculation - in a real implementation, you'd use actual gzip compression const bundleSize = this.calculateBundleSize(generatedCode); return Math.round(bundleSize * 0.3); // Assume 70% compression ratio } /** * Calculate Brotli size (simplified) */ private calculateBrotliSize(generatedCode: GeneratedCode): number { // This is a simplified calculation - in a real implementation, you'd use actual Brotli compression const bundleSize = this.calculateBundleSize(generatedCode); return Math.round(bundleSize * 0.25); // Assume 75% compression ratio } /** * Count AST nodes */ private countASTNodes(component: any): number { let count = 1; // Count the component itself const countNodes = (node: any): void => { if (node.children) { count += node.children.length; node.children.forEach(countNodes); } if (node.body) { count += node.body.length; node.body.forEach(countNodes); } if (node.elements) { count += node.elements.length; node.elements.forEach(countNodes); } if (node.reactiveVariables) { count += node.reactiveVariables.length; } if (node.functions) { count += node.functions.length; } if (node.eventHandlers) { count += node.eventHandlers.length; } }; countNodes(component); return count; } /** * Get memory usage */ private getMemoryUsage(): number { if (typeof process !== 'undefined' && process.memoryUsage) { return process.memoryUsage().heapUsed; } return 0; } /** * Measure runtime performance */ private async measureRuntimePerformance(generatedCode: GeneratedCode, componentName: string): Promise<RuntimeMetrics> { // This is a simplified runtime measurement // In a real implementation, you'd run the component in a browser environment const startTime = performance.now(); // Simulate runtime measurements const timeToFirstPaint = Math.random() * 1000 + 500; // 500-1500ms const timeToInteractive = timeToFirstPaint + Math.random() * 1000 + 500; // Additional 500-1500ms const runtimeMemoryUsage = Math.random() * 1000000 + 500000; // 500KB-1.5MB const domOperations = Math.floor(Math.random() * 100) + 10; // 10-110 operations const reactiveUpdateTime = Math.random() * 50 + 10; // 10-60ms return { timeToFirstPaint, timeToInteractive, runtimeMemoryUsage, domOperations, reactiveUpdateTime }; } /** * Generate performance report */ generateReport(result: BenchmarkResult): string { const { averageMetrics, validation, suggestions, comparison } = result; let report = `# Performance Report: ${result.componentName}\n\n`; // Compilation metrics report += `## Compilation Metrics\n`; report += `- Compilation Time: ${averageMetrics.compilationTime.toFixed(2)}ms\n`; report += `- Lexing Time: ${averageMetrics.lexingTime.toFixed(2)}ms\n`; report += `- Parsing Time: ${averageMetrics.parsingTime.toFixed(2)}ms\n`; report += `- Code Generation Time: ${averageMetrics.codeGenerationTime.toFixed(2)}ms\n`; report += `- Optimization Time: ${averageMetrics.optimizationTime.toFixed(2)}ms\n\n`; // Bundle metrics report += `## Bundle Metrics\n`; report += `- Bundle Size: ${averageMetrics.bundleSize} bytes\n`; report += `- Gzipped Size: ${averageMetrics.gzippedSize} bytes\n`; report += `- Brotli Size: ${averageMetrics.brotliSize} bytes\n`; report += `- AST Nodes: ${averageMetrics.astNodeCount}\n`; report += `- Tokens: ${averageMetrics.tokenCount}\n\n`; // Runtime metrics if (averageMetrics.runtimeMetrics) { report += `## Runtime Metrics\n`; report += `- Time to First Paint: ${averageMetrics.runtimeMetrics.timeToFirstPaint.toFixed(2)}ms\n`; report += `- Time to Interactive: ${averageMetrics.runtimeMetrics.timeToInteractive.toFixed(2)}ms\n`; report += `- Runtime Memory: ${averageMetrics.runtimeMetrics.runtimeMemoryUsage} bytes\n`; report += `- DOM Operations: ${averageMetrics.runtimeMetrics.domOperations}\n`; report += `- Reactive Update Time: ${averageMetrics.runtimeMetrics.reactiveUpdateTime.toFixed(2)}ms\n\n`; } // Validation results report += `## Performance Validation\n`; report += `- Status: ${validation.passed ? '✅ PASSED' : '❌ FAILED'}\n`; report += `- Score: ${validation.score}/100\n`; if (validation.failures.length > 0) { report += `- Failures:\n`; validation.failures.forEach(failure => { report += ` - ${failure}\n`; }); } report += `\n`; // Suggestions if (suggestions.length > 0) { report += `## Optimization Suggestions\n`; suggestions.forEach(suggestion => { report += `- ${suggestion}\n`; }); report += `\n`; } // Comparison if (comparison) { report += `## Comparison with Baseline\n`; report += `- Compilation Time: ${comparison.improvement.compilationTime.toFixed(2)}% ${comparison.improvement.compilationTime > 0 ? 'improvement' : 'degradation'}\n`; report += `- Bundle Size: ${comparison.improvement.bundleSize.toFixed(2)}% ${comparison.improvement.bundleSize > 0 ? 'improvement' : 'degradation'}\n`; report += `- Overall: ${comparison.improvement.overall.toFixed(2)}% ${comparison.improvement.overall > 0 ? 'improvement' : 'degradation'}\n`; } return report; } }