UNPKG

@ai-capabilities-suite/mcp-debugger-core

Version:

Core debugging engine for Node.js and TypeScript applications. Provides Inspector Protocol integration, breakpoint management, variable inspection, execution control, profiling, hang detection, and source map support.

263 lines 9.67 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MemoryProfiler = void 0; /** * Memory Profiler for capturing heap snapshots and detecting memory leaks * Uses the HeapProfiler domain of Chrome DevTools Protocol */ class MemoryProfiler { constructor(inspector) { this.tracking = false; this.snapshots = []; this.memoryUsageHistory = []; this.inspector = inspector; } /** * Enable heap profiling */ async enable() { await this.inspector.send('HeapProfiler.enable'); } /** * Disable heap profiling */ async disable() { await this.inspector.send('HeapProfiler.disable'); this.tracking = false; } /** * Take a heap snapshot * @returns The heap snapshot data */ async takeHeapSnapshot() { await this.enable(); // Take the snapshot // The snapshot is sent in chunks via HeapProfiler.addHeapSnapshotChunk events const chunks = []; const chunkHandler = (params) => { chunks.push(params.chunk); }; this.inspector.on('HeapProfiler.addHeapSnapshotChunk', chunkHandler); try { await this.inspector.send('HeapProfiler.takeHeapSnapshot', { reportProgress: false, }); // Parse the complete snapshot const snapshotJson = chunks.join(''); const snapshot = JSON.parse(snapshotJson); // Store the snapshot this.snapshots.push(snapshot); return snapshot; } finally { this.inspector.off('HeapProfiler.addHeapSnapshotChunk', chunkHandler); } } /** * Start tracking memory allocation over time * @param samplingInterval Sampling interval in bytes (default: 32768) */ async startTrackingHeapObjects(samplingInterval = 32768) { await this.enable(); await this.inspector.send('HeapProfiler.startTrackingHeapObjects', { trackAllocations: true, }); this.tracking = true; } /** * Stop tracking memory allocation * @param reportProgress Whether to report progress * @returns The final heap snapshot */ async stopTrackingHeapObjects(reportProgress = false) { if (!this.tracking) { throw new Error('Heap tracking is not active'); } const chunks = []; const chunkHandler = (params) => { chunks.push(params.chunk); }; this.inspector.on('HeapProfiler.addHeapSnapshotChunk', chunkHandler); try { await this.inspector.send('HeapProfiler.stopTrackingHeapObjects', { reportProgress, }); this.tracking = false; // Parse the complete snapshot const snapshotJson = chunks.join(''); const snapshot = JSON.parse(snapshotJson); // Store the snapshot this.snapshots.push(snapshot); return snapshot; } finally { this.inspector.off('HeapProfiler.addHeapSnapshotChunk', chunkHandler); } } /** * Get current memory usage statistics * @returns Memory usage information */ async getMemoryUsage() { // Use Runtime.getHeapUsage for quick memory stats const result = await this.inspector.send('Runtime.getHeapUsage'); const usage = { usedSize: result.usedSize, totalSize: result.totalSize, timestamp: Date.now(), }; this.memoryUsageHistory.push(usage); return usage; } /** * Collect garbage to clean up unreferenced objects */ async collectGarbage() { await this.inspector.send('HeapProfiler.collectGarbage'); } /** * Detect memory leaks by analyzing heap growth over time * Takes multiple snapshots and compares them * @param durationMs Duration to monitor in milliseconds * @param intervalMs Interval between snapshots in milliseconds * @returns Memory leak analysis */ async detectMemoryLeaks(durationMs = 10000, intervalMs = 2000) { const snapshots = []; const startTime = Date.now(); // Collect initial snapshot await this.collectGarbage(); snapshots.push(await this.getMemoryUsage()); // Collect snapshots at intervals while (Date.now() - startTime < durationMs) { await new Promise((resolve) => setTimeout(resolve, intervalMs)); await this.collectGarbage(); snapshots.push(await this.getMemoryUsage()); } // Analyze growth rate if (snapshots.length < 2) { return { isLeaking: false, growthRate: 0, snapshots, suspiciousObjects: [], }; } const firstSnapshot = snapshots[0]; const lastSnapshot = snapshots[snapshots.length - 1]; const timeDiff = (lastSnapshot.timestamp - firstSnapshot.timestamp) / 1000; // seconds const sizeDiff = lastSnapshot.usedSize - firstSnapshot.usedSize; const growthRate = sizeDiff / timeDiff; // bytes per second // Consider it a leak if memory grows by more than 1MB per second const isLeaking = growthRate > 1024 * 1024; return { isLeaking, growthRate, snapshots, suspiciousObjects: [], // Would need detailed snapshot analysis to populate }; } /** * Generate a memory usage report from a heap snapshot * @param snapshot The heap snapshot to analyze * @returns Memory usage report */ async generateMemoryReport(snapshot) { // Get current heap usage const heapUsage = await this.inspector.send('Runtime.getHeapUsage'); // If no snapshot provided, use the most recent one const targetSnapshot = snapshot || this.snapshots[this.snapshots.length - 1]; const report = { totalHeapSize: heapUsage.totalSize, usedHeapSize: heapUsage.usedSize, heapSizeLimit: heapUsage.totalSize, // Approximation mallocedMemory: 0, peakMallocedMemory: 0, objectTypes: [], }; if (targetSnapshot) { // Analyze object types from snapshot const objectTypeCounts = new Map(); // Parse nodes from the snapshot const nodeFields = targetSnapshot.snapshot.meta.node_fields; const nodeTypes = targetSnapshot.snapshot.meta.node_types[0]; const nodeFieldCount = nodeFields.length; const typeIndex = nodeFields.indexOf('type'); const nameIndex = nodeFields.indexOf('name'); const selfSizeIndex = nodeFields.indexOf('self_size'); for (let i = 0; i < targetSnapshot.nodes.length; i += nodeFieldCount) { const typeIdx = targetSnapshot.nodes[i + typeIndex]; const type = nodeTypes[typeIdx]; const selfSize = targetSnapshot.nodes[i + selfSizeIndex]; const existing = objectTypeCounts.get(type); if (existing) { existing.count++; existing.size += selfSize; } else { objectTypeCounts.set(type, { count: 1, size: selfSize }); } } // Convert to array and sort by size report.objectTypes = Array.from(objectTypeCounts.entries()) .map(([type, data]) => ({ type, count: data.count, size: data.size, percentage: (data.size / report.usedHeapSize) * 100, })) .sort((a, b) => b.size - a.size) .slice(0, 20); // Top 20 types } return report; } /** * Format memory report as a human-readable string * @param report The memory report to format * @returns Formatted string */ formatMemoryReport(report) { const lines = []; const formatBytes = (bytes) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`; return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; }; lines.push('Memory Usage Report'); lines.push('==================='); lines.push(`Total Heap Size: ${formatBytes(report.totalHeapSize)}`); lines.push(`Used Heap Size: ${formatBytes(report.usedHeapSize)}`); lines.push(`Heap Usage: ${((report.usedHeapSize / report.totalHeapSize) * 100).toFixed(2)}%`); lines.push(''); if (report.objectTypes.length > 0) { lines.push('Top Object Types by Size:'); for (const objType of report.objectTypes.slice(0, 10)) { lines.push(` ${objType.percentage.toFixed(2)}% - ${objType.type} (${objType.count} objects, ${formatBytes(objType.size)})`); } } return lines.join('\n'); } /** * Get all captured snapshots */ getSnapshots() { return this.snapshots; } /** * Get memory usage history */ getMemoryUsageHistory() { return this.memoryUsageHistory; } /** * Clear all captured snapshots and history */ clearHistory() { this.snapshots = []; this.memoryUsageHistory = []; } } exports.MemoryProfiler = MemoryProfiler; //# sourceMappingURL=memory-profiler.js.map