node-loop-detective 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -74,6 +74,12 @@ loop-detective -p 12345 -d 30 -t 100
74
74
  # Detect slow I/O with a 1-second threshold
75
75
  loop-detective -p 12345 --io-threshold 1000
76
76
 
77
+ # Connect to a remote inspector (Docker, K8s, remote server)
78
+ loop-detective --host 192.168.1.100 --port 9229
79
+
80
+ # Save raw CPU profile for Chrome DevTools / speedscope
81
+ loop-detective -p 12345 --save-profile ./profile.cpuprofile
82
+
77
83
  # Continuous monitoring mode
78
84
  loop-detective -p 12345 --watch
79
85
 
@@ -86,11 +92,13 @@ loop-detective -p 12345 --json
86
92
  | Flag | Description | Default |
87
93
  |------|-------------|---------|
88
94
  | `-p, --pid <pid>` | Target Node.js process ID | — |
95
+ | `-H, --host <host>` | Inspector host (remote connections) | 127.0.0.1 |
89
96
  | `-P, --port <port>` | Inspector port (skip SIGUSR1) | — |
90
97
  | `-d, --duration <sec>` | Profiling duration in seconds | 10 |
91
98
  | `-t, --threshold <ms>` | Event loop lag threshold | 50 |
92
99
  | `-i, --interval <ms>` | Lag sampling interval | 100 |
93
100
  | `--io-threshold <ms>` | Slow I/O threshold | 500 |
101
+ | `--save-profile <path>` | Save raw CPU profile to file | — |
94
102
  | `-j, --json` | Output as JSON | false |
95
103
  | `-w, --watch` | Continuous monitoring | false |
96
104
 
@@ -124,6 +132,7 @@ const { Detective } = require('node-loop-detective');
124
132
 
