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 DevToolsHeap 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 tabMemory 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.