UNPKG

traceperf

Version:

High-performance function execution tracking and monitoring for Node.js

226 lines 8.15 kB
import { getFunctionName } from '../utils/stack'; import { getHighResTime, getDuration } from '../utils/timing'; /** * Execution tracker for performance monitoring */ export class ExecutionTracker { /** * Constructor * * @param options - Options for execution tracking */ constructor(options = {}) { var _a; this._executions = []; this._currentExecution = null; this._callStack = []; this._trackedFunctions = new Map(); this._defaultThreshold = (_a = options.defaultThreshold) !== null && _a !== void 0 ? _a : 100; // ms // Get a reference to the global scope (works in both Node.js and browsers) this._globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {}; } /** * Track the execution of a function * * @param fn - The function to track * @param options - Options for tracking * @returns The return value of the tracked function */ track(fn, options) { var _a, _b, _c; const fnName = (options === null || options === void 0 ? void 0 : options.label) || getFunctionName(fn); const threshold = (_a = options === null || options === void 0 ? void 0 : options.threshold) !== null && _a !== void 0 ? _a : this._defaultThreshold; const includeMemory = (_b = options === null || options === void 0 ? void 0 : options.trackMemory) !== null && _b !== void 0 ? _b : true; const enableNestedTracking = (_c = options === null || options === void 0 ? void 0 : options.enableNestedTracking) !== null && _c !== void 0 ? _c : true; // Get memory usage before execution let startMemory; if (includeMemory) { try { startMemory = process.memoryUsage(); } catch (e) { console.warn('Unable to track memory usage:', e); } } // Start timing const startTime = getHighResTime(); // Add to call stack const level = this._callStack.push(fnName); // Create execution record const execution = { name: fnName, duration: 0, isSlow: false, level: level - 1, // 0-based level startTime, children: [], }; // Set parent-child relationship if (this._currentExecution && enableNestedTracking) { execution.parent = this._currentExecution; this._currentExecution.children.push(execution); } else { // This is a root execution this._executions.push(execution); } // Save the previous current execution const previousExecution = this._currentExecution; // Set this as the current execution this._currentExecution = execution; try { // Execute the function return fn(); } finally { // End timing const endTime = getHighResTime(); execution.endTime = endTime; // Calculate duration execution.duration = getDuration(startTime); // Check if it's a bottleneck execution.isSlow = execution.duration > threshold; // Calculate memory usage if (includeMemory && startMemory !== undefined) { try { const endMemory = process.memoryUsage(); // Calculate the memory difference, ensuring it's never negative const memoryDiff = endMemory.heapUsed - startMemory.heapUsed; execution.memoryUsage = Math.max(0, memoryDiff); // Ensure non-negative value // If memory decreased, store a separate field for better visualization if (memoryDiff < 0) { execution.memoryReleased = Math.abs(memoryDiff); } } catch (e) { console.warn('Error calculating memory usage:', e); } } // Restore the previous current execution this._currentExecution = previousExecution; // Remove from call stack this._callStack.pop(); } } /** * Create a trackable version of a function * * This is a helper method to create a tracked version of a function * that can be used for nested function tracking. * * @param fn - The function to make trackable * @param options - Options for tracking * @returns A tracked version of the function */ createTrackable(fn, options) { const fnName = (options === null || options === void 0 ? void 0 : options.label) || fn.name || 'anonymous'; // Create a tracked version of the function using arrow function to preserve 'this' const trackedFn = (...args) => { return this.track(() => fn.apply(this, args), { ...options, label: fnName }); }; // Store the original and tracked function this._trackedFunctions.set(fn, trackedFn); return trackedFn; } /** * Get all execution records * * @returns A list of execution records */ getExecutions() { return [...this._executions]; } /** * Generate a human-friendly memory size string * * @param bytes - The size in bytes * @returns A formatted string */ formatMemorySize(bytes) { if (bytes < 1024) { return `${bytes.toFixed(2)}B`; } else if (bytes < 1024 * 1024) { return `${(bytes / 1024).toFixed(2)}KB`; } else if (bytes < 1024 * 1024 * 1024) { return `${(bytes / (1024 * 1024)).toFixed(2)}MB`; } else { return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`; } } /** * Get the current call stack * * @returns The current call stack */ getCallStack() { return [...this._callStack]; } /** * Generate a visual representation of the execution flow * * @returns ASCII flow chart of the execution */ generateFlowChart() { let result = ''; // Process each execution this._executions.forEach(execution => { result = this.printExecutionTree(execution, result); }); return result; } /** * Recursively print an execution tree * * @param execution - The root execution * @param result - The output string * @returns The updated result string */ printExecutionTree(execution, result) { // Print this execution result = this.printSingleExecution(execution, result); // Print children recursively execution.children.forEach(child => { result = this.printExecutionTree(child, result); }); return result; } /** * Print a single execution with appropriate formatting * * @param execution - The execution to print * @param result - The result string to append to * @returns The updated result string */ printSingleExecution(execution, result) { const indent = ' '.repeat(execution.level); const durationStr = execution.duration.toFixed(2); const slowMarker = execution.isSlow ? ' [SLOW]' : ''; let memoryStr = ''; if (execution.memoryUsage !== undefined) { memoryStr = ` [${this.formatMemorySize(execution.memoryUsage)}]`; } result += `${indent}→ ${execution.name} (${durationStr}ms)${slowMarker}${memoryStr}\n`; // Add connector if this execution has children if (execution.children.length > 0) { result += `${indent} |\n`; } return result; } /** * Clear all execution records */ clear() { this._executions = []; this._currentExecution = null; this._callStack = []; } } //# sourceMappingURL=execution.js.map