Back to Blog
Node.jsInspectorDebuggingProfiling

Node.js Inspector Module Guide

Master the Node.js inspector module for programmatic debugging, profiling, and code coverage.

B
Bootspring Team
Engineering
October 30, 2019
6 min read

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

Basic Usage#

1const inspector = require('inspector'); 2 3// Open inspector 4inspector.open(9229, 'localhost', true); 5 6// Get inspector URL 7const url = inspector.url(); 8console.log(`Inspector URL: ${url}`); 9 10// Close inspector 11inspector.close(); 12 13// Check if inspector is active 14if (inspector.url()) { 15 console.log('Inspector is active'); 16} 17 18// Wait for debugger to connect 19inspector.open(9229, 'localhost', true); 20inspector.waitForDebugger(); 21console.log('Debugger connected!');

Session API#

1const inspector = require('inspector'); 2 3// Create session 4const session = new inspector.Session(); 5 6// Connect to inspector 7session.connect(); 8 9// Enable domains 10session.post('Debugger.enable', (err, result) => { 11 if (err) console.error(err); 12 console.log('Debugger enabled'); 13}); 14 15session.post('Runtime.enable', (err, result) => { 16 if (err) console.error(err); 17 console.log('Runtime enabled'); 18}); 19 20// Listen for events 21session.on('Debugger.paused', (message) => { 22 console.log('Paused at:', message.params.callFrames[0].location); 23}); 24 25// Disconnect 26session.disconnect();

CPU Profiling#

1const inspector = require('inspector'); 2const fs = require('fs'); 3 4async function profile(duration = 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 'profile.cpuprofile', 27 JSON.stringify(profile) 28 ); 29 30 console.log('Profile saved to profile.cpuprofile'); 31 32 session.disconnect(); 33 resolve(profile); 34 }); 35 }, duration); 36 }); 37 }); 38 }); 39} 40 41// Usage 42await profile(5000); 43// Load profile.cpuprofile in Chrome DevTools

Heap Snapshot#

1const inspector = require('inspector'); 2const fs = require('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.heapsnapshot', snapshot); 20 21 console.log('Heap snapshot saved to heap.heapsnapshot'); 22 23 session.disconnect(); 24 resolve(snapshot); 25 }); 26 }); 27} 28 29// Usage 30await takeHeapSnapshot(); 31// Load heap.heapsnapshot in Chrome DevTools Memory tab

Memory Profiling#

