@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
432 lines • 18.7 kB
JavaScript
/**
* @fileoverview OrdoJS Performance Benchmarker - Comprehensive performance measurement and optimization
*/
import { OptimizationError, OptimizationType } 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';
/**
* Default benchmark configuration
*/
const DEFAULT_BENCHMARK_CONFIG = {
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 {
config;
baselineMetrics = new Map();
errors = [];
constructor(config = {}) {
this.config = { ...DEFAULT_BENCHMARK_CONFIG, ...config };
}
/**
* Benchmark a component's performance
*/
async benchmarkComponent(source, componentName, baselineName) {
this.errors = [];
const measurements = [];
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;
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, metrics) {
this.baselineMetrics.set(name, metrics);
}
/**
* Get baseline metrics
*/
getBaseline(name) {
return this.baselineMetrics.get(name);
}
/**
* Get benchmark errors
*/
getErrors() {
return [...this.errors];
}
/**
* Measure performance of a single compilation
*/
async measurePerformance(source, componentName) {
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;
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
*/
calculateAverageMetrics(measurements) {
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
*/
validatePerformance(metrics) {
const failures = [];
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
*/
generateSuggestions(metrics, validation) {
const suggestions = [];
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
*/
compareWithBaseline(current, baseline) {
const calculateImprovement = (current, baseline) => {
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
*/
calculateBundleSize(generatedCode) {
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)
*/
calculateGzippedSize(generatedCode) {
// 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)
*/
calculateBrotliSize(generatedCode) {
// 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
*/
countASTNodes(component) {
let count = 1; // Count the component itself
const countNodes = (node) => {
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
*/
getMemoryUsage() {
if (typeof process !== 'undefined' && process.memoryUsage) {
return process.memoryUsage().heapUsed;
}
return 0;
}
/**
* Measure runtime performance
*/
async measureRuntimePerformance(generatedCode, componentName) {
// 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) {
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;
}
}
//# sourceMappingURL=performance-benchmarker.js.map