Back to Blog
Node.jsinspectorDebuggingProfiling

Node.js inspector Module Guide

Master the Node.js inspector module for debugging, profiling, and runtime code analysis.

B
Bootspring Team
Engineering
January 27, 2019
6 min read

The inspector module provides an API for interacting with the V8 inspector, enabling debugging, profiling, and runtime analysis. Here's how to use it.

Basic Setup#

1import inspector from 'node:inspector'; 2 3// Open inspector on default port (9229) 4inspector.open(); 5 6// Open on specific port 7inspector.open(9230); 8 9// Open with host binding 10inspector.open(9229, 'localhost'); 11 12// Wait for debugger to attach 13inspector.open(9229, 'localhost', true); 14 15// Check if inspector is active 16console.log('Inspector active:', inspector.url()); 17 18// Close inspector 19inspector.close();

Session API#

1import inspector from 'node:inspector'; 2 3// Create a new session 4const session = new inspector.Session(); 5 6// Connect to the inspector 7session.connect(); 8 9// Send commands 10session.post('Runtime.enable', (err) => { 11 if (err) console.error(err); 12}); 13 14// Receive events 15session.on('Runtime.consoleAPICalled', (message) => { 16 console.log('Console called:', message); 17}); 18 19// Disconnect when done 20session.disconnect();

CPU Profiling#