1const inspector = require('inspector'); 2 3async function startHeapProfiling() { 4 const session = new inspector.Session(); 5 session.connect(); 6 7 // Enable heap profiler 8 await new Promise((resolve, reject) => { 9 session.post('HeapProfiler.enable', (err) => { 10 err ? reject(err) : resolve(); 11 }); 12 }); 13 14 // Start sampling 15 await new Promise((resolve, reject) => { 16 session.post( 17 'HeapProfiler.startSampling', 18 { samplingInterval: 512 }, 19 (err) => { 20 err ? reject(err) : resolve(); 21 } 22 ); 23 }); 24 25 console.log('Heap profiling started'); 26 27 return { 28 stop: async () => { 29 return new Promise((resolve, reject) => { 30 session.post('HeapProfiler.stopSampling', (err, result) => { 31 if (err) return reject(err); 32 33 const fs = require('fs'); 34 fs.writeFileSync( 35 'heap-profile.heapprofile', 36 JSON.stringify(result.profile) 37 ); 38 39 session.disconnect(); 40 resolve(result.profile); 41 }); 42 }); 43 }, 44 }; 45} 46 47// Usage 48const profiler = await startHeapProfiling(); 49// ... run your code ... 50await profiler.stop();

Code Coverage#

1const inspector = require('inspector'); 2const fs = require('fs'); 3 4async function collectCoverage(fn) { 5 const session = new inspector.Session(); 6 session.connect(); 7 8 // Enable profiler and coverage 9 await post(session, 'Profiler.enable'); 10 await post(session, 'Profiler.startPreciseCoverage', { 11 callCount: true, 12 detailed: true, 13 }); 14 15 // Run the function 16 await fn(); 17 18 // Get coverage data 19 const { result } = await post(session, 'Profiler.takePreciseCoverage'); 20 21 // Disable and cleanup 22 await post(session, 'Profiler.stopPreciseCoverage'); 23 await post(session, 'Profiler.disable'); 24 session.disconnect(); 25 26 // Filter to your source files 27 const coverage = result.filter((script) => 28 script.url.startsWith('file://') 29 ); 30 31 return coverage; 32} 33 34function post(session, method, params = {}) { 35 return new Promise((resolve, reject) => { 36 session.post(method, params, (err, result) => { 37 err ? reject(err) : resolve(result); 38 }); 39 }); 40} 41 42// Usage 43const coverage = await collectCoverage(async () => { 44 require('./my-module.js'); 45}); 46 47console.log('Coverage:', JSON.stringify(coverage, null, 2));

Breakpoints#

1const inspector = require('inspector'); 2 3async function setBreakpoint(scriptUrl, lineNumber) { 4 const session = new inspector.Session(); 5 session.connect(); 6 7 await post(session, 'Debugger.enable'); 8 9 // Set breakpoint by URL 10 const { breakpointId } = await post(session, 'Debugger.setBreakpointByUrl', { 11 url: scriptUrl, 12 lineNumber: lineNumber, 13 columnNumber: 0, 14 }); 15 16 console.log(`Breakpoint set: ${breakpointId}`); 17 18 // Handle pause 19 session.on('Debugger.paused', async (message) => { 20 const frame = message.params.callFrames[0]; 21 console.log(`Paused at ${frame.url}:${frame.location.lineNumber}`); 22 23 // Resume execution 24 await post(session, 'Debugger.resume'); 25 }); 26 27 return { session, breakpointId }; 28} 29 30// Remove breakpoint 31async function removeBreakpoint(session, breakpointId) { 32 await post(session, 'Debugger.removeBreakpoint', { breakpointId }); 33}

Evaluate Expressions#

1const inspector = require('inspector'); 2 3async function evaluate(expression) { 4 const session = new inspector.Session(); 5 session.connect(); 6 7 await post(session, 'Runtime.enable'); 8 9 const { result, exceptionDetails } = await post(session, 'Runtime.evaluate', { 10 expression, 11 returnByValue: true, 12 }); 13 14 session.disconnect(); 15 16 if (exceptionDetails) { 17 throw new Error(exceptionDetails.exception.description); 18 } 19 20 return result.value; 21} 22 23// Usage 24const result = await evaluate('1 + 2 * 3'); 25console.log(result); // 7 26 27const arr = await evaluate('[1, 2, 3].map(x => x * 2)'); 28console.log(arr); // [2, 4, 6]

Call Stack Inspection#

1const inspector = require('inspector'); 2 3function captureStackTrace() { 4 const session = new inspector.Session(); 5 session.connect(); 6 7 return new Promise((resolve) => { 8 session.post('Runtime.enable', () => { 9 session.post( 10 'Runtime.evaluate', 11 { 12 expression: 'new Error().stack', 13 returnByValue: true, 14 }, 15 (err, { result }) => { 16 session.disconnect(); 17 resolve(result.value); 18 } 19 ); 20 }); 21 }); 22} 23 24// Get current execution context 25async function getContexts() { 26 const session = new inspector.Session(); 27 session.connect(); 28 29 await post(session, 'Runtime.enable'); 30 31 const contexts = []; 32 session.on('Runtime.executionContextCreated', ({ params }) => { 33 contexts.push(params.context); 34 }); 35 36 // Wait a bit for contexts 37 await new Promise((resolve) => setTimeout(resolve, 100)); 38 39 session.disconnect(); 40 return contexts; 41}

Wrapper Utility#

1const inspector = require('inspector'); 2const fs = require('fs'); 3 4class Inspector { 5 constructor() { 6 this.session = new inspector.Session(); 7 this.session.connect(); 8 } 9 10 async post(method, params = {}) { 11 return new Promise((resolve, reject) => { 12 this.session.post(method, params, (err, result) => { 13 err ? reject(err) : resolve(result); 14 }); 15 }); 16 } 17 18 async cpuProfile(fn) { 19 await this.post('Profiler.enable'); 20 await this.post('Profiler.start'); 21 22 await fn(); 23 24 const { profile } = await this.post('Profiler.stop'); 25 await this.post('Profiler.disable'); 26 27 return profile; 28 } 29 30 async heapSnapshot() { 31 const chunks = []; 32 33 this.session.on('HeapProfiler.addHeapSnapshotChunk', ({ params }) => { 34 chunks.push(params.chunk); 35 }); 36 37 await this.post('HeapProfiler.takeHeapSnapshot'); 38 39 return chunks.join(''); 40 } 41 42 async coverage(fn) { 43 await this.post('Profiler.enable'); 44 await this.post('Profiler.startPreciseCoverage', { 45 callCount: true, 46 detailed: true, 47 }); 48 49 await fn(); 50 51 const { result } = await this.post('Profiler.takePreciseCoverage'); 52 await this.post('Profiler.stopPreciseCoverage'); 53 await this.post('Profiler.disable'); 54 55 return result; 56 } 57 58 close() { 59 this.session.disconnect(); 60 } 61} 62 63// Usage 64const insp = new Inspector(); 65 66const profile = await insp.cpuProfile(async () => { 67 // Code to profile 68}); 69 70const snapshot = await insp.heapSnapshot(); 71 72insp.close();

Production Debugging#

1const inspector = require('inspector'); 2 3// Enable inspector on signal 4process.on('SIGUSR1', () => { 5 if (inspector.url()) { 6 console.log('Inspector already active:', inspector.url()); 7 return; 8 } 9 10 inspector.open(9229, '0.0.0.0', false); 11 console.log('Inspector activated:', inspector.url()); 12 13 // Auto-close after 5 minutes 14 setTimeout(() => { 15 inspector.close(); 16 console.log('Inspector closed'); 17 }, 5 * 60 * 1000); 18}); 19 20// Usage: kill -USR1 <pid> 21console.log(`Send SIGUSR1 to PID ${process.pid} to enable inspector`);

Best Practices#

Usage: ✓ Use for profiling ✓ Use for heap snapshots ✓ Use for code coverage ✓ Enable dynamically in production Performance: ✓ Profile in production-like env ✓ Take multiple samples ✓ Clean up sessions ✓ Limit inspector exposure Security: ✓ Bind to localhost only ✓ Use authentication in production ✓ Auto-close after timeout ✓ Limit exposed functionality Avoid: ✗ Leaving inspector open ✗ Exposing to network ✗ Heavy profiling in production ✗ Ignoring session cleanup

Conclusion#

The inspector module provides powerful programmatic access to V8's debugging and profiling capabilities. Use it for CPU profiling, heap snapshots, code coverage, and dynamic debugging. Always handle sessions properly and be cautious about security when enabling inspector access in production environments.

Share this article

Help spread the word about Bootspring