Skip to content
This repository was archived by the owner on Feb 1, 2022. It is now read-only.

Commit 3e1a66a

Browse files
author
Jan Krems
committed
feat: Support CPU & heap profiles
1 parent fe1af64 commit 3e1a66a

File tree

2 files changed

+98
-2
lines changed

2 files changed

+98
-2
lines changed

lib/_inspect.js

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const Buffer = require('buffer').Buffer;
2424
const { spawn } = require('child_process');
2525
const crypto = require('crypto');
2626
const { EventEmitter } = require('events');
27+
const FS = require('fs');
2728
const http = require('http');
2829
const Path = require('path');
2930
const Repl = require('repl');
@@ -585,7 +586,7 @@ function createRepl(inspector) {
585586
});
586587
}
587588

588-
const { Debugger, Runtime } = inspector;
589+
const { Debugger, HeapProfiler, Profiler, Runtime } = inspector;
589590

590591
let repl; // eslint-disable-line prefer-const
591592

@@ -676,6 +677,31 @@ function createRepl(inspector) {
676677
}
677678
}
678679

680+
const profiles = [];
681+
class Profile {
682+
constructor(data) {
683+
this.data = data;
684+
}
685+
686+
static createAndRegister({ profile }) {
687+
const p = new Profile(profile);
688+
profiles.push(p);
689+
return p;
690+
}
691+
692+
[util.inspect.custom](depth, { stylize }) {
693+
const { startTime, endTime } = this.data;
694+
return stylize(`[Profile ${endTime - startTime}μs]`, 'special');
695+
}
696+
697+
save(filename = 'node.cpuprofile') {
698+
const absoluteFile = Path.resolve(filename);
699+
const json = JSON.stringify(this.data);
700+
FS.writeFileSync(absoluteFile, json);
701+
print('Saved profile to ' + absoluteFile);
702+
}
703+
}
704+
679705
class SourceSnippet {
680706
constructor(location, delta, scriptSource) {
681707
Object.assign(this, location);
@@ -1113,6 +1139,14 @@ function createRepl(inspector) {
11131139
}
11141140
});
11151141

1142+
Profiler.on('consoleProfileFinished', ({ profile }) => {
1143+
Profile.createAndRegister({ profile });
1144+
print([
1145+
'Captured new CPU profile.',
1146+
`Access it with profiles[${profiles.length - 1}]`
1147+
].join('\n'));
1148+
});
1149+
11161150
function initializeContext(context) {
11171151
inspector.domainNames.forEach((domain) => {
11181152
Object.defineProperty(context, domain, {
@@ -1176,6 +1210,62 @@ function createRepl(inspector) {
11761210
return evalInCurrentContext(expr);
11771211
},
11781212

1213+
get profile() {
1214+
return Profiler.start();
1215+
},
1216+
1217+
get profileEnd() {
1218+
return Profiler.stop()
1219+
.then(Profile.createAndRegister);
1220+
},
1221+
1222+
get profiles() {
1223+
return profiles;
1224+
},
1225+
1226+
takeHeapSnapshot(filename = 'node.heapsnapshot') {
1227+
return new Promise((resolve, reject) => {
1228+
const absoluteFile = Path.resolve(filename);
1229+
const writer = FS.createWriteStream(absoluteFile);
1230+
let totalSize;
1231+
let sizeWritten = 0;
1232+
function onProgress({ done, total, finished }) {
1233+
totalSize = total;
1234+
if (finished) {
1235+
print('Heap snaphost prepared.');
1236+
} else {
1237+
print(`Heap snapshot: ${done}/${total}`, true);
1238+
}
1239+
}
1240+
function onChunk({ chunk }) {
1241+
sizeWritten += chunk.length;
1242+
writer.write(chunk);
1243+
print(`Writing snapshot: ${sizeWritten}/${totalSize}`, true);
1244+
if (sizeWritten >= totalSize) {
1245+
writer.end();
1246+
teardown();
1247+
print(`Wrote snapshot: ${absoluteFile}`);
1248+
resolve();
1249+
}
1250+
}
1251+
function teardown() {
1252+
HeapProfiler.removeListener(
1253+
'reportHeapSnapshotProgress', onProgress);
1254+
HeapProfiler.removeListener('addHeapSnapshotChunk', onChunk);
1255+
}
1256+
1257+
HeapProfiler.on('reportHeapSnapshotProgress', onProgress);
1258+
HeapProfiler.on('addHeapSnapshotChunk', onChunk);
1259+
1260+
print('Heap snapshot: 0/0', true);
1261+
HeapProfiler.takeHeapSnapshot({ reportProgress: true })
1262+
.then(null, (error) => {
1263+
teardown();
1264+
reject(error);
1265+
});
1266+
});
1267+
},
1268+
11791269
get watchers() {
11801270
return watchers();
11811271
},
@@ -1374,7 +1464,7 @@ class NodeInspector {
13741464

13751465
this.client = new ProtocolClient(options.port, options.host);
13761466

1377-
this.domainNames = ['Debugger', 'Runtime'];
1467+
this.domainNames = ['Debugger', 'HeapProfiler', 'Profiler', 'Runtime'];
13781468
this.domainNames.forEach((domain) => {
13791469
this[domain] = createAgentProxy(domain, this.client);
13801470
});

test/cli/profile.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ const { test } = require('tap');
33

44
const startCLI = require('./start-cli');
55

6+
function delay(ms) {
7+
return new Promise((resolve) => setTimeout(resolve, ms));
8+
}
9+
610
test('profiles', (t) => {
711
const cli = startCLI(['examples/empty.js']);
812

@@ -18,8 +22,10 @@ test('profiles', (t) => {
1822
t.match(cli.output, 'undefined');
1923
})
2024
.then(() => cli.command('exec console.profileEnd()'))
25+
.then(() => delay(250))
2126
.then(() => {
2227
t.match(cli.output, 'undefined');
28+
t.match(cli.output, 'Captured new CPU profile.');
2329
})
2430
.then(() => cli.quit())
2531
.then(null, onFatal);

0 commit comments

Comments
 (0)