Skip to content

Commit 4b74dae

Browse files
joyeecheungaddaleax
authored andcommitted
inspector: implement --heap-prof
In addition implements --heap-prof-name, --heap-prof-dir and --heap-prof-interval. These flags are similar to --cpu-prof flags but they are meant for the V8 sampling heap profiler instead of the CPU profiler. PR-URL: #27596 Fixes: #27421 Reviewed-By: Jan Krems <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent e2c0c0c commit 4b74dae

14 files changed

+682
-0
lines changed

doc/api/cli.md

+51
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,57 @@ new X();
245245
added: v12.0.0
246246
-->
247247

248+
### `--heap-prof`
249+
<!-- YAML
250+
added: REPLACEME
251+
-->
252+
253+
> Stability: 1 - Experimental
254+
255+
Starts the V8 heap profiler on start up, and writes the heap profile to disk
256+
before exit.
257+
258+
If `--heap-prof-dir` is not specified, the generated profile will be placed
259+
in the current working directory.
260+
261+
If `--heap-prof-name` is not specified, the generated profile will be
262+
named `Heap.${yyyymmdd}.${hhmmss}.${pid}.${tid}.${seq}.heapprofile`.
263+
264+
```console
265+
$ node --heap-prof index.js
266+
$ ls *.heapprofile
267+
Heap.20190409.202950.15293.0.001.heapprofile
268+
```
269+
270+
### `--heap-prof-dir`
271+
<!-- YAML
272+
added: REPLACEME
273+
-->
274+
275+
> Stability: 1 - Experimental
276+
277+
Specify the directory where the heap profiles generated by `--heap-prof` will
278+
be placed.
279+
280+
### `--heap-prof-interval`
281+
<!-- YAML
282+
added: REPLACEME
283+
-->
284+
285+
> Stability: 1 - Experimental
286+
287+
Specify the average sampling interval in bytes for the heap profiles generated
288+
by `--heap-prof`. The default is 512 * 1024 bytes.
289+
290+
### `--heap-prof-name`
291+
<!-- YAML
292+
added: REPLACEME
293+
-->
294+
295+
> Stability: 1 - Experimental
296+
297+
Specify the file name of the heap profile generated by `--heap-prof`.
298+
248299
Generates a heap snapshot each time the process receives the specified signal.
249300
`signal` must be a valid signal name. Disabled by default.
250301

doc/node.1

+22
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,28 @@ Enable experimental frozen intrinsics support.
139139
.It Fl -heapsnapshot-signal Ns = Ns Ar signal
140140
Generate heap snapshot on specified signal.
141141
.
142+
.It Fl -heap-prof
143+
Start the V8 heap profiler on start up, and write the heap profile to disk
144+
before exit. If
145+
.Fl -heap-prof-dir
146+
is not specified, the profile will be written to the current working directory
147+
with a generated file name.
148+
.
149+
.It Fl -heap-prof-dir
150+
The directory where the heap profiles generated by
151+
.Fl -heap-prof
152+
will be placed.
153+
.
154+
.It Fl -heap-prof-interval
155+
The average sampling interval in bytes for the heap profiles generated by
156+
.Fl -heap-prof .
157+
The default is
158+
.Sy 512 * 1024 .
159+
.
160+
.It Fl -heap-prof-name
161+
File name of the V8 heap profile generated with
162+
.Fl -heap-prof
163+
.
142164
.It Fl -http-parser Ns = Ns Ar library
143165
Chooses an HTTP parser library. Available values are
144166
.Sy llhttp

src/env-inl.h

+35
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,41 @@ inline const std::string& Environment::cpu_prof_dir() const {
689689
return cpu_prof_dir_;
690690
}
691691

692+
inline void Environment::set_heap_profiler_connection(
693+
std::unique_ptr<profiler::V8HeapProfilerConnection> connection) {
694+
CHECK_NULL(heap_profiler_connection_);
695+
std::swap(heap_profiler_connection_, connection);
696+
}
697+
698+
inline profiler::V8HeapProfilerConnection*
699+
Environment::heap_profiler_connection() {
700+
return heap_profiler_connection_.get();
701+
}
702+
703+
inline void Environment::set_heap_prof_name(const std::string& name) {
704+
heap_prof_name_ = name;
705+
}
706+
707+
inline const std::string& Environment::heap_prof_name() const {
708+
return heap_prof_name_;
709+
}
710+
711+
inline void Environment::set_heap_prof_dir(const std::string& dir) {
712+
heap_prof_dir_ = dir;
713+
}
714+
715+
inline const std::string& Environment::heap_prof_dir() const {
716+
return heap_prof_dir_;
717+
}
718+
719+
inline void Environment::set_heap_prof_interval(uint64_t interval) {
720+
heap_prof_interval_ = interval;
721+
}
722+
723+
inline uint64_t Environment::heap_prof_interval() const {
724+
return heap_prof_interval_;
725+
}
726+
692727
#endif // HAVE_INSPECTOR
693728

