Skip to content

Commit

Permalink
inspector: implement --heap-prof
Browse files Browse the repository at this point in the history
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 <jan.krems@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
  • Loading branch information
joyeecheung authored and targos committed May 28, 2019
1 parent 7a7fc4e commit d99e703
Show file tree
Hide file tree
Showing 14 changed files with 682 additions and 0 deletions.
51 changes: 51 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,57 @@ new X();
added: v12.0.0
-->

### `--heap-prof`
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental
Starts the V8 heap profiler on start up, and writes the heap profile to disk
before exit.

If `--heap-prof-dir` is not specified, the generated profile will be placed
in the current working directory.

If `--heap-prof-name` is not specified, the generated profile will be
named `Heap.${yyyymmdd}.${hhmmss}.${pid}.${tid}.${seq}.heapprofile`.

```console
$ node --heap-prof index.js
$ ls *.heapprofile
Heap.20190409.202950.15293.0.001.heapprofile
```

### `--heap-prof-dir`
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental
Specify the directory where the heap profiles generated by `--heap-prof` will
be placed.

### `--heap-prof-interval`
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental
Specify the average sampling interval in bytes for the heap profiles generated
by `--heap-prof`. The default is 512 * 1024 bytes.

### `--heap-prof-name`
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental
Specify the file name of the heap profile generated by `--heap-prof`.

Generates a heap snapshot each time the process receives the specified signal.
`signal` must be a valid signal name. Disabled by default.

Expand Down
22 changes: 22 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,28 @@ Enable experimental frozen intrinsics support.
.It Fl -heapsnapshot-signal Ns = Ns Ar signal
Generate heap snapshot on specified signal.
.
.It Fl -heap-prof
Start the V8 heap profiler on start up, and write the heap profile to disk
before exit. If
.Fl -heap-prof-dir
is not specified, the profile will be written to the current working directory
with a generated file name.
.
.It Fl -heap-prof-dir
The directory where the heap profiles generated by
.Fl -heap-prof
will be placed.
.
.It Fl -heap-prof-interval
The average sampling interval in bytes for the heap profiles generated by
.Fl -heap-prof .
The default is
.Sy 512 * 1024 .
.
.It Fl -heap-prof-name
File name of the V8 heap profile generated with
.Fl -heap-prof
.
.It Fl -http-parser Ns = Ns Ar library
Chooses an HTTP parser library. Available values are
.Sy llhttp
Expand Down
35 changes: 35 additions & 0 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,41 @@ inline const std::string& Environment::cpu_prof_dir() const {
return cpu_prof_dir_;
}

inline void Environment::set_heap_profiler_connection(
std::unique_ptr<profiler::V8HeapProfilerConnection> connection) {
CHECK_NULL(heap_profiler_connection_);
std::swap(heap_profiler_connection_, connection);
}

inline profiler::V8HeapProfilerConnection*
Environment::heap_profiler_connection() {
return heap_profiler_connection_.get();
}

inline void Environment::set_heap_prof_name(const std::string& name) {
heap_prof_name_ = name;
}

inline const std::string& Environment::heap_prof_name() const {
return heap_prof_name_;
}

inline void Environment::set_heap_prof_dir(const std::string& dir) {
heap_prof_dir_ = dir;
}

inline const std::string& Environment::heap_prof_dir() const {
return heap_prof_dir_;
}

inline void Environment::set_heap_prof_interval(uint64_t interval) {
heap_prof_interval_ = interval;
}

inline uint64_t Environment::heap_prof_interval() const {
return heap_prof_interval_;
}

#endif // HAVE_INSPECTOR

