const heapdump = require('heapdump');process.on('SIGUSR2', () => { heapdump.writeSnapshot((err, filename) => { if (err) console.error('Heap snapshot failed:', err); else console.log('Heap snapshot written to:', filename); });});
const v8 = require('v8');let lastHeapUsed = 0;setInterval(() => { const heapStats = v8.getHeapStatistics(); const currentHeapUsed = heapStats.used_heap_size; if (currentHeapUsed > lastHeapUsed * 1.5) { heapdump.writeSnapshot(); lastHeapUsed = currentHeapUsed; }}, 30000);
// Phiên bản gây rò rỉ:class DataProcessor { constructor() { this.cache = new Map(); this.setupEventListeners(); } setupEventListeners() { process.on('data-update', (data) => { this.cache.set(data.id, data); }); }}
// Phiên bản đã fix:class DataProcessor { constructor() { this.cache = new Map(); this.dataUpdateHandler = this.handleDataUpdate.bind(this); this.setupEventListeners(); } handleDataUpdate(data) { this.cache.set(data.id, data); } setupEventListeners() { process.on('data-update', this.dataUpdateHandler); } cleanup() { process.removeListener('data-update', this.dataUpdateHandler); this.cache.clear(); }}
npm install -g clinic
Lệnh | Mục Đích |
---|---|
clinic doctor -- node app.js | Báo cáo tổng quan hiệu năng |
clinic heapprofiler -- node app.js | Phân tích bộ nhớ và heap |
clinic flame -- node app.js | Tạo biểu đồ ngọn lửa trình bày hoạt động CPU và bộ nhớ |
const { PerformanceObserver } = require('perf_hooks');class MemoryMonitor { constructor() { this.startMonitoring(); } startMonitoring() { const obs = new PerformanceObserver((list) => { list.getEntries().forEach((entry) => { if (entry.entryType === 'gc') { console.log(`GC ${entry.kind}: ${entry.duration}ms`); } }); }); obs.observe({ entryTypes: ['gc'] }); setInterval(() => { const usage = process.memoryUsage(); console.log('Memory Usage:', { rss: Math.round(usage.rss / 1024 / 1024) + 'MB', heapUsed: Math.round(usage.heapUsed / 1024 / 1024) + 'MB', heapTotal: Math.round(usage.heapTotal / 1024 / 1024) + 'MB', }); }, 10000); }}
const v8 = require('v8');class V8MemoryAnalyzer { static getDetailedHeapStats() { const heapStats = v8.getHeapStatistics(); const heapSpaceStats = v8.getHeapSpaceStatistics(); return { heap: { totalHeapSize: heapStats.total_heap_size, totalHeapSizeExecutable: heapStats.total_heap_size_executable, totalPhysicalSize: heapStats.total_physical_size, totalAvailableSize: heapStats.total_available_size, usedHeapSize: heapStats.used_heap_size, heapSizeLimit: heapStats.heap_size_limit, }, spaces: heapSpaceStats.map(space => ({ name: space.space_name, size: space.space_size, used: space.space_used_size, available: space.space_available_size, physical: space.physical_space_size, })), }; } static generateHeapSnapshot() { const snapshotStream = v8.getHeapSnapshot(); const chunks = []; return new Promise((resolve, reject) => { snapshotStream.on('data', chunk => chunks.push(chunk)); snapshotStream.on('end', () => { const snapshot = Buffer.concat(chunks).toString(); resolve(JSON.parse(snapshot)); }); snapshotStream.on('error', reject); }); }}
class MemoryLeakDetector { constructor(options = {}) { this.threshold = options.threshold || 50; // MB this.interval = options.interval || 60000; // 1 phút this.samples = []; this.maxSamples = options.maxSamples || 10; } startMonitoring() { setInterval(() => { const stats = V8MemoryAnalyzer.getDetailedHeapStats(); this.samples.push({ timestamp: Date.now(), heapUsed: stats.heap.usedHeapSize }); if (this.samples.length > this.maxSamples) { this.samples.shift(); } this.analyzeMemoryTrend(); }, this.interval); } analyzeMemoryTrend() { if (this.samples.length < 3) return; const recent = this.samples.slice(-3); const isIncreasing = recent.every((sample, i) => i === 0 || sample.heapUsed > recent[i-1].heapUsed); if (isIncreasing) { const growthRate = (recent[2].heapUsed - recent[0].heapUsed) / (recent[2].timestamp - recent[0].timestamp); if (growthRate > this.threshold * 1024 * 1024 / 60000) { this.onMemoryLeakDetected(growthRate); } } } onMemoryLeakDetected(growthRate) { console.warn(`Memory leak detected! Growth rate: ${ Math.round(growthRate / 1024 / 1024 * 60000) }MB/min`); const heapdump = require('heapdump'); heapdump.writeSnapshot(); }}
Tiêu chí | Production Debugging | Development Analysis |
---|---|---|
Overhead | ✅ Thấp | ⚠️ Cao hơn |
Độ chi tiết | ⚠️ Giới hạn | ✅ Snapshot chi tiết |
Tự động theo dõi | ✅ Có thể cài đặt | ⚠️ Thường thủ công |
Phân tích hiệu năng | ⚠️ Giới hạn | ✅ Báo cáo toàn diện |
Tích hợp monitoring | ✅ Được khuyến khích | ✅ Thường xuyên sử dụng |
class ComprehensiveMemoryDebugger { constructor() { this.heapMonitor = new MemoryLeakDetector(); this.memoryMonitor = new MemoryMonitor(); this.heapMonitor.onMemoryLeakDetected = (growthRate) => { this.captureDebugSnapshot(growthRate); }; } async captureDebugSnapshot(growthRate) { const timestamp = new Date().toISOString(); const heapdump = require('heapdump'); const heapFile = `heap-${timestamp}.heapsnapshot`; heapdump.writeSnapshot(heapFile); const v8Stats = V8MemoryAnalyzer.getDetailedHeapStats(); const debugReport = { timestamp, growthRate: Math.round(growthRate / 1024 / 1024 * 60000), heapSnapshot: heapFile, v8Statistics: v8Stats, processMemory: process.memoryUsage(), }; console.log('Debug snapshot captured:', debugReport); return debugReport; }}
function generateFilteredSnapshot(filter = {}) { return new Promise((resolve, reject) => { const snapshot = v8.getHeapSnapshot(); let jsonData = ''; snapshot.on('data', chunk => { jsonData += chunk; }); snapshot.on('end', () => { const parsed = JSON.parse(jsonData); if (filter.excludeArrays) { parsed.nodes = parsed.nodes.filter(node => parsed.strings[node.type] !== 'Array' || node.self_size < filter.maxArraySize); } resolve(JSON.stringify(parsed)); }); snapshot.on('error', reject); });}
const assert = require('assert');describe('Memory Leak Tests', () => { it('should not leak memory during repeated operations', async () => { const initialMemory = process.memoryUsage().heapUsed; for (let i = 0; i < 1000; i++) { await performOperation(); if (global.gc) { global.gc(); } } const finalMemory = process.memoryUsage().heapUsed; const memoryGrowth = finalMemory - initialMemory; assert(memoryGrowth < 10 * 1024 * 1024, `Memory grew by ${memoryGrowth / 1024 / 1024}MB`); });});