694729
inline std::shared_ptr<HostPort> Environment::inspector_host_port() {

src/env.h

+19
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class AgentWriterHandle;
7373
namespace profiler {
7474
class V8CoverageConnection;
7575
class V8CpuProfilerConnection;
76+
class V8HeapProfilerConnection;
7677
} // namespace profiler
7778
#endif // HAVE_INSPECTOR
7879

@@ -1151,6 +1152,20 @@ class Environment : public MemoryRetainer {
11511152

11521153
inline void set_cpu_prof_dir(const std::string& dir);
11531154
inline const std::string& cpu_prof_dir() const;
1155+
1156+
void set_heap_profiler_connection(
1157+
std::unique_ptr<profiler::V8HeapProfilerConnection> connection);
1158+
profiler::V8HeapProfilerConnection* heap_profiler_connection();
1159+
1160+
inline void set_heap_prof_name(const std::string& name);
1161+
inline const std::string& heap_prof_name() const;
1162+
1163+
inline void set_heap_prof_dir(const std::string& dir);
1164+
inline const std::string& heap_prof_dir() const;
1165+
1166+
inline void set_heap_prof_interval(uint64_t interval);
1167+
inline uint64_t heap_prof_interval() const;
1168+
11541169
#endif // HAVE_INSPECTOR
11551170

11561171
private:
@@ -1190,6 +1205,10 @@ class Environment : public MemoryRetainer {
11901205
std::string cpu_prof_dir_;
11911206
std::string cpu_prof_name_;
11921207
uint64_t cpu_prof_interval_;
1208+
std::unique_ptr<profiler::V8HeapProfilerConnection> heap_profiler_connection_;
1209+
std::string heap_prof_dir_;
1210+
std::string heap_prof_name_;
1211+
uint64_t heap_prof_interval_;
11931212
#endif // HAVE_INSPECTOR
11941213

11951214
std::shared_ptr<EnvironmentOptions> options_;

src/inspector_profiler.cc

+58
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,44 @@ void V8CpuProfilerConnection::End() {
258258
DispatchMessage("Profiler.stop");
259259
}
260260

261+
std::string V8HeapProfilerConnection::GetDirectory() const {
262+
return env()->heap_prof_dir();
263+
}
264+
265+
std::string V8HeapProfilerConnection::GetFilename() const {
266+
return env()->heap_prof_name();
267+
}
268+
269+
MaybeLocal<Object> V8HeapProfilerConnection::GetProfile(Local<Object> result) {
270+
Local<Value> profile_v;
271+
if (!result
272+
->Get(env()->context(),
273+
FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
274+
.ToLocal(&profile_v)) {
275+
fprintf(stderr, "'profile' from heap profile result is undefined\n");
276+
return MaybeLocal<Object>();
277+
}
278+
if (!profile_v->IsObject()) {
279+
fprintf(stderr, "'profile' from heap profile result is not an Object\n");
280+
return MaybeLocal<Object>();
281+
}
282+
return profile_v.As<Object>();
283+
}
284+
285+
void V8HeapProfilerConnection::Start() {
286+
DispatchMessage("HeapProfiler.enable");
287+
std::string params = R"({ "samplingInterval": )";
288+
params += std::to_string(env()->heap_prof_interval());
289+
params += " }";
290+
DispatchMessage("HeapProfiler.startSampling", params.c_str());
291+
}
292+
293+
void V8HeapProfilerConnection::End() {
294+
CHECK_EQ(ending_, false);
295+
ending_ = true;
296+
DispatchMessage("HeapProfiler.stopSampling");
297+
}
298+
261299
// For now, we only support coverage profiling, but we may add more
262300
// in the future.
263301
void EndStartedProfilers(Environment* env) {
@@ -268,6 +306,12 @@ void EndStartedProfilers(Environment* env) {
268306
connection->End();
269307
}
270308

309+
connection = env->heap_profiler_connection();
310+
if (connection != nullptr && !connection->ending()) {
311+
Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending heap profiling\n");
312+
connection->End();
313+
}
314+
271315
connection = env->coverage_connection();
272316
if (connection != nullptr && !connection->ending()) {
273317
Debug(
@@ -313,6 +357,20 @@ void StartProfilers(Environment* env) {
313357
std::make_unique<V8CpuProfilerConnection>(env));
314358
env->cpu_profiler_connection()->Start();
315359
}
360+
if (env->options()->heap_prof) {
361+
const std::string& dir = env->options()->heap_prof_dir;
362+
env->set_heap_prof_interval(env->options()->heap_prof_interval);
363+
env->set_heap_prof_dir(dir.empty() ? GetCwd() : dir);
364+
if (env->options()->heap_prof_name.empty()) {
365+
DiagnosticFilename filename(env, "Heap", "heapprofile");
366+
env->set_heap_prof_name(*filename);
367+
} else {
368+
env->set_heap_prof_name(env->options()->heap_prof_name);
369+
}
370+
env->set_heap_profiler_connection(
371+
std::make_unique<profiler::V8HeapProfilerConnection>(env));
372+
env->heap_profiler_connection()->Start();
373+
}
316374
}
317375

318376
static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) {

src/inspector_profiler.h

+20
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,26 @@ class V8CpuProfilerConnection : public V8ProfilerConnection {
107107
bool ending_ = false;
108108
};
109109

110+
class V8HeapProfilerConnection : public V8ProfilerConnection {
111+
public:
112+
explicit V8HeapProfilerConnection(Environment* env)
113+
: V8ProfilerConnection(env) {}
114+
115+
void Start() override;
116+
void End() override;
117+
118+
const char* type() const override { return "heap"; }
119+
bool ending() const override { return ending_; }
120+
121+
std::string GetDirectory() const override;
122+
std::string GetFilename() const override;
123+
v8::MaybeLocal<v8::Object> GetProfile(v8::Local<v8::Object> result) override;
124+
125+
private:
126+
std::unique_ptr<inspector::InspectorSession> session_;
127+
bool ending_ = false;
128+
};
129+
110130
} // namespace profiler
111131
} // namespace node
112132

src/node_options.cc

+31
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,19 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
168168
}
169169
}
170170

171+
if (!heap_prof) {
172+
if (!heap_prof_name.empty()) {
173+
errors->push_back("--heap-prof-name must be used with --heap-prof");
174+
}
175+
if (!heap_prof_dir.empty()) {
176+
errors->push_back("--heap-prof-dir must be used with --heap-prof");
177+
}
178+
// We can't catch the case where the value passed is the default value,
179+
// then the option just becomes a noop which is fine.
180+
if (heap_prof_interval != kDefaultHeapProfInterval) {
181+
errors->push_back("--heap-prof-interval must be used with --heap-prof");
182+
}
183+
}
171184
debug_options_.CheckOptions(errors);
172185
#endif // HAVE_INSPECTOR
173186
}
@@ -369,6 +382,24 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
369382
"Directory where the V8 profiles generated by --cpu-prof will be "
370383
"placed. Does not affect --prof.",
371384
&EnvironmentOptions::cpu_prof_dir);
385+
AddOption(
386+
"--heap-prof",
387+
"Start the V8 heap profiler on start up, and write the heap profile "
388+
"to disk before exit. If --heap-prof-dir is not specified, write "
389+
"the profile to the current working directory.",
390+
&EnvironmentOptions::heap_prof);
391+
AddOption("--heap-prof-name",
392+
"specified file name of the V8 CPU profile generated with "
393+
"--heap-prof",
394+
&EnvironmentOptions::heap_prof_name);
395+
AddOption("--heap-prof-dir",
396+
"Directory where the V8 heap profiles generated by --heap-prof "
397+
"will be placed.",
398+
&EnvironmentOptions::heap_prof_dir);
399+
AddOption("--heap-prof-interval",
400+
"specified sampling interval in bytes for the V8 heap "
401+
"profile generated with --heap-prof. (default: 512 * 1024)",
402+
&EnvironmentOptions::heap_prof_interval);
372403
#endif // HAVE_INSPECTOR
373404
AddOption("--redirect-warnings",
374405
"write warnings to file instead of stderr",

src/node_options.h

+5
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ class EnvironmentOptions : public Options {
115115
uint64_t cpu_prof_interval = kDefaultCpuProfInterval;
116116
std::string cpu_prof_name;
117117
bool cpu_prof = false;
118+
std::string heap_prof_dir;
119+
std::string heap_prof_name;
120+
static const uint64_t kDefaultHeapProfInterval = 512 * 1024;
121+
uint64_t heap_prof_interval = kDefaultHeapProfInterval;
122+
bool heap_prof = false;
118123
#endif // HAVE_INSPECTOR
119124
std::string redirect_warnings;
120125
bool throw_deprecation = false;
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use strict';
2+
3+
const util = require('util');
4+
const total = parseInt(process.env.TEST_ALLOCATION) || 100;
5+
let count = 0;
6+
let string = '';
7+
function runAllocation() {
8+
string += util.inspect(process.env);
9+
if (count++ < total) {
10+
setTimeout(runAllocation, 1);
11+
} else {
12+
console.log(string.length);
13+
process.exit(55);
14+
}
15+
}
16+
17+
setTimeout(runAllocation, 1);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use strict';
2+
3+
const util = require('util');
4+
const total = parseInt(process.env.TEST_ALLOCATION) || 100;
5+
let count = 0;
6+
let string = '';
7+
function runAllocation() {
8+
string += util.inspect(process.env);
9+
if (count++ < total) {
10+
setTimeout(runAllocation, 1);
11+
} else {
12+
console.log(string.length);
13+
process.kill(process.pid, "SIGINT");
14+
}
15+
}
16+
17+
setTimeout(runAllocation, 1);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
3+
const { Worker } = require('worker_threads');
4+
const path = require('path');
5+
new Worker(path.join(__dirname, 'allocation.js'), {
6+
execArgv: [
7+
'--heap-prof',
8+
'--heap-prof-interval',
9+
process.HEAP_PROF_INTERVAL || '128',
10+
]
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
3+
const { Worker } = require('worker_threads');
4+
const path = require('path');
5+
new Worker(path.join(__dirname, 'allocation.js'));

0 commit comments

Comments
 (0)