BMAD-METHOD/tools/installer/lib/memory-profiler.js

225 lines
5.6 KiB
JavaScript

/**
* 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()