Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inspector: implement --heap-prof #27596

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,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 @@ -142,6 +142,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 @@ -1150,6 +1151,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 @@ -1189,6 +1204,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 @@ -256,6 +256,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) {
Copy link
Member Author

@joyeecheung joyeecheung May 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ofrobots See here and V8ProfilerConnection:ParseProfile(). Because the response returned from the protocol is:

{
  "result": {
    "profile": {
      // ... what we need to write to the file
    }
  }
}

We need to do JSON::Parse and then JSON::Stringify to get to the profile. It would be better if the profile is formatted directly from the C++ data and since we already know about the schema of the returned object, we can serialize it more efficiently (similar to how heap snapshots are serialized in the API). That may be useful for a particularly big profile.

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 @@ -266,6 +304,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 @@ -311,6 +355,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 @@ -173,6 +173,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");
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved
}
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 @@ -378,6 +391,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