inline std::shared_ptr<HostPort> Environment::inspector_host_port() {
Expand Down
19 changes: 19 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class AgentWriterHandle;
namespace profiler {
class V8CoverageConnection;
class V8CpuProfilerConnection;
class V8HeapProfilerConnection;
} // namespace profiler
#endif // HAVE_INSPECTOR

Expand Down Expand Up @@ -1151,6 +1152,20 @@ class Environment : public MemoryRetainer {

inline void set_cpu_prof_dir(const std::string& dir);
inline const std::string& cpu_prof_dir() const;

void set_heap_profiler_connection(
std::unique_ptr<profiler::V8HeapProfilerConnection> connection);
profiler::V8HeapProfilerConnection* heap_profiler_connection();

inline void set_heap_prof_name(const std::string& name);
inline const std::string& heap_prof_name() const;

inline void set_heap_prof_dir(const std::string& dir);
inline const std::string& heap_prof_dir() const;

inline void set_heap_prof_interval(uint64_t interval);
inline uint64_t heap_prof_interval() const;

#endif // HAVE_INSPECTOR

private:
Expand Down Expand Up @@ -1190,6 +1205,10 @@ class Environment : public MemoryRetainer {
std::string cpu_prof_dir_;
std::string cpu_prof_name_;
uint64_t cpu_prof_interval_;
std::unique_ptr<profiler::V8HeapProfilerConnection> heap_profiler_connection_;
std::string heap_prof_dir_;
std::string heap_prof_name_;
uint64_t heap_prof_interval_;
#endif // HAVE_INSPECTOR

std::shared_ptr<EnvironmentOptions> options_;
Expand Down
58 changes: 58 additions & 0 deletions src/inspector_profiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,44 @@ void V8CpuProfilerConnection::End() {
DispatchMessage("Profiler.stop");
}

std::string V8HeapProfilerConnection::GetDirectory() const {
return env()->heap_prof_dir();
}

std::string V8HeapProfilerConnection::GetFilename() const {
return env()->heap_prof_name();
}

MaybeLocal<Object> V8HeapProfilerConnection::GetProfile(Local<Object> result) {
Local<Value> profile_v;
if (!result
->Get(env()->context(),
FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
.ToLocal(&profile_v)) {
fprintf(stderr, "'profile' from heap profile result is undefined\n");
return MaybeLocal<Object>();
}
if (!profile_v->IsObject()) {
fprintf(stderr, "'profile' from heap profile result is not an Object\n");
return MaybeLocal<Object>();
}
return profile_v.As<Object>();
}

void V8HeapProfilerConnection::Start() {
DispatchMessage("HeapProfiler.enable");
std::string params = R"({ "samplingInterval": )";
params += std::to_string(env()->heap_prof_interval());
params += " }";
DispatchMessage("HeapProfiler.startSampling", params.c_str());
}

void V8HeapProfilerConnection::End() {
CHECK_EQ(ending_, false);
ending_ = true;
DispatchMessage("HeapProfiler.stopSampling");
}

// For now, we only support coverage profiling, but we may add more
// in the future.
void EndStartedProfilers(Environment* env) {
Expand All @@ -268,6 +306,12 @@ void EndStartedProfilers(Environment* env) {
connection->End();
}

connection = env->heap_profiler_connection();
if (connection != nullptr && !connection->ending()) {
Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending heap profiling\n");
connection->End();
}

connection = env->coverage_connection();
if (connection != nullptr && !connection->ending()) {
Debug(
Expand Down Expand Up @@ -313,6 +357,20 @@ void StartProfilers(Environment* env) {
std::make_unique<V8CpuProfilerConnection>(env));
env->cpu_profiler_connection()->Start();
}
if (env->options()->heap_prof) {
const std::string& dir = env->options()->heap_prof_dir;
env->set_heap_prof_interval(env->options()->heap_prof_interval);
env->set_heap_prof_dir(dir.empty() ? GetCwd() : dir);
if (env->options()->heap_prof_name.empty()) {
DiagnosticFilename filename(env, "Heap", "heapprofile");
env->set_heap_prof_name(*filename);
} else {
env->set_heap_prof_name(env->options()->heap_prof_name);
}
env->set_heap_profiler_connection(
std::make_unique<profiler::V8HeapProfilerConnection>(env));
env->heap_profiler_connection()->Start();
}
}

static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) {
Expand Down
20 changes: 20 additions & 0 deletions src/inspector_profiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,26 @@ class V8CpuProfilerConnection : public V8ProfilerConnection {
bool ending_ = false;
};

class V8HeapProfilerConnection : public V8ProfilerConnection {
public:
explicit V8HeapProfilerConnection(Environment* env)
: V8ProfilerConnection(env) {}

void Start() override;
void End() override;

const char* type() const override { return "heap"; }
bool ending() const override { return ending_; }

std::string GetDirectory() const override;
std::string GetFilename() const override;
v8::MaybeLocal<v8::Object> GetProfile(v8::Local<v8::Object> result) override;

private:
std::unique_ptr<inspector::InspectorSession> session_;
bool ending_ = false;
};

} // namespace profiler
} // namespace node

Expand Down
31 changes: 31 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,19 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
}
}

if (!heap_prof) {
if (!heap_prof_name.empty()) {
errors->push_back("--heap-prof-name must be used with --heap-prof");
}
if (!heap_prof_dir.empty()) {
errors->push_back("--heap-prof-dir must be used with --heap-prof");
}
// We can't catch the case where the value passed is the default value,
// then the option just becomes a noop which is fine.
if (heap_prof_interval != kDefaultHeapProfInterval) {
errors->push_back("--heap-prof-interval must be used with --heap-prof");
}
}
debug_options_.CheckOptions(errors);
#endif // HAVE_INSPECTOR
}
Expand Down Expand Up @@ -374,6 +387,24 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"Directory where the V8 profiles generated by --cpu-prof will be "
"placed. Does not affect --prof.",
&EnvironmentOptions::cpu_prof_dir);
AddOption(
"--heap-prof",
"Start the V8 heap profiler on start up, and write the heap profile "
"to disk before exit. If --heap-prof-dir is not specified, write "
"the profile to the current working directory.",
&EnvironmentOptions::heap_prof);
AddOption("--heap-prof-name",
"specified file name of the V8 CPU profile generated with "
"--heap-prof",
&EnvironmentOptions::heap_prof_name);
AddOption("--heap-prof-dir",
"Directory where the V8 heap profiles generated by --heap-prof "
"will be placed.",
&EnvironmentOptions::heap_prof_dir);
AddOption("--heap-prof-interval",
"specified sampling interval in bytes for the V8 heap "
"profile generated with --heap-prof. (default: 512 * 1024)",
&EnvironmentOptions::heap_prof_interval);
#endif // HAVE_INSPECTOR
AddOption("--redirect-warnings",
"write warnings to file instead of stderr",
Expand Down
5 changes: 5 additions & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ class EnvironmentOptions : public Options {
uint64_t cpu_prof_interval = kDefaultCpuProfInterval;
std::string cpu_prof_name;
bool cpu_prof = false;
std::string heap_prof_dir;
std::string heap_prof_name;
static const uint64_t kDefaultHeapProfInterval = 512 * 1024;
uint64_t heap_prof_interval = kDefaultHeapProfInterval;
bool heap_prof = false;
#endif // HAVE_INSPECTOR
std::string redirect_warnings;
bool throw_deprecation = false;
Expand Down
17 changes: 17 additions & 0 deletions test/fixtures/workload/allocation-exit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const util = require('util');
const total = parseInt(process.env.TEST_ALLOCATION) || 100;
let count = 0;
let string = '';
function runAllocation() {
string += util.inspect(process.env);
if (count++ < total) {
setTimeout(runAllocation, 1);
} else {
console.log(string.length);
process.exit(55);
}
}

setTimeout(runAllocation, 1);
17 changes: 17 additions & 0 deletions test/fixtures/workload/allocation-sigint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const util = require('util');
const total = parseInt(process.env.TEST_ALLOCATION) || 100;
let count = 0;
let string = '';
function runAllocation() {
string += util.inspect(process.env);
if (count++ < total) {
setTimeout(runAllocation, 1);
} else {
console.log(string.length);
process.kill(process.pid, "SIGINT");
}
}

setTimeout(runAllocation, 1);
11 changes: 11 additions & 0 deletions test/fixtures/workload/allocation-worker-argv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

const { Worker } = require('worker_threads');
const path = require('path');
new Worker(path.join(__dirname, 'allocation.js'), {
execArgv: [
'--heap-prof',
'--heap-prof-interval',
process.HEAP_PROF_INTERVAL || '128',
]
});
5 changes: 5 additions & 0 deletions test/fixtures/workload/allocation-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

const { Worker } = require('worker_threads');
const path = require('path');
new Worker(path.join(__dirname, 'allocation.js'));
Loading

0 comments on commit d99e703

Please sign in to comment.