1import inspector from 'node:inspector'; 2import fs from 'node:fs'; 3 4async function profileCPU(durationMs = 5000) { 5 const session = new inspector.Session(); 6 session.connect(); 7 8 return new Promise((resolve, reject) => { 9 // Enable profiler 10 session.post('Profiler.enable', (err) => { 11 if (err) return reject(err); 12 13 // Start profiling 14 session.post('Profiler.start', (err) => { 15 if (err) return reject(err); 16 17 console.log('Profiling started...'); 18 19 // Stop after duration 20 setTimeout(() => { 21 session.post('Profiler.stop', (err, { profile }) => { 22 if (err) return reject(err); 23 24 // Save profile 25 fs.writeFileSync( 26 'cpu-profile.cpuprofile', 27 JSON.stringify(profile) 28 ); 29 30 console.log('Profile saved to cpu-profile.cpuprofile'); 31 32 session.disconnect(); 33 resolve(profile); 34 }); 35 }, durationMs); 36 }); 37 }); 38 }); 39} 40 41// Usage 42await profileCPU(5000);

Heap Snapshot#

1import inspector from 'node:inspector'; 2import fs from 'node:fs'; 3 4async function takeHeapSnapshot() { 5 const session = new inspector.Session(); 6 session.connect(); 7 8 const chunks = []; 9 10 return new Promise((resolve, reject) => { 11 session.on('HeapProfiler.addHeapSnapshotChunk', (message) => { 12 chunks.push(message.params.chunk); 13 }); 14 15 session.post('HeapProfiler.takeHeapSnapshot', null, (err) => { 16 if (err) return reject(err); 17 18 const snapshot = chunks.join(''); 19 fs.writeFileSync('heap-snapshot.heapsnapshot', snapshot); 20 21 console.log('Heap snapshot saved'); 22 23 session.disconnect(); 24 resolve(snapshot); 25 }); 26 }); 27} 28 29// Take snapshots to find memory leaks 30async function findMemoryLeak() { 31 console.log('Taking initial snapshot...'); 32 await takeHeapSnapshot(); 33 34 // Run code that might leak 35 await runPotentiallyLeakyCode(); 36 37 console.log('Taking second snapshot...'); 38 await takeHeapSnapshot(); 39 40 console.log('Compare snapshots in Chrome DevTools'); 41}

Heap Profiling#

1import inspector from 'node:inspector'; 2import fs from 'node:fs'; 3 4async function profileHeapAllocation(durationMs = 5000) { 5 const session = new inspector.Session(); 6 session.connect(); 7 8 return new Promise((resolve, reject) => { 9 session.post('HeapProfiler.enable', (err) => { 10 if (err) return reject(err); 11 12 // Start sampling 13 session.post( 14 'HeapProfiler.startSampling', 15 { samplingInterval: 32768 }, 16 (err) => { 17 if (err) return reject(err); 18 19 console.log('Heap profiling started...'); 20 21 setTimeout(() => { 22 session.post('HeapProfiler.stopSampling', (err, { profile }) => { 23 if (err) return reject(err); 24 25 fs.writeFileSync( 26 'heap-profile.heapprofile', 27 JSON.stringify(profile) 28 ); 29 30 console.log('Heap profile saved'); 31 32 session.disconnect(); 33 resolve(profile); 34 }); 35 }, durationMs); 36 } 37 ); 38 }); 39 }); 40}

Code Coverage#

1import inspector from 'node:inspector'; 2 3async function collectCoverage(fn) { 4 const session = new inspector.Session(); 5 session.connect(); 6 7 return new Promise((resolve, reject) => { 8 // Enable profiler and debugger 9 session.post('Profiler.enable', (err) => { 10 if (err) return reject(err); 11 12 // Start precise coverage 13 session.post( 14 'Profiler.startPreciseCoverage', 15 { callCount: true, detailed: true }, 16 async (err) => { 17 if (err) return reject(err); 18 19 // Run the function 20 await fn(); 21 22 // Get coverage 23 session.post('Profiler.takePreciseCoverage', (err, { result }) => { 24 if (err) return reject(err); 25 26 // Stop coverage 27 session.post('Profiler.stopPreciseCoverage', () => { 28 session.disconnect(); 29 resolve(result); 30 }); 31 }); 32 } 33 ); 34 }); 35 }); 36} 37 38// Usage 39const coverage = await collectCoverage(async () => { 40 // Code to measure coverage for 41 await runTests(); 42}); 43 44console.log('Coverage:', coverage);

Runtime Evaluation#

1import inspector from 'node:inspector'; 2 3function createEvaluator() { 4 const session = new inspector.Session(); 5 session.connect(); 6 7 session.post('Runtime.enable'); 8 9 return { 10 evaluate(expression) { 11 return new Promise((resolve, reject) => { 12 session.post( 13 'Runtime.evaluate', 14 { 15 expression, 16 returnByValue: true, 17 }, 18 (err, result) => { 19 if (err) return reject(err); 20 resolve(result.result.value); 21 } 22 ); 23 }); 24 }, 25 26 close() { 27 session.disconnect(); 28 }, 29 }; 30} 31 32// Usage 33const evaluator = createEvaluator(); 34 35const result = await evaluator.evaluate('1 + 2 + 3'); 36console.log('Result:', result); // 6 37 38const globals = await evaluator.evaluate('Object.keys(globalThis)'); 39console.log('Globals:', globals); 40 41evaluator.close();

Breakpoint Management#

1import inspector from 'node:inspector'; 2 3function setupDebugger(scriptPath) { 4 const session = new inspector.Session(); 5 session.connect(); 6 7 session.post('Debugger.enable'); 8 9 // Set breakpoint 10 session.post('Debugger.setBreakpointByUrl', { 11 lineNumber: 10, 12 url: scriptPath, 13 }); 14 15 // Handle breakpoint hit 16 session.on('Debugger.paused', (message) => { 17 console.log('Paused at:', message.params.callFrames[0].location); 18 19 // Get local variables 20 const scopeChain = message.params.callFrames[0].scopeChain; 21 for (const scope of scopeChain) { 22 session.post( 23 'Runtime.getProperties', 24 { objectId: scope.object.objectId }, 25 (err, result) => { 26 if (!err) { 27 console.log('Variables:', result.result); 28 } 29 } 30 ); 31 } 32 33 // Resume execution 34 session.post('Debugger.resume'); 35 }); 36 37 return session; 38}

Async Stack Traces#

1import inspector from 'node:inspector'; 2 3function enableAsyncStackTraces() { 4 const session = new inspector.Session(); 5 session.connect(); 6 7 session.post('Debugger.enable'); 8 session.post('Debugger.setAsyncCallStackDepth', { maxDepth: 32 }); 9 10 return session; 11} 12 13// Now async stack traces will show full call chain

Memory Monitoring#

1import inspector from 'node:inspector'; 2import v8 from 'node:v8'; 3 4function monitorMemory(intervalMs = 1000) { 5 const session = new inspector.Session(); 6 session.connect(); 7 8 session.post('Runtime.enable'); 9 10 const stats = []; 11 12 const interval = setInterval(() => { 13 const heapStats = v8.getHeapStatistics(); 14 15 session.post('Runtime.getHeapUsage', (err, result) => { 16 if (!err) { 17 stats.push({ 18 timestamp: Date.now(), 19 heapUsed: heapStats.used_heap_size, 20 heapTotal: heapStats.total_heap_size, 21 external: heapStats.external_memory, 22 ...result, 23 }); 24 25 console.log('Memory:', { 26 used: `${(heapStats.used_heap_size / 1024 / 1024).toFixed(2)} MB`, 27 total: `${(heapStats.total_heap_size / 1024 / 1024).toFixed(2)} MB`, 28 }); 29 } 30 }); 31 }, intervalMs); 32 33 return { 34 stop() { 35 clearInterval(interval); 36 session.disconnect(); 37 return stats; 38 }, 39 }; 40} 41 42// Usage 43const monitor = monitorMemory(1000); 44 45// Later... 46const memoryStats = monitor.stop();

Profiling Wrapper#

1import inspector from 'node:inspector'; 2import fs from 'node:fs'; 3 4class Profiler { 5 constructor() { 6 this.session = new inspector.Session(); 7 this.session.connect(); 8 } 9 10 async startCPUProfile() { 11 return new Promise((resolve, reject) => { 12 this.session.post('Profiler.enable', (err) => { 13 if (err) return reject(err); 14 this.session.post('Profiler.start', (err) => { 15 if (err) return reject(err); 16 resolve(); 17 }); 18 }); 19 }); 20 } 21 22 async stopCPUProfile(filename = 'profile.cpuprofile') { 23 return new Promise((resolve, reject) => { 24 this.session.post('Profiler.stop', (err, { profile }) => { 25 if (err) return reject(err); 26 fs.writeFileSync(filename, JSON.stringify(profile)); 27 resolve(profile); 28 }); 29 }); 30 } 31 32 async profileFunction(fn, filename) { 33 await this.startCPUProfile(); 34 const startTime = Date.now(); 35 36 try { 37 await fn(); 38 } finally { 39 const profile = await this.stopCPUProfile(filename); 40 const duration = Date.now() - startTime; 41 console.log(`Profiled in ${duration}ms, saved to ${filename}`); 42 return profile; 43 } 44 } 45 46 close() { 47 this.session.disconnect(); 48 } 49} 50 51// Usage 52const profiler = new Profiler(); 53await profiler.profileFunction(async () => { 54 // Code to profile 55 await heavyComputation(); 56}, 'computation.cpuprofile'); 57profiler.close();

Best Practices#

Profiling: ✓ Profile in production-like environment ✓ Use sampling for long runs ✓ Take multiple snapshots ✓ Compare before/after Debugging: ✓ Use conditional breakpoints ✓ Enable async stack traces ✓ Inspect scope chain ✓ Monitor memory usage Performance: ✓ Disable when not needed ✓ Use appropriate sample intervals ✓ Clean up sessions ✓ Avoid in production Avoid: ✗ Leaving inspector open in production ✗ Taking snapshots too frequently ✗ Ignoring session cleanup ✗ Profiling with debugger attached

Conclusion#

The Node.js inspector module provides powerful tools for debugging and profiling. Use it for CPU profiling to find performance bottlenecks, heap snapshots to diagnose memory leaks, and code coverage for testing. Create wrapper utilities for common profiling tasks and remember to properly clean up sessions. For production debugging, consider using the inspector URL to connect Chrome DevTools remotely.

Share this article

Help spread the word about Bootspring