UNPKG

ghost-performance

Version:

A lightweight Node.js utility that profiles individual function executions with detailed performance metrics

244 lines (241 loc) 8.48 kB
// Cross-platform performance measurement const getPerformanceNow = () => { if (typeof globalThis !== 'undefined' && globalThis.performance) { return globalThis.performance.now(); } // Fallback for environments without performance API return Date.now(); }; // Cross-platform console logging const log = (message) => { if (typeof globalThis !== 'undefined' && globalThis.console) { globalThis.console.log(message); } }; class GhostProfiler { constructor(fn, options = {}) { this.executionTimes = []; this.memorySamples = []; this.functionName = options.functionName || fn.name || 'anonymous'; this.options = { trackMemory: options.trackMemory ?? false, maxSamples: options.maxSamples ?? 1000, functionName: this.functionName, verbose: options.verbose ?? false }; } /** * Profile a function execution */ profile(fn, args) { const startTime = getPerformanceNow(); const startMemory = this.getMemoryUsage(); let result; let error = null; try { result = fn(...args); } catch (err) { error = err; throw error; } finally { const endTime = getPerformanceNow(); const endMemory = this.getMemoryUsage(); const executionTime = endTime - startTime; const memoryDelta = endMemory - startMemory; this.recordExecution(executionTime, memoryDelta); if (this.options.verbose) { log(`[GhostProfiler] ${this.functionName}: ${executionTime.toFixed(2)}ms`); } } return { result, metrics: this.getMetrics() }; } /** * Record an execution time and memory usage */ recordExecution(executionTime, memoryDelta) { this.executionTimes.push(executionTime); if (this.options.trackMemory) { this.memorySamples.push(memoryDelta); } // Keep only the most recent samples if (this.executionTimes.length > this.options.maxSamples) { this.executionTimes = this.executionTimes.slice(-this.options.maxSamples); if (this.options.trackMemory) { this.memorySamples = this.memorySamples.slice(-this.options.maxSamples); } } } /** * Get current memory usage (Node.js only) */ getMemoryUsage() { if (typeof globalThis !== 'undefined' && globalThis.process?.memoryUsage) { const memUsage = globalThis.process.memoryUsage(); return memUsage.heapUsed; } return 0; } /** * Calculate performance metrics */ getMetrics() { if (this.executionTimes.length === 0) { return this.getEmptyMetrics(); } const sortedTimes = [...this.executionTimes].sort((a, b) => a - b); const totalTime = this.executionTimes.reduce((sum, time) => sum + time, 0); const averageTime = totalTime / this.executionTimes.length; // Calculate standard deviation const variance = this.executionTimes.reduce((sum, time) => { return sum + Math.pow(time - averageTime, 2); }, 0) / this.executionTimes.length; const standardDeviation = Math.sqrt(variance); return { calls: this.executionTimes.length, bestTime: sortedTimes[0], worstTime: sortedTimes[sortedTimes.length - 1], averageTime, standardDeviation, totalTime, memoryTrend: this.calculateMemoryTrend(), executionTimes: [...this.executionTimes], memorySamples: [...this.memorySamples] }; } /** * Calculate memory usage trend */ calculateMemoryTrend() { if (!this.options.trackMemory || this.memorySamples.length < 2) { return { direction: 'unknown', rateOfChange: 0, confidence: 0 }; } // Simple linear regression const n = this.memorySamples.length; const sumX = (n * (n - 1)) / 2; const sumY = this.memorySamples.reduce((sum, sample) => sum + sample, 0); const sumXY = this.memorySamples.reduce((sum, sample, index) => sum + (index * sample), 0); const sumX2 = (n * (n - 1) * (2 * n - 1)) / 6; const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); const rateOfChange = slope; // Calculate confidence based on R-squared const meanY = sumY / n; const ssRes = this.memorySamples.reduce((sum, sample, index) => { const predicted = slope * index + (sumY / n - slope * sumX / n); return sum + Math.pow(sample - predicted, 2); }, 0); const ssTot = this.memorySamples.reduce((sum, sample) => { return sum + Math.pow(sample - meanY, 2); }, 0); const rSquared = ssTot > 0 ? 1 - (ssRes / ssTot) : 0; const confidence = Math.max(0, Math.min(1, rSquared)); let direction; if (Math.abs(rateOfChange) < 100) { // Less than 100 bytes per call direction = 'stable'; } else if (rateOfChange > 0) { direction = 'increasing'; } else { direction = 'decreasing'; } return { direction, rateOfChange, confidence }; } /** * Get empty metrics for when no executions have been recorded */ getEmptyMetrics() { return { calls: 0, bestTime: 0, worstTime: 0, averageTime: 0, standardDeviation: 0, totalTime: 0, memoryTrend: { direction: 'unknown', rateOfChange: 0, confidence: 0 }, executionTimes: [], memorySamples: [] }; } /** * Reset all metrics */ reset() { this.executionTimes = []; this.memorySamples = []; } /** * Generate a human-readable report */ getReport() { const metrics = this.getMetrics(); if (metrics.calls === 0) { return `[GhostProfiler] ${this.functionName}: No executions recorded`; } const memoryInfo = this.options.trackMemory ? `\nMemory Trend: ${metrics.memoryTrend.direction} (${metrics.memoryTrend.rateOfChange.toFixed(2)} bytes/call, confidence: ${(metrics.memoryTrend.confidence * 100).toFixed(1)}%)` : ''; return `[GhostProfiler] ${this.functionName} Performance Report: Calls: ${metrics.calls} Best Time: ${metrics.bestTime.toFixed(2)}ms Worst Time: ${metrics.worstTime.toFixed(2)}ms Average Time: ${metrics.averageTime.toFixed(2)}ms Standard Deviation: ${metrics.standardDeviation.toFixed(2)}ms Total Time: ${metrics.totalTime.toFixed(2)}ms${memoryInfo}`; } } /** * Profile a function and return a profiled version that tracks performance metrics * @param fn The function to profile * @param options Profiling options * @returns A profiled function with performance tracking capabilities */ function profileFunction(fn, options = {}) { const profiler = new GhostProfiler(fn, options); const profiledFn = ((...args) => { return profiler.profile(fn, args); }); // Add methods to the profiled function profiledFn.getMetrics = () => profiler.getMetrics(); profiledFn.reset = () => profiler.reset(); profiledFn.getReport = () => profiler.getReport(); return profiledFn; } /** * Profile a function execution once and return the result with metrics * @param fn The function to profile * @param args Arguments to pass to the function * @param options Profiling options * @returns The function result and performance metrics */ function profileOnce(fn, args = [], options = {}) { const profiler = new GhostProfiler(fn, options); return profiler.profile(fn, args); } /** * Create a performance profiler instance * @param fn The function to profile * @param options Profiling options * @returns A profiler instance */ function createProfiler(fn, options = {}) { return new GhostProfiler(fn, options); } export { GhostProfiler, createProfiler, profileFunction, profileOnce }; //# sourceMappingURL=index.esm.js.map