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 chainMemory 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.