UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

232 lines (231 loc) 8.84 kB
import v8 from 'v8'; import fs from 'fs/promises'; import fsSync from 'fs'; import path from 'path'; import os from 'os'; import logger from '../../../logger.js'; import { getMemoryStats } from '../parser.js'; export class MemoryLeakDetector { options; memorySamples = []; snapshots = []; snapshotTimer = null; checkTimer = null; isInitialized = false; static DEFAULT_OPTIONS = { snapshotDir: path.join(os.tmpdir(), 'code-map-generator-heap-snapshots'), snapshotInterval: 5 * 60 * 1000, maxSnapshots: 5, leakThreshold: 0.2, autoDetect: true, checkInterval: 60 * 1000, maxSamples: 10 }; constructor(options = {}) { this.options = { ...MemoryLeakDetector.DEFAULT_OPTIONS, ...options }; } async init() { if (this.isInitialized) { return; } await fs.mkdir(this.options.snapshotDir, { recursive: true }); if (this.options.autoDetect) { this.startAutomaticDetection(); } this.isInitialized = true; logger.info(`MemoryLeakDetector initialized with snapshot directory: ${this.options.snapshotDir}`); } startAutomaticDetection() { this.startMemorySampling(); this.startSnapshotSchedule(); logger.info('Automatic memory leak detection started'); } stopAutomaticDetection() { if (this.checkTimer) { clearInterval(this.checkTimer); this.checkTimer = null; } if (this.snapshotTimer) { clearInterval(this.snapshotTimer); this.snapshotTimer = null; } logger.info('Automatic memory leak detection stopped'); } startMemorySampling() { if (this.checkTimer) { clearInterval(this.checkTimer); } this.takeMemorySample(); this.checkTimer = setInterval(() => { this.takeMemorySample(); this.analyzeMemoryTrend(); }, this.options.checkInterval); logger.debug(`Memory sampling started with interval: ${this.options.checkInterval}ms`); } takeMemorySample() { const memoryUsage = process.memoryUsage(); const sample = { timestamp: Date.now(), heapUsed: memoryUsage.heapUsed, heapTotal: memoryUsage.heapTotal, rss: memoryUsage.rss, external: memoryUsage.external, arrayBuffers: memoryUsage.arrayBuffers || 0 }; this.memorySamples.push(sample); if (this.memorySamples.length > this.options.maxSamples) { this.memorySamples.shift(); } logger.debug(`Memory sample taken: ${JSON.stringify(sample)}`); } startSnapshotSchedule() { if (this.snapshotTimer) { clearInterval(this.snapshotTimer); } this.snapshotTimer = setInterval(() => { this.takeHeapSnapshot(); }, this.options.snapshotInterval); logger.debug(`Heap snapshot schedule started with interval: ${this.options.snapshotInterval}ms`); } async takeHeapSnapshot() { const timestamp = Date.now(); const snapshotPath = path.join(this.options.snapshotDir, `heap-snapshot-${timestamp}.heapsnapshot`); try { const snapshot = v8.getHeapSnapshot(); const writeStream = fsSync.createWriteStream(snapshotPath); snapshot.pipe(writeStream); await new Promise((resolve, reject) => { writeStream.on('finish', resolve); writeStream.on('error', reject); }); const memoryUsage = process.memoryUsage(); const metadata = { timestamp, path: snapshotPath, memoryUsage: { heapUsed: memoryUsage.heapUsed, heapTotal: memoryUsage.heapTotal, rss: memoryUsage.rss, external: memoryUsage.external, arrayBuffers: memoryUsage.arrayBuffers || 0 } }; this.snapshots.push(metadata); if (this.snapshots.length > this.options.maxSnapshots) { const oldestSnapshot = this.snapshots.shift(); if (oldestSnapshot) { try { await fs.unlink(oldestSnapshot.path); logger.debug(`Deleted old heap snapshot: ${oldestSnapshot.path}`); } catch (error) { logger.warn(`Failed to delete old heap snapshot: ${oldestSnapshot.path}`, { error }); } } } logger.info(`Heap snapshot taken: ${snapshotPath}`); return snapshotPath; } catch (error) { logger.error(`Failed to take heap snapshot: ${error}`); throw error; } } analyzeMemoryTrend() { if (this.memorySamples.length < 2) { const stats = getMemoryStats(); return { leakDetected: false, trend: 'stable', samples: [...this.memorySamples], latestStats: stats, timestamp: Date.now() }; } const oldestSample = this.memorySamples[0]; const newestSample = this.memorySamples[this.memorySamples.length - 1]; const heapUsedChange = (newestSample.heapUsed - oldestSample.heapUsed) / oldestSample.heapUsed; const externalChange = (newestSample.external - oldestSample.external) / oldestSample.external; const arrayBuffersChange = oldestSample.arrayBuffers > 0 ? (newestSample.arrayBuffers - oldestSample.arrayBuffers) / oldestSample.arrayBuffers : 0; let trend = 'stable'; if (heapUsedChange > 0.05) { trend = 'increasing'; } else if (heapUsedChange < -0.05) { trend = 'decreasing'; } let leakDetected = false; let leakType; let increasePercentage; if (heapUsedChange > this.options.leakThreshold) { leakDetected = true; leakType = 'heap'; increasePercentage = heapUsedChange * 100; logger.warn(`Potential heap memory leak detected: ${increasePercentage.toFixed(2)}% increase over ${this.memorySamples.length} samples`); } else if (externalChange > this.options.leakThreshold) { leakDetected = true; leakType = 'external'; increasePercentage = externalChange * 100; logger.warn(`Potential external memory leak detected: ${increasePercentage.toFixed(2)}% increase over ${this.memorySamples.length} samples`); } else if (arrayBuffersChange > this.options.leakThreshold) { leakDetected = true; leakType = 'arrayBuffers'; increasePercentage = arrayBuffersChange * 100; logger.warn(`Potential array buffers memory leak detected: ${increasePercentage.toFixed(2)}% increase over ${this.memorySamples.length} samples`); } const stats = getMemoryStats(); return { leakDetected, leakType, increasePercentage, trend, samples: [...this.memorySamples], latestStats: stats, timestamp: Date.now() }; } async compareHeapSnapshots(snapshot1Path, snapshot2Path) { logger.info(`Comparing heap snapshots: ${snapshot1Path} and ${snapshot2Path}`); const stat1 = await fs.stat(snapshot1Path); const stat2 = await fs.stat(snapshot2Path); const sizeDiff = stat2.size - stat1.size; const percentChange = (sizeDiff / stat1.size) * 100; return { snapshot1Path, snapshot2Path, snapshot1Size: stat1.size, snapshot2Size: stat2.size, sizeDiff, percentChange, memoryDifference: sizeDiff, objectCountDifference: 0, leakSuspects: [], analysis: { hasLeak: sizeDiff > 0, confidence: Math.min(Math.abs(percentChange) / 10, 1), recommendations: sizeDiff > 0 ? ['Monitor memory usage', 'Check for memory leaks'] : ['Memory usage is stable'] } }; } getLatestSnapshot() { if (this.snapshots.length === 0) { return undefined; } return this.snapshots[this.snapshots.length - 1]; } getAllSnapshots() { return [...this.snapshots]; } cleanup() { this.stopAutomaticDetection(); this.memorySamples = []; logger.info('Memory leak detector cleaned up'); } }