125
133
  const detective = new Detective({
126
134
  pid: 12345,
135
+ inspectorHost: '127.0.0.1', // or remote host
127
136
  duration: 10000,
128
137
  threshold: 50,
129
138
  interval: 100,
package/bin/cli.js CHANGED
@@ -4,17 +4,21 @@
4
4
 
5
5
  const { Detective } = require('../src/detective');
6
6
  const { Reporter } = require('../src/reporter');
7
+ const fs = require('node:fs');
8
+ const path = require('node:path');
7
9
 
8
10
  // Simple arg parser compatible with Node.js 16+
9
11
  function parseCliArgs(argv) {
10
12
  const args = argv.slice(2);
11
13
  const values = {
12
14
  pid: null,
15
+ host: null,
13
16
  port: null,
14
17
  duration: '10',
15
18
  threshold: '50',
16
19
  interval: '100',
17
20
  'io-threshold': '500',
21
+ 'save-profile': null,
18
22
  json: false,
19
23
  watch: false,
20
24
  help: false,
@@ -24,11 +28,13 @@ function parseCliArgs(argv) {
24
28
 
25
29
  const flagMap = {
26
30
  '-p': 'pid', '--pid': 'pid',
31
+ '-H': 'host', '--host': 'host',
27
32
  '-P': 'port', '--port': 'port',
28
33
  '-d': 'duration', '--duration': 'duration',
29
34
  '-t': 'threshold', '--threshold': 'threshold',
30
35
  '-i': 'interval', '--interval': 'interval',
31
36
  '--io-threshold': 'io-threshold',
37
+ '--save-profile': 'save-profile',
32
38
  };
33
39
  const boolMap = {
34
40
  '-j': 'json', '--json': 'json',
@@ -85,14 +91,17 @@ function printUsage() {
85
91
  loop-detective <pid>
86
92
  loop-detective --pid <pid>
87
93
  loop-detective --port <inspector-port>
94
+ loop-detective --host <remote-host> --port <inspector-port>
88
95
 
89
96
  OPTIONS
90
97
  -p, --pid <pid> Target Node.js process ID
98
+ -H, --host <host> Inspector host (default: 127.0.0.1)
91
99
  -P, --port <port> Connect to an already-open inspector port
92
100
  -d, --duration <sec> Profiling duration in seconds (default: 10)
93
101
  -t, --threshold <ms> Event loop lag threshold in ms (default: 50)
94
102
  -i, --interval <ms> Sampling interval in ms (default: 100)
95
103
  --io-threshold <ms> Slow I/O threshold in ms (default: 500)
104
+ --save-profile <path> Save raw CPU profile to .cpuprofile file
96
105
  -j, --json Output results as JSON
97
106
  -w, --watch Continuous monitoring mode
98
107
  -h, --help Show this help
@@ -102,6 +111,7 @@ function printUsage() {
102
111
  loop-detective 12345
103
112
  loop-detective --pid 12345 --duration 30 --threshold 100
104
113
  loop-detective --port 9229 --watch
114
+ loop-detective --host 192.168.1.100 --port 9229
105
115
  loop-detective -p 12345 -d 5 -j
106
116
 
107
117
  HOW IT WORKS
@@ -116,11 +126,13 @@ function printUsage() {
116
126
  async function main() {
117
127
  const config = {
118
128
  pid: pid ? parseInt(pid, 10) : null,
129
+ inspectorHost: values.host || '127.0.0.1',
119
130
  inspectorPort,
120
131
  duration: parseInt(values.duration, 10) * 1000,
121
132
  threshold: parseInt(values.threshold, 10),
122
133
  interval: parseInt(values.interval, 10),
123
134
  ioThreshold: parseInt(values['io-threshold'], 10),
135
+ saveProfile: values['save-profile'],
124
136
  watch: values.watch,
125
137
  json: values.json,
126
138
  };
@@ -128,10 +140,32 @@ async function main() {
128
140
  const reporter = new Reporter(config);
129
141
  const detective = new Detective(config);
130
142
 
143
+ // Security warning for remote connections
144
+ if (config.inspectorHost !== '127.0.0.1' && config.inspectorHost !== 'localhost') {
145
+ reporter.onInfo(`⚠ Warning: Connecting to remote host ${config.inspectorHost}. The CDP protocol has no authentication — ensure the network is trusted.`);
146
+ }
147
+
131
148
  detective.on('connected', () => reporter.onConnected());
132
149
  detective.on('lag', (data) => reporter.onLag(data));
133
150
  detective.on('slowIO', (data) => reporter.onSlowIO(data));
134
- detective.on('profile', (data) => reporter.onProfile(data));
151
+ detective.on('profile', (analysis, rawProfile) => {
152
+ reporter.onProfile(analysis);
153
+
154
+ // Save raw CPU profile if requested
155
+ if (config.saveProfile && rawProfile) {
156
+ try {
157
+ const filePath = path.resolve(config.saveProfile);
158
+ fs.writeFileSync(filePath, JSON.stringify(rawProfile));
159
+ if (!config.json) {
160
+ console.log(`\n \x1b[32m✔\x1b[0m CPU profile saved to ${filePath}`);
161
+ console.log(` Open in Chrome DevTools: Performance tab → Load profile`);
162
+ console.log(` Or visit https://www.speedscope.app\n`);
163
+ }
164
+ } catch (err) {
165
+ console.error(`\n \x1b[31m✖ Failed to save profile: ${err.message}\x1b[0m\n`);
166
+ }
167
+ }
168
+ });
135
169
  detective.on('error', (err) => reporter.onError(err));
136
170
  detective.on('disconnected', () => reporter.onDisconnected());
137
171
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-loop-detective",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Detect event loop blocking & lag in running Node.js apps without code changes or restarts",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/detective.js CHANGED
@@ -406,7 +406,7 @@ class Detective extends EventEmitter {
406
406
  this._activateInspector();
407
407
  const port = await this._findInspectorPort();
408
408
 
409
- this.inspector = new Inspector({ port });
409
+ this.inspector = new Inspector({ host: this.config.inspectorHost, port });
410
410
  await this.inspector.connect();
411
411
  this.emit('connected');
412
412
 
@@ -424,7 +424,7 @@ class Detective extends EventEmitter {
424
424
 
425
425
  const profile = await this._captureProfile(this.config.duration);
426
426
  const analysis = this.analyzer.analyzeProfile(profile);
427
- this.emit('profile', analysis);
427
+ this.emit('profile', analysis, profile);
428
428
  } finally {
429
429
  await this.stop();
430
430
  }
@@ -444,7 +444,7 @@ class Detective extends EventEmitter {
444
444
  try {
445
445
  const profile = await this._captureProfile(this.config.duration);
446
446
  const analysis = this.analyzer.analyzeProfile(profile);
447
- this.emit('profile', analysis);
447
+ this.emit('profile', analysis, profile);
448
448
  } catch (err) {
449
449
  this.emit('error', err);
450
450
  }