UNPKG

traceperf

Version:

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

581 lines 21.2 kB
"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