traceperf
Version:
High-performance function execution tracking and monitoring for Node.js
581 lines • 21.2 kB
JavaScript
"use strict";
/**
* Browser-compatible version of TracePerf
*
* This file provides a browser-compatible version of TracePerf that can be used
* in React, Next.js, or other frontend applications.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserExecutionTracker = exports.BrowserPerformanceMonitor = exports.BrowserLogger = exports.TrackingMode = void 0;
exports.createBrowserLogger = createBrowserLogger;
exports.createTracePerf = createTracePerf;
// Define tracking mode constants to avoid circular dependencies
exports.TrackingMode = {
PERFORMANCE: 'performance',
BALANCED: 'balanced',
DETAILED: 'detailed',
DEBUG: 'debug'
};
/**
* Browser-compatible performance monitoring
*/
class BrowserPerformanceMonitor {
constructor(options = {}) {
var _a;
this._timers = new Map();
this._defaultThreshold = (_a = options.defaultThreshold) !== null && _a !== void 0 ? _a : 100; // ms
}
startTimer(label) {
const id = `${label}-${Math.random().toString(36).substring(2, 9)}`;
this._timers.set(id, performance.now());
return id;
}
endTimer(id) {
const startTime = this._timers.get(id);
if (!startTime) {
throw new Error(`Timer with id ${id} not found`);
}
const duration = performance.now() - startTime;
this._timers.delete(id);
return duration;
}
isBottleneck(duration, threshold) {
const effectiveThreshold = threshold !== null && threshold !== void 0 ? threshold : this._defaultThreshold;
return duration > effectiveThreshold;
}
/**
* Get memory usage from browser performance API
* @returns Memory usage object with heap metrics or undefined if not available
*/
getMemoryUsage() {
try {
// Chrome-specific memory API
if (performance && 'memory' in performance) {
const memory = performance.memory;
return {
heapUsed: (memory === null || memory === void 0 ? void 0 : memory.usedJSHeapSize) || 0,
heapTotal: (memory === null || memory === void 0 ? void 0 : memory.totalJSHeapSize) || 0,
external: 0, // Not available in browser
rss: 0, // Not available in browser
};
}
// Firefox has a different memory API
if (window && window.performance && window.performance.memory) {
const memory = window.performance.memory;
return {
heapUsed: memory.usedJSHeapSize || 0,
heapTotal: memory.totalJSHeapSize || 0,
external: 0,
rss: 0,
};
}
return undefined;
}
catch (e) {
console.warn('Memory API not available or permission denied:', e);
return undefined;
}
}
/**
* Calculate memory difference
* @param start - Starting memory usage
* @returns Memory difference in bytes
*/
getMemoryDiff(start) {
try {
const current = this.getMemoryUsage();
if (!current)
return 0;
const diff = current.heapUsed - start.heapUsed;
// Return zero instead of negative values
return Math.max(0, diff);
}
catch (e) {
console.warn('Error calculating memory difference:', e);
return 0;
}
}
now() {
return performance.now();
}
generateSuggestion(functionName, duration, memoryUsage) {
if (duration > 1000) {
return `🛠 Potential Fix: Consider optimizing or adding caching to ${functionName}`;
}
if (duration > 500) {
return `🛠 Potential Fix: Look for blocking operations in ${functionName}`;
}
return `🛠 Potential Fix: Review ${functionName} for performance optimizations`;
}
}
exports.BrowserPerformanceMonitor = BrowserPerformanceMonitor;
/**
* Browser-compatible execution tracker for tracking function calls and generating flow charts
*/
class BrowserExecutionTracker {
/**
* Create a new BrowserExecutionTracker instance
*
* @param options - Tracker options
*/
constructor(options = {}) {
var _a;
this._callStack = [];
this._executions = [];
this._currentExecution = null;
this._trackedFunctions = new Map();
this._defaultThreshold = (_a = options.defaultThreshold) !== null && _a !== void 0 ? _a : 100; // ms
this._performanceMonitor = new BrowserPerformanceMonitor(options);
// Get a reference to the global scope (window in browsers)
this._globalScope = typeof window !== 'undefined' ? window :
typeof global !== 'undefined' ? global :
{};
}
/**
* Generate a performance optimization suggestion based on execution duration
*
* @param name - Function name
* @param duration - Execution duration in ms
* @returns Optimization suggestion
*/
generateSuggestion(name, duration) {
if (duration > 500) {
return `Function "${name}" took ${duration.toFixed(2)}ms to execute. Consider optimizing or breaking it down into smaller functions.`;
}
else if (duration > 200) {
return `Function "${name}" took ${duration.toFixed(2)}ms to execute. Consider optimizing if called frequently.`;
}
else if (duration > 100) {
return `Function "${name}" took ${duration.toFixed(2)}ms to execute. Monitor if called in critical paths.`;
}
return '';
}
/**
* 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.label || fn.name || 'anonymous';
const threshold = (_a = options.threshold) !== null && _a !== void 0 ? _a : this._defaultThreshold;
const enableNestedTracking = (_b = options.enableNestedTracking) !== null && _b !== void 0 ? _b : true;
const trackMemory = (_c = options.trackMemory) !== null && _c !== void 0 ? _c : true;
// Get memory usage before execution
let startMemory;
if (trackMemory) {
try {
startMemory = this._performanceMonitor.getMemoryUsage();
}
catch (e) {
console.warn('Unable to track memory usage:', e);
}
}
// Add to call stack
this._callStack.push(fnName);
// Create execution record
const execution = {
name: fnName,
duration: 0,
isSlow: false,
level: this._callStack.length - 1,
startTime: performance.now(),
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;
let result;
try {
// Execute the function
result = fn();
}
catch (error) {
// End timing
const endTime = performance.now();
execution.endTime = endTime;
execution.duration = endTime - execution.startTime;
execution.isSlow = execution.duration > threshold;
// Calculate memory usage
if (trackMemory && startMemory !== undefined) {
try {
const memoryDiff = this._performanceMonitor.getMemoryDiff(startMemory);
execution.memoryUsage = 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();
// Re-throw the error
throw error;
}
// End timing
const endTime = performance.now();
execution.endTime = endTime;
execution.duration = endTime - execution.startTime;
execution.isSlow = execution.duration > threshold;
// Calculate memory usage
if (trackMemory && startMemory !== undefined) {
try {
const memoryDiff = this._performanceMonitor.getMemoryDiff(startMemory);
execution.memoryUsage = memoryDiff;
}
catch (e) {
console.warn('Error calculating memory usage:', e);
}
}
// Generate suggestion if slow
if (execution.isSlow) {
execution.suggestion = this.generateSuggestion(fnName, execution.duration);
}
// Restore the previous current execution
this._currentExecution = previousExecution;
// Remove from call stack
this._callStack.pop();
return result;
}
/**
* 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) {
var _a, _b, _c;
const label = (options === null || options === void 0 ? void 0 : options.label) || fn.name || 'anonymous';
const threshold = (_a = options === null || options === void 0 ? void 0 : options.threshold) !== null && _a !== void 0 ? _a : 100; // Use a sensible default of 100ms
const enableNestedTracking = (_b = options === null || options === void 0 ? void 0 : options.enableNestedTracking) !== null && _b !== void 0 ? _b : true;
const trackMemory = (_c = options === null || options === void 0 ? void 0 : options.trackMemory) !== null && _c !== void 0 ? _c : true;
const trackingMode = (options === null || options === void 0 ? void 0 : options.trackingMode) || exports.TrackingMode.BALANCED;
// Create a tracked version of the function using arrow function
const self = this;
return function (...args) {
return self.track(() => fn.apply(this, args), {
...options,
label,
threshold,
enableNestedTracking,
trackMemory,
trackingMode
});
};
}
/**
* 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 = 'Execution Flow:\n';
// Process each execution
this.prepareExecutionData(this._executions).forEach(execution => {
const indent = ' '.repeat(execution.level);
const durationStr = execution.duration.toFixed(2);
const slowMarker = execution.isSlow ? ' [SLOW]' : '';
let memoryStr = '';
if (execution.memoryUsage !== undefined && execution.memoryUsage > 0) {
memoryStr = ` [${this.formatMemorySize(execution.memoryUsage)}]`;
}
result += `${indent}→ ${execution.name} (${durationStr}ms)${slowMarker}${memoryStr}\n`;
// Add suggestion if available
if (execution.suggestion) {
result += `${indent} ⚠️ ${execution.suggestion}\n`;
}
});
return result;
}
/**
* Prepare execution data for the flow chart
*
* @param executions - The execution records to prepare
* @returns A list of execution data
*/
prepareExecutionData(executions) {
const result = [];
// Process each root execution
executions.forEach(execution => {
// Add the root execution
result.push({
name: execution.name,
duration: execution.duration,
isSlow: execution.isSlow,
suggestion: execution.suggestion,
level: execution.level,
memoryUsage: execution.memoryUsage
});
// Add children recursively
this.addChildrenToResult(execution.children, result);
});
return result;
}
/**
* Add children to the result list
*
* @param children - The children to add
* @param result - The result list
*/
addChildrenToResult(children, result) {
children.forEach(child => {
// Add the child
result.push({
name: child.name,
duration: child.duration,
isSlow: child.isSlow,
suggestion: child.suggestion,
level: child.level,
memoryUsage: child.memoryUsage
});
// Add grandchildren recursively
this.addChildrenToResult(child.children, result);
});
}
/**
* Format memory size for display
* @param bytes Memory usage in bytes
* @returns 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`;
}
}
/**
* Clear all execution records
*/
clear() {
this._executions = [];
this._currentExecution = null;
this._callStack = [];
}
}
exports.BrowserExecutionTracker = BrowserExecutionTracker;
/**
* Browser-compatible logger
*/
class BrowserLogger {
constructor(config = {}) {
this._mode = 'dev';
this._indentLevel = 0;
this._trackingMode = exports.TrackingMode.BALANCED;
this._mode = config.mode || 'dev';
this._trackingMode = config.trackingMode || exports.TrackingMode.BALANCED;
this._executionTracker = new BrowserExecutionTracker({
defaultThreshold: config.performanceThreshold || 100,
});
this._performanceMonitor = new BrowserPerformanceMonitor({
defaultThreshold: config.performanceThreshold || 100,
});
}
info(message, ...args) {
this.log('info', message, args);
}
warn(message, ...args) {
this.log('warn', message, args);
}
error(message, ...args) {
this.log('error', message, args);
}
debug(message, ...args) {
if (this._mode === 'prod') {
return;
}
this.log('debug', message, args);
}
group(label) {
this.info(label);
this._indentLevel++;
if (typeof console.group === 'function') {
console.group(label);
}
}
groupEnd() {
if (this._indentLevel > 0) {
this._indentLevel--;
}
if (typeof console.groupEnd === 'function') {
console.groupEnd();
}
}
setMode(mode) {
this._mode = mode;
}
getMode() {
return this._mode;
}
/**
* Set the tracking mode
* @param mode Tracking mode from TrackingMode enum
* @returns This instance for chaining
*/
setTrackingMode(mode) {
this._trackingMode = mode;
return this;
}
/**
* Get the current tracking mode
* @returns Current tracking mode
*/
getTrackingMode() {
return this._trackingMode;
}
track(fn, options) {
var _a, _b;
const silent = (_a = options === null || options === void 0 ? void 0 : options.silent) !== null && _a !== void 0 ? _a : false;
const trackMemory = (_b = options === null || options === void 0 ? void 0 : options.trackMemory) !== null && _b !== void 0 ? _b : true;
const trackingMode = (options === null || options === void 0 ? void 0 : options.trackingMode) || this._trackingMode;
// Track the function execution
const result = this._executionTracker.track(fn, {
...options,
trackMemory,
trackingMode
});
// Log the execution flow if not silent
if (!silent) {
const flowChart = this._executionTracker.generateFlowChart();
console.log(flowChart);
}
return result;
}
/**
* 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) {
var _a, _b, _c;
const label = (options === null || options === void 0 ? void 0 : options.label) || fn.name || 'anonymous';
const threshold = (_a = options === null || options === void 0 ? void 0 : options.threshold) !== null && _a !== void 0 ? _a : 100; // Use a sensible default of 100ms
const enableNestedTracking = (_b = options === null || options === void 0 ? void 0 : options.enableNestedTracking) !== null && _b !== void 0 ? _b : true;
const trackMemory = (_c = options === null || options === void 0 ? void 0 : options.trackMemory) !== null && _c !== void 0 ? _c : true;
const trackingMode = (options === null || options === void 0 ? void 0 : options.trackingMode) || this._trackingMode;
// Create a tracked version of the function using arrow function
const self = this;
return function (...args) {
return self.track(() => fn.apply(this, args), {
...options,
label,
threshold,
enableNestedTracking,
trackMemory,
trackingMode
});
};
}
log(level, message, args) {
// Skip logs based on mode
if (this._mode === 'prod' && level !== 'error') {
return;
}
if (this._mode === 'staging' && (level === 'debug' || level === 'info')) {
return;
}
// Format the message
let formattedMessage;
if (typeof message === 'string') {
formattedMessage = message;
}
else {
try {
formattedMessage = JSON.stringify(message, null, 2);
}
catch (error) {
formattedMessage = String(message);
}
}
// Add indentation
const indent = ' '.repeat(this._indentLevel);
formattedMessage = `${indent}${formattedMessage}`;
// Log to console with appropriate method
switch (level) {
case 'info':
console.info(formattedMessage, ...args);
break;
case 'warn':
console.warn(formattedMessage, ...args);
break;
case 'error':
console.error(formattedMessage, ...args);
break;
case 'debug':
console.debug(formattedMessage, ...args);
break;
}
}
}
exports.BrowserLogger = BrowserLogger;
/**
* Create a browser-compatible logger instance
* @param config Configuration options
* @returns Browser logger instance
*/
function createBrowserLogger(config = {}) {
return new BrowserLogger(config);
}
/**
* Create a browser-compatible TracePerf instance
* Alias of createBrowserLogger for API consistency with Node.js version
* @param config Configuration options
* @returns Browser logger instance
*/
function createTracePerf(config = {}) {
return createBrowserLogger(config);
}
// Create default browser logger instance
const browserLogger = createBrowserLogger();
// Export the default instance
exports.default = browserLogger;
// Add CommonJS compatibility
if (typeof module !== 'undefined' && module.exports) {
module.exports = Object.assign(browserLogger, {
default: browserLogger,
createBrowserLogger,
createTracePerf,
BrowserLogger,
BrowserPerformanceMonitor,
BrowserExecutionTracker,
TrackingMode: exports.TrackingMode
});
}
//# sourceMappingURL=browser.js.map