/** * Memory Profiler - Track memory usage during installation * Helps identify memory leaks and optimize resource usage */ const v8 = require('v8') class MemoryProfiler { constructor () { this.checkpoints = [] this.startTime = Date.now() this.peakMemory = 0 } /** * Create a memory checkpoint * @param {string} label - Label for this checkpoint */ checkpoint (label) { const memUsage = process.memoryUsage() const heapStats = v8.getHeapStatistics() const checkpoint = { label, timestamp: Date.now() - this.startTime, memory: { rss: this.formatBytes(memUsage.rss), heapTotal: this.formatBytes(memUsage.heapTotal), heapUsed: this.formatBytes(memUsage.heapUsed), external: this.formatBytes(memUsage.external), arrayBuffers: this.formatBytes(memUsage.arrayBuffers || 0) }, heap: { totalHeapSize: this.formatBytes(heapStats.total_heap_size), usedHeapSize: this.formatBytes(heapStats.used_heap_size), heapSizeLimit: this.formatBytes(heapStats.heap_size_limit), mallocedMemory: this.formatBytes(heapStats.malloced_memory), externalMemory: this.formatBytes(heapStats.external_memory) }, raw: { heapUsed: memUsage.heapUsed } } // Track peak memory if (memUsage.heapUsed > this.peakMemory) { this.peakMemory = memUsage.heapUsed } this.checkpoints.push(checkpoint) return checkpoint } /** * Force garbage collection (requires --expose-gc flag) */ forceGC () { if (global.gc) { global.gc() return true } return false } /** * Get memory usage summary */ getSummary () { const currentMemory = process.memoryUsage() return { currentUsage: { rss: this.formatBytes(currentMemory.rss), heapTotal: this.formatBytes(currentMemory.heapTotal), heapUsed: this.formatBytes(currentMemory.heapUsed) }, peakMemory: this.formatBytes(this.peakMemory), totalCheckpoints: this.checkpoints.length, runTime: `${((Date.now() - this.startTime) / 1000).toFixed(2)}s` } } /** * Get detailed report of memory usage */ getDetailedReport () { const summary = this.getSummary() const memoryGrowth = this.calculateMemoryGrowth() return { summary, memoryGrowth, checkpoints: this.checkpoints, recommendations: this.getRecommendations(memoryGrowth) } } /** * Calculate memory growth between checkpoints */ calculateMemoryGrowth () { if (this.checkpoints.length < 2) return [] const growth = [] for (let i = 1; i < this.checkpoints.length; i++) { const prev = this.checkpoints[i - 1] const curr = this.checkpoints[i] const heapDiff = curr.raw.heapUsed - prev.raw.heapUsed growth.push({ from: prev.label, to: curr.label, heapGrowth: this.formatBytes(Math.abs(heapDiff)), isIncrease: heapDiff > 0, timeDiff: `${((curr.timestamp - prev.timestamp) / 1000).toFixed(2)}s` }) } return growth } /** * Get recommendations based on memory usage */ getRecommendations (memoryGrowth) { const recommendations = [] // Check for large memory growth const largeGrowths = memoryGrowth.filter(g => { const bytes = this.parseBytes(g.heapGrowth) return bytes > 50 * 1024 * 1024 // 50MB }) if (largeGrowths.length > 0) { recommendations.push({ type: 'warning', message: `Large memory growth detected in ${largeGrowths.length} operations`, details: largeGrowths.map(g => `${g.from} → ${g.to}: ${g.heapGrowth}`) }) } // Check peak memory if (this.peakMemory > 500 * 1024 * 1024) { // 500MB recommendations.push({ type: 'warning', message: `High peak memory usage: ${this.formatBytes(this.peakMemory)}`, suggestion: 'Consider processing files in smaller batches' }) } // Check for potential memory leaks const continuousGrowth = this.checkContinuousGrowth() if (continuousGrowth) { recommendations.push({ type: 'error', message: 'Potential memory leak detected', details: 'Memory usage continuously increases without significant decreases' }) } return recommendations } /** * Check for continuous memory growth (potential leak) */ checkContinuousGrowth () { if (this.checkpoints.length < 5) return false let increasingCount = 0 for (let i = 1; i < this.checkpoints.length; i++) { if (this.checkpoints[i].raw.heapUsed > this.checkpoints[i - 1].raw.heapUsed) { increasingCount++ } } // If memory increases in more than 80% of checkpoints, might be a leak return increasingCount / (this.checkpoints.length - 1) > 0.8 } /** * Format bytes to human-readable string */ formatBytes (bytes) { if (bytes === 0) return '0 B' const k = 1024 const sizes = ['B', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] } /** * Parse human-readable bytes back to number */ parseBytes (str) { const match = str.match(/^([\d.]+)\s*([KMGT]?B?)$/i) if (!match) return 0 const value = parseFloat(match[1]) const unit = match[2].toUpperCase() const multipliers = { B: 1, KB: 1024, MB: 1024 * 1024, GB: 1024 * 1024 * 1024 } return value * (multipliers[unit] || 1) } /** * Clear checkpoints to free memory */ clear () { this.checkpoints = [] } } // Export singleton instance module.exports = new MemoryProfiler()