traceperf
Version:
High-performance function execution tracking and monitoring for Node.js
230 lines • 8.33 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExecutionTracker = void 0;
const stack_1 = require("../utils/stack");
const timing_1 = require("../utils/timing");
/**
* Execution tracker for performance monitoring
*/
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) || (0, stack_1.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 = (0, timing_1.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 = (0, timing_1.getHighResTime)();
execution.endTime = endTime;
// Calculate duration
execution.duration = (0, timing_1.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 = [];
}
}
exports.ExecutionTracker = ExecutionTracker;
//# sourceMappingURL=execution.js.map