traceperf
Version:
High-performance function execution tracking and monitoring for Node.js
543 lines • 22.9 kB
JavaScript
"use strict";
/**
* Optimized Performance Monitoring Library
*
* This implementation incorporates all optimizations from benchmarking:
* 1. Tiered tracking modes for performance vs. detail balance
* 2. Context preservation for object methods
* 3. Async/Promise support
* 4. Browser/Node.js environment detection
* 5. Memory usage tracking optimizations
* 6. Module registration capability
* 7. Enhanced visualization
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.OptimizedPerformanceMonitor = exports.OptimizedExecutionTracker = exports.getDuration = exports.getTimestamp = exports.getMemoryUsage = exports.TrackingModes = void 0;
// Define tracking mode constants directly to avoid circular dependency
exports.TrackingModes = {
PERFORMANCE: 'performance',
BALANCED: 'balanced',
DETAILED: 'detailed',
DEBUG: 'debug'
};
// Define default tracking mode separately to avoid circular dependency
const DEFAULT_TRACKING_MODE = exports.TrackingModes.BALANCED;
/**
* Get current memory usage with environment detection
* @returns Memory usage in bytes
*/
const getMemoryUsage = () => {
var _a;
// Safe check for Node.js process.memoryUsage()
if (typeof process !== 'undefined' && process.memoryUsage) {
const mem = process.memoryUsage();
return mem.heapUsed;
}
// Browser memory API if available
else if (typeof performance !== 'undefined' && 'memory' in performance) {
// @ts-expect-error This is a browser-specific check
return ((_a = window.performance.memory) === null || _a === void 0 ? void 0 : _a.usedJSHeapSize) || 0;
}
// Fallback when no memory API available
return 0;
};
exports.getMemoryUsage = getMemoryUsage;
/**
* Get high-resolution timestamp with environment detection
* @returns Timestamp in nanoseconds
*/
const getTimestamp = () => {
// Use performance.now() in browsers (convert to nanoseconds)
if (typeof performance !== 'undefined' && performance.now) {
return performance.now() * 1000000;
}
// Use process.hrtime() in Node.js
else if (typeof process !== 'undefined' && process.hrtime) {
const hrTime = process.hrtime();
return hrTime[0] * 1000000000 + hrTime[1];
}
// Fallback to Date.now() (converted to nanoseconds)
return Date.now() * 1000000;
};
exports.getTimestamp = getTimestamp;
/**
* Calculate time difference between two timestamps
* @param startTime Starting timestamp in nanoseconds
* @returns Duration in nanoseconds
*/
const getDuration = (startTime) => {
return (0, exports.getTimestamp)() - startTime;
};
exports.getDuration = getDuration;
/**
* Optimized tracker for individual function executions
*/
class OptimizedExecutionTracker {
/**
* Create a new execution tracker
* @param options Tracker configuration options
*/
constructor(options = {}) {
var _a, _b, _c, _d, _e;
this.executionTree = {};
this.currentExecutionId = 0;
this.activeExecutions = new Map();
// Formatters for flow chart generation
this._formatters = {
indent: ' ',
childPrefix: '├─ ',
lastChildPrefix: '└─ ',
linePrefix: '│ ',
emptyPrefix: ' ',
errorMarker: '✖ ',
memoryLabel: '📊 '
};
this.showMemoryUsage = (_a = options.showMemoryUsage) !== null && _a !== void 0 ? _a : true;
this.enableNestedTracking = (_b = options.enableNestedTracking) !== null && _b !== void 0 ? _b : true;
this.silent = (_c = options.silent) !== null && _c !== void 0 ? _c : false;
this.trackingMode = (_d = options.trackingMode) !== null && _d !== void 0 ? _d : exports.TrackingModes.BALANCED;
this.defaultThreshold = (_e = options.threshold) !== null && _e !== void 0 ? _e : 100;
}
/**
* Start timing a function execution
* @param label Function name or label
* @param parentExecutionId ID of parent execution if nested
* @param options Additional options
* @returns Execution ID
*/
startTimer(label, parentExecutionId = null, options = {}) {
// Skip detailed tracking in performance mode
const collectMemory = this.trackingMode !== exports.TrackingModes.PERFORMANCE && this.showMemoryUsage;
const startTime = (0, exports.getTimestamp)();
const executionId = (++this.currentExecutionId).toString();
const execution = {
id: executionId,
label: label || 'anonymous',
startTime,
endTime: 0,
duration: 0,
parentId: parentExecutionId,
children: [],
args: options.captureArgs && options.args ?
Array.from(options.args).map(arg => String(arg).substring(0, 50)) : [],
complete: false
};
// Collect memory usage if enabled
if (collectMemory) {
execution.memoryAtStart = (0, exports.getMemoryUsage)();
execution.memoryAtEnd = 0;
execution.memoryDelta = 0;
}
// Add to parent's children if parent exists
if (parentExecutionId && this.activeExecutions.has(parentExecutionId)) {
const parent = this.activeExecutions.get(parentExecutionId);
if (parent && parent.children) {
parent.children.push(executionId);
}
}
// Store in active executions map
this.activeExecutions.set(executionId, execution);
return executionId;
}
/**
* End timing a function execution
* @param executionId ID of the execution to end
* @param result Result of the function
* @param error Error if the function threw
* @returns Execution data
*/
endTimer(executionId, result, error = null) {
if (!this.activeExecutions.has(executionId)) {
return null;
}
const execution = this.activeExecutions.get(executionId);
execution.endTime = (0, exports.getTimestamp)();
execution.duration = execution.endTime - execution.startTime;
execution.result = result;
execution.error = error;
execution.complete = true;
// Only collect memory in non-performance modes
if (this.trackingMode !== exports.TrackingModes.PERFORMANCE && this.showMemoryUsage) {
execution.memoryAtEnd = (0, exports.getMemoryUsage)();
execution.memoryDelta = execution.memoryAtEnd - (execution.memoryAtStart || 0);
}
// Store in the execution tree
this.executionTree[executionId] = { ...execution };
// Generate flow chart if not silent
if (!this.silent && this.trackingMode !== exports.TrackingModes.PERFORMANCE) {
const flowChart = this._generateExecutionFlowChart(execution);
console.log(flowChart);
}
return execution;
}
/**
* Generate a visualization of the execution flow
* @param execution The execution to visualize
* @param depth Current depth in the tree
* @param isLastChild Whether this is the last child
* @param ancestors Array tracking if parents were last children
* @returns Formatted execution flow chart
*/
_generateExecutionFlowChart(execution, depth = 0, isLastChild = true, ancestors = []) {
// Skip in performance mode
if (this.trackingMode === exports.TrackingModes.PERFORMANCE) {
return '';
}
// Build the flow chart
let flowChart = '';
// Format the execution line based on tracking mode
const durationStr = (execution.duration / 1000000).toFixed(3);
const formatters = this._formatters;
let line = `${formatters.indent.repeat(depth)}${isLastChild ? formatters.lastChildPrefix : formatters.childPrefix}${execution.label}: ${durationStr} ms`;
// Add memory usage if enabled and in detailed modes
if (this.showMemoryUsage &&
execution.memoryDelta !== undefined &&
this.trackingMode !== exports.TrackingModes.PERFORMANCE) {
const memoryStr = (execution.memoryDelta / 1024).toFixed(2);
const sign = execution.memoryDelta >= 0 ? '+' : '';
line += ` ${formatters.memoryLabel}${sign}${memoryStr} KB`;
}
// Add error indicator
if (execution.error && (this.trackingMode === exports.TrackingModes.DETAILED || this.trackingMode === exports.TrackingModes.DEBUG)) {
line += ` ${formatters.errorMarker}${execution.error.message}`;
}
flowChart += line + '\n';
// Process children (skip in performance mode)
if (execution.children && execution.children.length > 0 && this.trackingMode !== exports.TrackingModes.PERFORMANCE) {
const childExecutions = execution.children
.map(childId => this.executionTree[childId] || this.activeExecutions.get(childId))
.filter(Boolean);
for (let i = 0; i < childExecutions.length; i++) {
const isLast = i === childExecutions.length - 1;
const childFlowChart = this._generateExecutionFlowChart(childExecutions[i], depth + 1, isLast, [...ancestors, !isLastChild]);
flowChart += childFlowChart;
}
}
return flowChart;
}
/**
* Create a trackable version of a function
* @param fn Function to track
* @param options Tracking options
* @returns Tracked version of the function
*/
createTrackable(fn, options = {}) {
if (typeof fn !== 'function') {
throw new Error('First argument must be a function');
}
const label = options.label || fn.name || 'anonymous';
const parentExecutionId = options.parentExecutionId || null;
const silent = options.hasOwnProperty('silent') ? options.silent : this.silent;
const trackingMode = options.trackingMode || this.trackingMode;
const sampleRate = options.hasOwnProperty('sampleRate') ?
Math.max(0, Math.min(1, options.sampleRate)) : 1.0;
// Performance optimization - if in performance mode and silent, return original function
if (trackingMode === exports.TrackingModes.PERFORMANCE &&
(options.silent || this.silent) &&
!this.enableNestedTracking) {
return fn;
}
// Create the tracked function using arrow function to preserve 'this'
const trackedFn = (...args) => {
// Apply sampling - randomly decide whether to track this call
const shouldSample = Math.random() < sampleRate;
// If not sampling this call, just execute the function directly
if (!shouldSample) {
return fn.apply(this, args);
}
// Start tracking the execution
const executionId = this.startTimer(label, parentExecutionId, {
trackMemory: options.trackMemory !== undefined ? options.trackMemory : this.showMemoryUsage,
captureArgs: options.captureArgs,
args: args
});
try {
// Execute the function with correct this context
const result = fn.apply(this, args);
// Handle promises
if (result && typeof result.then === 'function') {
return result
.then((resolvedValue) => {
this.endTimer(executionId, resolvedValue);
return resolvedValue;
})
.catch((error) => {
this.endTimer(executionId, undefined, error);
throw error; // Re-throw to preserve error behavior
});
}
// Handle synchronous functions
this.endTimer(executionId, result);
return result;
}
catch (error) {
// Handle synchronous errors
this.endTimer(executionId, undefined, error);
throw error; // Re-throw to preserve error behavior
}
};
// Preserve function properties
Object.defineProperty(trackedFn, 'name', { value: fn.name, configurable: true });
Object.defineProperty(trackedFn, 'length', { value: fn.length });
return trackedFn;
}
/**
* Track a module's methods
* @param module Module to track
* @param options Tracking options
* @returns Tracked module
*/
trackModule(module, options = {}) {
if (!module || typeof module !== 'object') {
throw new Error('Module must be an object');
}
const prefix = options.prefix || '';
const trackingMode = options.trackingMode || this.trackingMode;
// Performance optimization - if in performance mode and silent, return original module
if (trackingMode === exports.TrackingModes.PERFORMANCE &&
(options.silent || this.silent) &&
!this.enableNestedTracking) {
return module;
}
// Create a new object to avoid modifying the original
const trackedModule = Object.create(Object.getPrototypeOf(module));
// Copy own properties
Object.getOwnPropertyNames(module).forEach(key => {
const descriptor = Object.getOwnPropertyDescriptor(module, key);
if (!descriptor)
return;
// If it's a method, track it
if (typeof module[key] === 'function') {
const methodOptions = {
...options,
label: `${prefix}${key}`
};
trackedModule[key] = this.createTrackable(module[key], methodOptions);
}
else {
// Otherwise, just copy the property
Object.defineProperty(trackedModule, key, descriptor);
}
});
return trackedModule;
}
/**
* Track a function execution directly
* @param fn Function to track
* @param options Tracking options
* @returns Function result
*/
track(fn, options = {}) {
// Create a trackable version and call it immediately
const trackableFn = this.createTrackable(fn, options);
return trackableFn.apply(options.thisContext || this, options.args || []);
}
/**
* Reset the tracker state
*/
reset() {
this.executionTree = {};
this.currentExecutionId = 0;
this.activeExecutions = new Map();
}
}
exports.OptimizedExecutionTracker = OptimizedExecutionTracker;
/**
* Main performance monitoring interface
*/
class OptimizedPerformanceMonitor {
/**
* Create a new performance monitor instance
* @param options Configuration options
*/
constructor(options = {}) {
this.config = {
trackingMode: options.trackingMode || DEFAULT_TRACKING_MODE,
silent: options.silent || false,
trackMemory: options.trackMemory !== undefined ? options.trackMemory : true,
enableNestedTracking: options.enableNestedTracking !== undefined ? options.enableNestedTracking : true,
threshold: options.threshold || 100,
sampleRate: options.sampleRate !== undefined ?
Math.max(0, Math.min(1, options.sampleRate)) : 1.0 // Clamp between 0 and 1
};
this.executionTracker = new OptimizedExecutionTracker({
trackingMode: this.config.trackingMode,
silent: this.config.silent,
showMemoryUsage: this.config.trackMemory,
enableNestedTracking: this.config.enableNestedTracking,
threshold: this.config.threshold
});
}
/**
* Track a function execution
* @param fn The function to track
* @param options Tracking options
* @returns Result of the tracked function
*/
track(fn, options = {}) {
if (typeof fn !== 'function') {
throw new Error('First argument must be a function');
}
// Merge global config with function-specific options
const trackingOptions = {
label: options.label || fn.name || 'anonymous',
trackingMode: options.trackingMode || this.config.trackingMode,
silent: options.hasOwnProperty('silent') ? options.silent : this.config.silent,
trackMemory: options.hasOwnProperty('trackMemory') ? options.trackMemory : this.config.trackMemory,
sampleRate: options.hasOwnProperty('sampleRate') ? options.sampleRate : this.config.sampleRate,
captureArgs: options.hasOwnProperty('captureArgs') ? options.captureArgs : false,
parentExecutionId: options.parentExecutionId || null,
thisContext: options.thisContext,
args: options.args
};
// Use the tracker to directly track the function
return this.executionTracker.track(fn, trackingOptions);
}
/**
* Create a trackable version of a function
* @param fn The function to track
* @param options Tracking options
* @returns Tracked version of the function
*/
createTrackable(fn, options = {}) {
if (typeof fn !== 'function') {
throw new Error('First argument must be a function');
}
// Merge global config with function-specific options
const trackingOptions = {
label: options.label || fn.name || 'anonymous',
trackingMode: options.trackingMode || this.config.trackingMode,
silent: options.hasOwnProperty('silent') ? options.silent : this.config.silent,
trackMemory: options.hasOwnProperty('trackMemory') ? options.trackMemory : this.config.trackMemory,
sampleRate: options.hasOwnProperty('sampleRate') ? options.sampleRate : this.config.sampleRate,
captureArgs: options.hasOwnProperty('captureArgs') ? options.captureArgs : false,
parentExecutionId: options.parentExecutionId || null
};
return this.executionTracker.createTrackable(fn, trackingOptions);
}
/**
* Register a module for tracking
* @param module The module or object containing methods to track
* @param options Tracking options
* @returns Modified module with tracked methods
*/
registerModule(module, options = {}) {
if (!module || typeof module !== 'object') {
throw new Error('Module must be an object');
}
// Merge global config with module-specific options
const trackingOptions = {
prefix: options.prefix || '',
trackingMode: options.trackingMode || this.config.trackingMode,
silent: options.hasOwnProperty('silent') ? options.silent : this.config.silent,
trackMemory: options.hasOwnProperty('trackMemory') ? options.trackMemory : this.config.trackMemory,
sampleRate: options.hasOwnProperty('sampleRate') ? options.sampleRate : this.config.sampleRate,
captureArgs: options.hasOwnProperty('captureArgs') ? options.captureArgs : false
};
return this.executionTracker.trackModule(module, trackingOptions);
}
/**
* Set configuration options
* @param config Configuration to update
* @returns Current configuration after update
*/
setConfig(config = {}) {
// Update trackingMode if specified
if (config.trackingMode) {
this.config.trackingMode = config.trackingMode;
this.executionTracker.trackingMode = config.trackingMode;
}
// Update silent if specified
if (config.silent !== undefined) {
this.config.silent = config.silent;
this.executionTracker.silent = config.silent;
}
// Update trackMemory if specified
if (config.trackMemory !== undefined) {
this.config.trackMemory = config.trackMemory;
this.executionTracker.showMemoryUsage = config.trackMemory;
}
// Update enableNestedTracking if specified
if (config.enableNestedTracking !== undefined) {
this.config.enableNestedTracking = config.enableNestedTracking;
this.executionTracker.enableNestedTracking = config.enableNestedTracking;
}
// Update threshold if specified
if (config.threshold !== undefined) {
this.config.threshold = config.threshold;
this.executionTracker.defaultThreshold = config.threshold;
}
// Update sampleRate if specified
if (config.sampleRate !== undefined) {
this.config.sampleRate = Math.max(0, Math.min(1, config.sampleRate));
}
return { ...this.config };
}
/**
* Enable silent mode (no console output)
* @returns This instance for chaining
*/
enableSilentMode() {
this.config.silent = true;
this.executionTracker.silent = true;
return this;
}
/**
* Disable silent mode (allow console output)
* @returns This instance for chaining
*/
disableSilentMode() {
this.config.silent = false;
this.executionTracker.silent = false;
return this;
}
/**
* Set the tracking mode
* @param mode Tracking mode from TrackingMode enum
* @returns This instance for chaining
*/
setTrackingMode(mode) {
this.config.trackingMode = mode;
this.executionTracker.trackingMode = mode;
return this;
}
/**
* Enable memory usage tracking
* @returns This instance for chaining
*/
enableMemoryTracking() {
this.config.trackMemory = true;
this.executionTracker.showMemoryUsage = true;
return this;
}
/**
* Disable memory usage tracking
* @returns This instance for chaining
*/
disableMemoryTracking() {
this.config.trackMemory = false;
this.executionTracker.showMemoryUsage = false;
return this;
}
/**
* Set the sampling rate
* @param rate Sampling rate between 0.0 and 1.0
* @returns This instance for chaining
*/
setSampleRate(rate) {
this.config.sampleRate = Math.max(0, Math.min(1, rate));
return this;
}
/**
* Reset the tracker state
* @returns This instance for chaining
*/
reset() {
this.executionTracker.reset();
return this;
}
}
exports.OptimizedPerformanceMonitor = OptimizedPerformanceMonitor;
// Remove singleton instance to avoid circular dependency - it will be created in index.ts
// export const optimizedTracePerf = new OptimizedPerformanceMonitor();
//# sourceMappingURL=optimized-performance-monitor.js.map