UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

305 lines 11.4 kB
/** * PerformanceProfiler - NFR performance measurement with statistical rigor * * Provides high-precision performance measurement, statistical analysis, * and memory profiling for validating NFR performance targets. * * Features: * - Sub-millisecond precision timing using performance.now() * - Statistical analysis (95th percentile, 95% confidence intervals) * - Outlier detection and filtering (IQR method) * - Memory profiling support * - Async operation support * - Comprehensive reporting * * @module testing/performance-profiler */ /** * PerformanceProfiler - Measure and validate NFR performance targets * * @example * ```typescript * const profiler = new PerformanceProfiler(); * * // Measure synchronous operation * const result = profiler.measureSync(() => { * // Operation to measure * }, 1000); * * console.log(`P95 latency: ${result.p95.toFixed(3)}ms`); * console.log(`95% CI: [${result.confidenceInterval[0].toFixed(3)}, ${result.confidenceInterval[1].toFixed(3)}]ms`); * ``` */ export class PerformanceProfiler { options; constructor(options = {}) { this.options = { warmupIterations: options.warmupIterations ?? 10, filterOutliers: options.filterOutliers ?? false, confidenceLevel: options.confidenceLevel ?? 0.95, }; } /** * Measure synchronous operation performance * * @param fn - Function to measure * @param iterations - Number of measurement iterations * @returns Performance measurement results with statistics */ measureSync(fn, iterations) { if (iterations <= 0) { throw new Error('Iterations must be positive'); } // Warmup phase to stabilize JIT compilation for (let i = 0; i < this.options.warmupIterations; i++) { fn(); } // Measurement phase const samples = []; for (let i = 0; i < iterations; i++) { const start = performance.now(); fn(); const end = performance.now(); samples.push(end - start); } return this.analyzeResults(samples, iterations); } /** * Measure asynchronous operation performance * * @param fn - Async function to measure * @param iterations - Number of measurement iterations * @returns Performance measurement results with statistics */ async measureAsync(fn, iterations) { if (iterations <= 0) { throw new Error('Iterations must be positive'); } // Warmup phase for (let i = 0; i < this.options.warmupIterations; i++) { await fn(); } // Measurement phase const samples = []; for (let i = 0; i < iterations; i++) { const start = performance.now(); await fn(); const end = performance.now(); samples.push(end - start); } return this.analyzeResults(samples, iterations); } /** * Calculate specific percentile from samples * * @param samples - Array of measurements * @param percentile - Percentile to calculate (0-100) * @returns Percentile value */ calculatePercentile(samples, percentile) { if (samples.length === 0) { throw new Error('Cannot calculate percentile of empty sample set'); } if (percentile < 0 || percentile > 100) { throw new Error('Percentile must be between 0 and 100'); } const sorted = [...samples].sort((a, b) => a - b); const index = (percentile / 100) * (sorted.length - 1); if (Number.isInteger(index)) { return sorted[index]; } // Linear interpolation for non-integer indices const lower = Math.floor(index); const upper = Math.ceil(index); const weight = index - lower; return sorted[lower] * (1 - weight) + sorted[upper] * weight; } /** * Calculate confidence interval using t-distribution * * @param samples - Array of measurements * @param confidence - Confidence level (0-1) * @returns Confidence interval [lower, upper] bounds */ calculateConfidenceInterval(samples, confidence) { if (samples.length < 2) { throw new Error('Need at least 2 samples to calculate confidence interval'); } if (confidence <= 0 || confidence >= 1) { throw new Error('Confidence level must be between 0 and 1'); } const n = samples.length; const mean = this.calculateMean(samples); const stddev = this.calculateStdDev(samples, mean); // Standard error of the mean const sem = stddev / Math.sqrt(n); // Critical value from t-distribution (approximation for large n) // For n >= 30, t-distribution ≈ normal distribution const tCritical = this.getTCriticalValue(n - 1, confidence); // Margin of error const marginOfError = tCritical * sem; return [mean - marginOfError, mean + marginOfError]; } /** * Measure memory usage of an operation * * @param fn - Function to measure memory usage * @returns Memory usage statistics */ measureMemory(fn) { // Force garbage collection if available (requires --expose-gc flag) if (global.gc) { global.gc(); } const memBefore = process.memoryUsage(); fn(); const memAfter = process.memoryUsage(); return { heapUsed: memAfter.heapUsed - memBefore.heapUsed, heapTotal: memAfter.heapTotal - memBefore.heapTotal, external: memAfter.external - memBefore.external, arrayBuffers: memAfter.arrayBuffers - memBefore.arrayBuffers, }; } /** * Generate formatted performance report * * @param results - Array of performance results to report * @returns Formatted report string */ generateReport(results) { if (results.length === 0) { return 'No performance results to report'; } const lines = [ '╔════════════════════════════════════════════════════════════╗', '║ Performance Profiler Report ║', '╚════════════════════════════════════════════════════════════╝', '', ]; results.forEach((result, index) => { lines.push(`Measurement ${index + 1}:`); lines.push(` Iterations: ${result.iterations}`); lines.push(` Mean: ${result.mean.toFixed(3)} ms`); lines.push(` Median: ${result.median.toFixed(3)} ms`); lines.push(` P95: ${result.p95.toFixed(3)} ms`); lines.push(` P99: ${result.p99.toFixed(3)} ms`); lines.push(` Min: ${result.min.toFixed(3)} ms`); lines.push(` Max: ${result.max.toFixed(3)} ms`); lines.push(` Std Dev: ${result.stddev.toFixed(3)} ms`); lines.push(` 95% CI: [${result.confidenceInterval[0].toFixed(3)}, ${result.confidenceInterval[1].toFixed(3)}] ms`); if (result.outliersRemoved !== undefined && result.outliersRemoved > 0) { lines.push(` Outliers: ${result.outliersRemoved} removed`); } lines.push(''); }); return lines.join('\n'); } /** * Analyze raw samples and compute statistics * * @private */ analyzeResults(rawSamples, iterations) { let samples = rawSamples; let outliersRemoved = 0; // Filter outliers if enabled if (this.options.filterOutliers) { const filtered = this.filterOutliers(samples); samples = filtered.filtered; outliersRemoved = filtered.outliers.length; } // Calculate statistics const mean = this.calculateMean(samples); const stddev = this.calculateStdDev(samples, mean); const median = this.calculatePercentile(samples, 50); const p95 = this.calculatePercentile(samples, 95); const p99 = this.calculatePercentile(samples, 99); const min = Math.min(...samples); const max = Math.max(...samples); const confidenceInterval = this.calculateConfidenceInterval(samples, this.options.confidenceLevel); return { mean, median, p95, p99, min, max, stddev, confidenceInterval, samples: rawSamples, // Return original samples iterations, outliersRemoved: outliersRemoved > 0 ? outliersRemoved : undefined, }; } /** * Filter outliers using IQR (Interquartile Range) method * * @private */ filterOutliers(samples) { const q1 = this.calculatePercentile(samples, 25); const q3 = this.calculatePercentile(samples, 75); const iqr = q3 - q1; const lowerBound = q1 - 1.5 * iqr; const upperBound = q3 + 1.5 * iqr; const filtered = []; const outliers = []; for (const sample of samples) { if (sample >= lowerBound && sample <= upperBound) { filtered.push(sample); } else { outliers.push(sample); } } return { filtered, outliers }; } /** * Calculate arithmetic mean * * @private */ calculateMean(samples) { return samples.reduce((sum, val) => sum + val, 0) / samples.length; } /** * Calculate standard deviation * * @private */ calculateStdDev(samples, mean) { const variance = samples.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / (samples.length - 1); return Math.sqrt(variance); } /** * Get critical t-value for confidence interval * Uses approximation for large sample sizes * * @private */ getTCriticalValue(degreesOfFreedom, confidence) { // For large samples (df >= 30), use normal approximation if (degreesOfFreedom >= 30) { // Z-scores for common confidence levels const zScores = { '0.90': 1.645, '0.95': 1.960, '0.99': 2.576, }; return zScores[confidence.toFixed(2)] ?? 1.960; } // T-table for small samples (df < 30) // Simplified lookup table for 95% confidence const tTable = { 1: 12.706, 2: 4.303, 3: 3.182, 4: 2.776, 5: 2.571, 6: 2.447, 7: 2.365, 8: 2.306, 9: 2.262, 10: 2.228, 15: 2.131, 20: 2.086, 25: 2.060, 29: 2.045, }; // Find closest df in table const availableDf = Object.keys(tTable).map(Number); const closestDf = availableDf.reduce((prev, curr) => Math.abs(curr - degreesOfFreedom) < Math.abs(prev - degreesOfFreedom) ? curr : prev); return tTable[closestDf]; } } //# sourceMappingURL=performance-profiler.js.map