UNPKG

@crudmates/profiler

Version:

A comprehensive performance profiling utility for Node.js applications with timing, memory monitoring, and garbage collection tracking. Perfect for debugging performance issues, memory leaks, and optimizing application performance.

926 lines 34.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.V8Profiler = exports.Profiler = exports.MemMonitor = exports.Timer = void 0; exports.profile = profile; exports.Profile = Profile; exports.detectMemLeak = detectMemLeak; /** * Format memory usage in MB */ const formatMemMB = (bytes) => { return `${(bytes / 1024 / 1024).toFixed(2)}MB`; }; /** * Calculate memory delta between two memory usage objects */ const calcMemDelta = (before, after) => ({ heapUsed: after.heapUsed - before.heapUsed, heapTotal: after.heapTotal - before.heapTotal, external: after.external - before.external, rss: after.rss - before.rss, }); /** * Check if GC should be triggered based on memory threshold */ const shouldTriggerGC = (memUsage, thresholdMB) => { const heapUsedMB = memUsage.heapUsed / 1024 / 1024; return heapUsedMB > thresholdMB; }; /** * Profile a function execution with timing and memory monitoring * * @example * ```typescript * // As a wrapper function * const result = await profile( * () => someAsyncOperation(), * 'MyOperation', * { trackMemory: true, enableGC: true } * ); * * // Using with CarService method * const { result: cars } = await profile( * () => this.carRepository.find({ where: { marketplaceVisible: true } }), * 'fetchMarketplaceCars', * { * trackMemory: true, * enableGC: true, * gcThresholdMB: 100, * logger: this.logger, * tags: { operation: 'database-query' } * } * ); * ``` */ async function profile(fn, name, config = {}) { const { trackMemory = true, enableGC = false, gcThresholdMB = 100, logger = console, tags = {}, logLevel = 'debug', silent = false, verbose = false, } = config; const startTime = performance.now(); let memBefore; let memAfter; let gcTriggered = false; let result; let error; // Capture initial memory state if (trackMemory) { memBefore = process.memoryUsage(); } try { // Execute the function result = await fn(); } catch (err) { error = err; throw err; // Re-throw the error after capturing it } const endTime = performance.now(); const duration = endTime - startTime; // Capture final memory state and handle GC if (trackMemory) { memAfter = process.memoryUsage(); if (enableGC && shouldTriggerGC(memAfter, gcThresholdMB)) { if (global.gc) { global.gc(); gcTriggered = true; // Capture memory after GC memAfter = process.memoryUsage(); } } } // Calculate memory delta const memDelta = memBefore && memAfter ? calcMemDelta(memBefore, memAfter) : undefined; // Build profile result const profileResult = { name, duration, memBefore, memAfter, memDelta, gcTriggered, tags, result, error, }; // Log the profiling results if (!silent && verbose) { const logData = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ function: name, duration: `${duration.toFixed(2)}ms` }, (memBefore && { memBefore: { heap: formatMemMB(memBefore.heapUsed), rss: formatMemMB(memBefore.rss), }, })), (memAfter && { memAfter: { heap: formatMemMB(memAfter.heapUsed), rss: formatMemMB(memAfter.rss), }, })), (memDelta && { memDelta: { heap: `${memDelta.heapUsed >= 0 ? '+' : ''}${formatMemMB(memDelta.heapUsed)}`, rss: `${memDelta.rss >= 0 ? '+' : ''}${formatMemMB(memDelta.rss)}`, }, })), (gcTriggered && { gcTriggered: true })), tags); if (error) { logData.error = error instanceof Error ? error.message : String(error); } const logMessage = `Profile: ${name} completed in ${duration.toFixed(2)}ms`; // Log based on the specified log level if (logLevel && logger && typeof logger[logLevel] === 'function') { logger[logLevel](Object.assign({ message: logMessage }, logData)); } else if (logger && typeof logger.log === 'function') { logger.log(Object.assign({ message: logMessage }, logData)); } else { console.log(Object.assign({ message: logMessage }, logData)); } } return { result, profile: profileResult }; } /** * Decorator for profiling class methods * * @example * ```typescript * class CarService { * @Profile('listCars', { trackMemory: true, enableGC: true }) * async listCar(data: AdminListCarRequestPayload.AsObject) { * // method implementation * } * * @Profile('searchCars', { * trackMemory: true, * gcThresholdMB: 150, * tags: { operation: 'search' } * }) * async searchCar(data: MarketplaceSearchRequestPayload.AsObject) { * // method implementation * } * } * ``` */ function Profile(name, config = {}) { return function (target, context) { const originalMethod = target; const functionName = name || `${String(context.name)}`; return async function (...args) { const { result } = await profile(() => originalMethod.apply(this, args), functionName, config); return result; }; }; } /** * Simple timing utility for quick performance checks * * @example * ```typescript * // Basic usage * const timer = new Timer('database-query'); * const cars = await this.carRepository.find(); * timer.stop({ carCount: cars.length }); * * // Static method usage * const result = await Timer.time( * () => this.processLargeBatch(data), * 'processLargeBatch' * ); * ``` */ class Timer { constructor(name, logger = console) { this.name = name; this.logger = logger; this.startTime = performance.now(); } stop(data) { const endTime = performance.now(); const duration = endTime - this.startTime; if (typeof this.logger.debug === 'function') { this.logger.debug(Object.assign({ message: `Timer: ${this.name} completed in ${duration.toFixed(2)}ms`, duration: `${duration.toFixed(2)}ms` }, data)); } else { this.logger.log(Object.assign({ message: `Timer: ${this.name} completed in ${duration.toFixed(2)}ms`, duration: `${duration.toFixed(2)}ms` }, data)); } return duration; } static async time(fn, name, logger = console) { const timer = new Timer(name, logger); try { const result = await fn(); timer.stop(); return result; } catch (error) { timer.stop({ error: error instanceof Error ? error.message : String(error) }); throw error; } } } exports.Timer = Timer; /** * Memory monitoring utility for tracking memory usage over time * * @example * ```typescript * const monitor = new MemMonitor(this.logger); * * monitor.snap('start'); * await this.processAlerts(alerts); * monitor.snap('after-processing'); * * // Compare memory usage * monitor.compare('start', 'after-processing'); * * // Get all snapshots for analysis * const snapshots = monitor.getSnaps(); * ``` */ class MemMonitor { constructor(logger = console) { this.snaps = []; this.logger = logger; } snap(label) { const usage = process.memoryUsage(); this.snaps.push({ timestamp: Date.now(), usage, label, }); if (typeof this.logger.debug === 'function') { this.logger.debug({ message: `Memory snapshot: ${label || 'unnamed'}`, heap: formatMemMB(usage.heapUsed), rss: formatMemMB(usage.rss), external: formatMemMB(usage.external), }); } else { this.logger.log({ message: `Memory snapshot: ${label || 'unnamed'}`, heap: formatMemMB(usage.heapUsed), rss: formatMemMB(usage.rss), external: formatMemMB(usage.external), }); } return usage; } compare(fromLabel, toLabel) { const from = fromLabel ? this.snaps.find((s) => s.label === fromLabel) : this.snaps[0]; const to = toLabel ? this.snaps.find((s) => s.label === toLabel) : this.snaps[this.snaps.length - 1]; if (!from || !to) { if (typeof this.logger.warn === 'function') { this.logger.warn('Memory comparison failed: snapshot not found'); } else { this.logger.warn('Memory comparison failed: snapshot not found'); } return; } const delta = calcMemDelta(from.usage, to.usage); const duration = to.timestamp - from.timestamp; if (typeof this.logger.debug === 'function') { this.logger.debug({ message: `Memory comparison: ${from.label || 'start'} → ${to.label || 'end'}`, duration: `${duration}ms`, heapDelta: `${delta.heapUsed >= 0 ? '+' : ''}${formatMemMB(delta.heapUsed)}`, rssDelta: `${delta.rss >= 0 ? '+' : ''}${formatMemMB(delta.rss)}`, externalDelta: `${delta.external >= 0 ? '+' : ''}${formatMemMB(delta.external)}`, }); } else { this.logger.log({ message: `Memory comparison: ${from.label || 'start'} → ${to.label || 'end'}`, duration: `${duration}ms`, heapDelta: `${delta.heapUsed >= 0 ? '+' : ''}${formatMemMB(delta.heapUsed)}`, rssDelta: `${delta.rss >= 0 ? '+' : ''}${formatMemMB(delta.rss)}`, externalDelta: `${delta.external >= 0 ? '+' : ''}${formatMemMB(delta.external)}`, }); } } clear() { this.snaps = []; } getSnaps() { return [...this.snaps]; } } exports.MemMonitor = MemMonitor; /** * Advanced profiler for continuous monitoring * Useful for monitoring memory-intensive operations like alert processing * * @example * ```typescript * const profiler = new Profiler('alert-processing', { * enableGC: true, * gcThresholdMB: 100, * logger: this.logger * }); * * profiler.start(); * * for (const alert of alerts) { * profiler.mark(`processing-alert-${alert.id}`); * await this.processAlert(alert); * } * * const summary = profiler.end(); * // Returns detailed performance summary * ``` */ class Profiler { constructor(name, config = {}) { this.startTime = 0; this.marks = []; this.gcCount = 0; this.name = name; this.config = Object.assign({ trackMemory: true, enableGC: false, gcThresholdMB: 100, logger: console, logLevel: 'debug', logSteps: true, verbose: false, silent: false }, config); } start() { this.startTime = performance.now(); this.marks = [ { label: 'start', timestamp: this.startTime, memory: this.config.trackMemory ? process.memoryUsage() : undefined, gcTriggered: false, }, ]; this.gcCount = 0; // Only log start message if enabled and verbose mode if (!this.config.silent && this.config.verbose) { this.log('Profiler started'); } } mark(label) { const timestamp = performance.now(); let memory; let gcTriggered = false; if (this.config.trackMemory) { memory = process.memoryUsage(); if (this.config.enableGC && this.config.gcThresholdMB && shouldTriggerGC(memory, this.config.gcThresholdMB)) { if (global.gc) { global.gc(); gcTriggered = true; this.gcCount++; // Capture memory after GC memory = process.memoryUsage(); } } } // Calculate duration since last checkpoint const lastMark = this.marks[this.marks.length - 1]; const stepDuration = lastMark ? timestamp - lastMark.timestamp : timestamp - this.startTime; this.marks.push({ label, timestamp, memory, gcTriggered, }); // Enhanced logging with phase duration const logData = Object.assign(Object.assign({ mark: label, totalElapsed: `${(timestamp - this.startTime).toFixed(2)}ms`, stepDuration: `${stepDuration.toFixed(2)}ms` }, (memory && { memory: { heap: formatMemMB(memory.heapUsed), rss: formatMemMB(memory.rss), }, })), (gcTriggered && { gcTriggered: true })); // Show memory delta from previous checkpoint if ((lastMark === null || lastMark === void 0 ? void 0 : lastMark.memory) && memory) { const delta = calcMemDelta(lastMark.memory, memory); logData.memDelta = { heap: `${delta.heapUsed >= 0 ? '+' : ''}${formatMemMB(delta.heapUsed)}`, rss: `${delta.rss >= 0 ? '+' : ''}${formatMemMB(delta.rss)}`, }; } // Only log checkpoints if enabled and verbose mode if (!this.config.silent && this.config.logSteps && this.config.verbose) { this.log(`Mark: ${label}`, logData); } } /** * Get current performance summary without ending the profiler */ getSummary() { const currentTime = performance.now(); const elapsed = currentTime - this.startTime; const lastMark = this.marks[this.marks.length - 1]; const secondLastMark = this.marks[this.marks.length - 2]; return Object.assign({ name: this.name, elapsed, markCount: this.marks.length, gcCount: this.gcCount }, (lastMark && secondLastMark && { lastStep: `${secondLastMark.label} → ${lastMark.label}`, lastStepDuration: lastMark.timestamp - secondLastMark.timestamp, })); } /** * Get detailed phase breakdown up to current point */ getSteps() { if (this.marks.length < 2) return []; const currentTime = performance.now(); const totalElapsed = currentTime - this.startTime; const steps = []; for (let i = 1; i < this.marks.length; i++) { const prev = this.marks[i - 1]; const current = this.marks[i]; const duration = current.timestamp - prev.timestamp; steps.push({ step: `${prev.label} → ${current.label}`, duration, durationFormatted: `${duration.toFixed(2)}ms`, percentage: (duration / totalElapsed) * 100, }); } return steps.sort((a, b) => b.duration - a.duration); } end() { const endTime = performance.now(); const totalDuration = endTime - this.startTime; // Add final checkpoint for memory tracking if enabled if (this.config.trackMemory) { const lastMark = this.marks[this.marks.length - 1]; if ((lastMark === null || lastMark === void 0 ? void 0 : lastMark.label) !== 'end') { this.mark('end'); } } // Calculate memory deltas and phase durations between checkpoints const memDeltas = []; const steps = []; for (let i = 1; i < this.marks.length; i++) { const prev = this.marks[i - 1]; const current = this.marks[i]; const stepDuration = current.timestamp - prev.timestamp; // Add phase information steps.push({ step: `${prev.label} → ${current.label}`, duration: stepDuration, durationFormatted: `${stepDuration.toFixed(2)}ms`, startTime: prev.timestamp - this.startTime, endTime: current.timestamp - this.startTime, gcTriggered: current.gcTriggered, }); if (prev.memory && current.memory) { const delta = calcMemDelta(prev.memory, current.memory); memDeltas.push({ from: prev.label, to: current.label, duration: stepDuration, delta, }); } } const summary = { name: this.name, totalDuration, totalGCCount: this.gcCount, marks: this.marks.map((cp, index) => (Object.assign(Object.assign(Object.assign({ label: cp.label, timestamp: cp.timestamp - this.startTime, timestampFormatted: `${(cp.timestamp - this.startTime).toFixed(2)}ms` }, (index > 0 && { stepDuration: cp.timestamp - this.marks[index - 1].timestamp, stepDurationFormatted: `${(cp.timestamp - this.marks[index - 1].timestamp).toFixed(2)}ms`, })), (cp.memory && { memory: { heap: formatMemMB(cp.memory.heapUsed), rss: formatMemMB(cp.memory.rss), }, })), { gcTriggered: cp.gcTriggered }))), steps, memDeltas: memDeltas.map((md) => ({ from: md.from, to: md.to, duration: `${md.duration.toFixed(2)}ms`, heapDelta: `${md.delta.heapUsed >= 0 ? '+' : ''}${formatMemMB(md.delta.heapUsed)}`, rssDelta: `${md.delta.rss >= 0 ? '+' : ''}${formatMemMB(md.delta.rss)}`, })), }; // Enhanced logging with phase durations const slowestSteps = steps .sort((a, b) => b.duration - a.duration) .slice(0, 3) .map((p) => `${p.step}: ${p.durationFormatted}`); // Only log completion summary if logging is enabled if (!this.config.silent) { this.log(`Profiler completed: ${this.name}`, Object.assign(Object.assign({ totalDuration: `${totalDuration.toFixed(2)}ms`, marks: this.marks.length, steps: steps.length, gcTriggered: this.gcCount }, (slowestSteps.length > 0 && { slowestSteps })), { tags: this.config.tags })); } return summary; } log(message, data) { const logger = this.config.logger; const logLevel = this.config.logLevel; if (logLevel && logger && typeof logger[logLevel] === 'function') { logger[logLevel](Object.assign({ message }, data)); } else if (logger && typeof logger.log === 'function') { logger.log(Object.assign({ message }, data)); } else { console.log(Object.assign({ message }, data)); } } } exports.Profiler = Profiler; /** * Utility function to monitor memory leaks * Useful for detecting memory growth patterns */ function detectMemLeak(measurements, threshold = 50) { if (measurements.length < 2) { return { isLeaking: false, trend: 'stable', avgGrowth: 0 }; } const deltas = []; for (let i = 1; i < measurements.length; i++) { const delta = measurements[i].heapUsed - measurements[i - 1].heapUsed; deltas.push(delta); } const avgGrowth = deltas.reduce((sum, delta) => sum + delta, 0) / deltas.length; const avgGrowthMB = avgGrowth / 1024 / 1024; let trend = 'stable'; if (avgGrowthMB > 1) trend = 'increasing'; else if (avgGrowthMB < -1) trend = 'decreasing'; const isLeaking = avgGrowthMB > threshold; return { isLeaking, trend, avgGrowth: avgGrowthMB, }; } /** * Advanced V8 profiler for long-running application analysis * Provides CPU profiling and memory allocation tracking with automatic * interval-based logging to prevent memory buildup * * @example * ```typescript * // Start continuous profiling with 60-minute intervals * const v8Profiler = new V8Profiler('production-app', { * maxMemoryBudgetMB: 100, * intervalMinutes: 60, * cpuProfiling: true, * samplingHeapProfiler: true * }); * * await v8Profiler.startContinuousProfiling(); * // Runs indefinitely, auto-logs insights every 60 minutes * * // Later, stop profiling * await v8Profiler.stopContinuousProfiling(); * ``` */ class V8Profiler { constructor(_name, options) { this.currentMemoryUsage = 0; this.profilingStartTime = 0; this.isRunning = false; this.options = Object.assign({ intervalMinutes: 60, streamingMode: true, cpuProfiling: true, samplingHeapProfiler: true, logger: console, suppressWarnings: false }, options); this.validateConfiguration(); this.initializeInspector(); } /** * Initialize V8 Inspector session */ initializeInspector() { try { // Dynamic import to handle environments where inspector might not be available const inspector = require('inspector'); this.session = new inspector.Session(); this.session.connect(); } catch (error) { throw new Error('V8 Inspector not available. Run with --inspect flag or in supported environment.'); } } /** * Validate configuration and show warnings if needed */ validateConfiguration() { const { maxMemoryBudgetMB, intervalMinutes } = this.options; // Estimate memory usage per minute (rough heuristics) const estimatedMBPerMinute = this.calculateEstimatedUsage(); const projectedUsage = estimatedMBPerMinute * intervalMinutes; // Warning thresholds if (projectedUsage > maxMemoryBudgetMB * 0.8) { this.warn(`High memory risk: ${intervalMinutes}min interval may use ~${projectedUsage.toFixed(1)}MB, ` + `approaching limit of ${maxMemoryBudgetMB}MB`); this.warn(`Consider: reducing intervalMinutes to ${Math.floor(maxMemoryBudgetMB * 0.8 / estimatedMBPerMinute)} ` + `or increasing maxMemoryBudgetMB to ${Math.ceil(projectedUsage * 1.2)}`); } if (intervalMinutes > 240) { // 4+ hours this.warn(`Very long interval (${intervalMinutes}min) may accumulate significant data. ` + `Consider shorter intervals for better memory management.`); } if (maxMemoryBudgetMB < 50) { this.warn(`Low memory budget (${maxMemoryBudgetMB}MB) may cause frequent early flushes. ` + `Consider increasing budget or reducing interval.`); } } /** * Calculate estimated memory usage per minute */ calculateEstimatedUsage() { let mbPerMinute = 0; if (this.options.cpuProfiling) { mbPerMinute += 0.5; // ~0.5MB per minute for CPU profiling } if (this.options.samplingHeapProfiler) { mbPerMinute += 1.0; // ~1MB per minute for heap sampling } return mbPerMinute; } /** * Log warning message if warnings are not suppressed */ warn(message) { if (!this.options.suppressWarnings) { const logger = this.options.logger; if (typeof logger.warn === 'function') { logger.warn(`V8Profiler Warning: ${message}`); } else { logger.warn(`V8Profiler Warning: ${message}`); } } } /** * Log info message */ log(message, data) { const logger = this.options.logger; if (typeof logger.debug === 'function') { logger.debug(Object.assign({ message: `V8Profiler: ${message}` }, data)); } else { logger.log(Object.assign({ message: `V8Profiler: ${message}` }, data)); } } /** * Start continuous V8 profiling with automatic interval logging */ async startContinuousProfiling() { if (this.isRunning) { throw new Error('V8 profiling is already running'); } try { await this.startProfilingSession(); this.setupIntervalTimer(); this.isRunning = true; this.log(`Started continuous profiling with ${this.options.intervalMinutes}min intervals`); } catch (error) { throw new Error(`Failed to start V8 profiling: ${error instanceof Error ? error.message : String(error)}`); } } /** * Stop continuous V8 profiling */ async stopContinuousProfiling() { if (!this.isRunning) { throw new Error('V8 profiling is not running'); } // Clear interval timer if (this.intervalTimer) { clearInterval(this.intervalTimer); this.intervalTimer = undefined; } // Get final insights before stopping const insights = await this.flushCurrentInterval(); // Stop profiling sessions await this.stopProfilingSession(); this.isRunning = false; this.log('Stopped continuous profiling'); return insights; } /** * Manually flush current interval (useful for testing) */ async flushCurrentInterval() { if (!this.isRunning) { throw new Error('V8 profiling is not running'); } const insights = await this.collectInsights(); await this.resetProfilingSession(); this.log('Flushed profiling interval', { duration: `${insights.duration.toFixed(2)}ms`, topFunctions: insights.topFunctions.length, memoryHotSpots: insights.memoryHotspots.length, }); return insights; } /** * Check if memory budget is exceeded */ isMemoryBudgetExceeded() { return this.currentMemoryUsage > this.options.maxMemoryBudgetMB; } /** * Get current memory usage by V8 profiler */ getCurrentMemoryUsage() { return this.currentMemoryUsage; } /** * Start V8 profiling session */ async startProfilingSession() { this.profilingStartTime = performance.now(); this.currentMemoryUsage = 0; const promises = []; if (this.options.cpuProfiling) { promises.push(this.session.post('Profiler.enable')); promises.push(this.session.post('Profiler.start')); } if (this.options.samplingHeapProfiler) { promises.push(this.session.post('HeapProfiler.enable')); promises.push(this.session.post('HeapProfiler.startSampling', { samplingInterval: 32768 })); } await Promise.all(promises); } /** * Stop V8 profiling session */ async stopProfilingSession() { const promises = []; if (this.options.cpuProfiling) { promises.push(this.session.post('Profiler.stop')); promises.push(this.session.post('Profiler.disable')); } if (this.options.samplingHeapProfiler) { promises.push(this.session.post('HeapProfiler.stopSampling')); promises.push(this.session.post('HeapProfiler.disable')); } await Promise.all(promises); } /** * Reset profiling session (for interval flushing) */ async resetProfilingSession() { await this.stopProfilingSession(); await this.startProfilingSession(); } /** * Setup interval timer for automatic flushing */ setupIntervalTimer() { const intervalMs = this.options.intervalMinutes * 60 * 1000; this.intervalTimer = setInterval(async () => { try { await this.flushCurrentInterval(); } catch (error) { this.log('Error during interval flush', { error: error instanceof Error ? error.message : String(error) }); } }, intervalMs); } /** * Collect and process V8 insights */ async collectInsights() { const endTime = performance.now(); const duration = endTime - this.profilingStartTime; const insights = { duration, topFunctions: [], memoryHotspots: [], gcImpact: { gcTime: 0, gcCount: 0, avgGcDuration: 0, }, memoryUsage: { heap: '', rss: '', external: '', }, }; const promises = []; // Collect CPU profile data if (this.options.cpuProfiling) { promises.push(this.collectCPUProfile(insights)); } // Collect heap allocation data if (this.options.samplingHeapProfiler) { promises.push(this.collectHeapProfile(insights)); } // Collect current memory usage promises.push(this.collectMemoryUsage(insights)); await Promise.all(promises); return insights; } /** * Collect CPU profiling insights */ async collectCPUProfile(insights) { try { const { profile } = await this.session.post('Profiler.stop'); await this.session.post('Profiler.start'); // Restart for continuous profiling if (profile && profile.nodes) { const functionTimes = new Map(); // Process profile nodes to extract function timing data profile.nodes.forEach((node) => { if (node.callFrame && node.callFrame.functionName) { const funcName = node.callFrame.functionName || 'anonymous'; const selfTime = node.hitCount ? node.hitCount * profile.timeInterval : 0; if (functionTimes.has(funcName)) { const existing = functionTimes.get(funcName); existing.selfTime += selfTime; existing.totalTime += selfTime; } else { functionTimes.set(funcName, { selfTime, totalTime: selfTime }); } } }); // Sort by self time and get top functions const sortedFunctions = Array.from(functionTimes.entries()) .map(([name, times]) => ({ functionName: name, selfTime: times.selfTime / 1000, // Convert to ms totalTime: times.totalTime / 1000, percentage: (times.selfTime / insights.duration) * 100, })) .sort((a, b) => b.selfTime - a.selfTime) .slice(0, 10); insights.topFunctions = sortedFunctions; // Update memory usage estimate this.currentMemoryUsage += this.estimateProfileSize(profile); } } catch (error) { this.log('Error collecting CPU profile', { error: error instanceof Error ? error.message : String(error) }); } } /** * Collect heap profiling insights */ async collectHeapProfile(insights) { try { const { profile } = await this.session.post('HeapProfiler.stopSampling'); await this.session.post('HeapProfiler.startSampling', { samplingInterval: 32768 }); if (profile && profile.samples) { const allocationMap = new Map(); profile.samples.forEach((sample) => { if (sample.stack && sample.stack.length > 0) { const topFrame = sample.stack[0]; const location = `${topFrame.functionName || 'anonymous'} at ${topFrame.scriptName || 'unknown'}:${topFrame.lineNumber || 0}`; if (allocationMap.has(location)) { const existing = allocationMap.get(location); existing.size += sample.size || 0; existing.count += 1; } else { allocationMap.set(location, { size: sample.size || 0, count: 1 }); } } }); // Sort by size and get top allocations const sortedAllocations = Array.from(allocationMap.entries()) .map(([location, data]) => ({ allocation: location, size: data.size, count: data.count, })) .sort((a, b) => b.size - a.size) .slice(0, 10); insights.memoryHotspots = sortedAllocations; // Update memory usage estimate this.currentMemoryUsage += this.estimateProfileSize(profile); } } catch (error) { this.log('Error collecting heap profile', { error: error instanceof Error ? error.message : String(error) }); } } /** * Collect current memory usage */ async collectMemoryUsage(insights) { const memUsage = process.memoryUsage(); insights.memoryUsage = { heap: formatMemMB(memUsage.heapUsed), rss: formatMemMB(memUsage.rss), external: formatMemMB(memUsage.external), }; } /** * Estimate memory size of profile data */ estimateProfileSize(profile) { try { const jsonString = JSON.stringify(profile); return (jsonString.length * 2) / 1024 / 1024; // Rough estimate in MB } catch (_a) { return 1; // Default estimate if JSON.stringify fails } } } exports.V8Profiler = V8Profiler; //# sourceMappingURL=index.js.map