ghost-performance
Version:
A lightweight Node.js utility that profiles individual function executions with detailed performance metrics
244 lines (241 loc) • 8.48 kB
JavaScript
// 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