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: https://github.com/nodejs/node/pull/27596 Fixes: https://github.com/nodejs/node/issues/27421 Reviewed-By: Jan Krems <jan.krems@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
parent
e2c0c0c680
commit
4b74dae6b2
@ -245,6 +245,57 @@ new X();
|
|||||||
added: v12.0.0
|
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.
|
Generates a heap snapshot each time the process receives the specified signal.
|
||||||
`signal` must be a valid signal name. Disabled by default.
|
`signal` must be a valid signal name. Disabled by default.
|
||||||
|
|
||||||
|
22
doc/node.1
22
doc/node.1
@ -139,6 +139,28 @@ Enable experimental frozen intrinsics support.
|
|||||||
.It Fl -heapsnapshot-signal Ns = Ns Ar signal
|
.It Fl -heapsnapshot-signal Ns = Ns Ar signal
|
||||||
Generate heap snapshot on specified 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
|
.It Fl -http-parser Ns = Ns Ar library
|
||||||
Chooses an HTTP parser library. Available values are
|
Chooses an HTTP parser library. Available values are
|
||||||
.Sy llhttp
|
.Sy llhttp
|
||||||
|
@ -689,6 +689,41 @@ inline const std::string& Environment::cpu_prof_dir() const {
|
|||||||
return cpu_prof_dir_;
|
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
|
#endif // HAVE_INSPECTOR
|
||||||
|
|
||||||
inline std::shared_ptr<HostPort> Environment::inspector_host_port() {
|
inline std::shared_ptr<HostPort> Environment::inspector_host_port() {
|
||||||
|
19
src/env.h
19
src/env.h
@ -73,6 +73,7 @@ class AgentWriterHandle;
|
|||||||
namespace profiler {
|
namespace profiler {
|
||||||
class V8CoverageConnection;
|
class V8CoverageConnection;
|
||||||
class V8CpuProfilerConnection;
|
class V8CpuProfilerConnection;
|
||||||
|
class V8HeapProfilerConnection;
|
||||||
} // namespace profiler
|
} // namespace profiler
|
||||||
#endif // HAVE_INSPECTOR
|
#endif // HAVE_INSPECTOR
|
||||||
|
|
||||||
@ -1151,6 +1152,20 @@ class Environment : public MemoryRetainer {
|
|||||||
|
|
||||||
inline void set_cpu_prof_dir(const std::string& dir);
|
inline void set_cpu_prof_dir(const std::string& dir);
|
||||||
inline const std::string& cpu_prof_dir() const;
|
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
|
#endif // HAVE_INSPECTOR
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -1190,6 +1205,10 @@ class Environment : public MemoryRetainer {
|
|||||||
std::string cpu_prof_dir_;
|
std::string cpu_prof_dir_;
|
||||||
std::string cpu_prof_name_;
|
std::string cpu_prof_name_;
|
||||||
uint64_t cpu_prof_interval_;
|
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
|
#endif // HAVE_INSPECTOR
|
||||||
|
|
||||||
std::shared_ptr<EnvironmentOptions> options_;
|
std::shared_ptr<EnvironmentOptions> options_;
|
||||||
|
@ -258,6 +258,44 @@ void V8CpuProfilerConnection::End() {
|
|||||||
DispatchMessage("Profiler.stop");
|
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
|
// For now, we only support coverage profiling, but we may add more
|
||||||
// in the future.
|
// in the future.
|
||||||
void EndStartedProfilers(Environment* env) {
|
void EndStartedProfilers(Environment* env) {
|
||||||
@ -268,6 +306,12 @@ void EndStartedProfilers(Environment* env) {
|
|||||||
connection->End();
|
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();
|
connection = env->coverage_connection();
|
||||||
if (connection != nullptr && !connection->ending()) {
|
if (connection != nullptr && !connection->ending()) {
|
||||||
Debug(
|
Debug(
|
||||||
@ -313,6 +357,20 @@ void StartProfilers(Environment* env) {
|
|||||||
std::make_unique<V8CpuProfilerConnection>(env));
|
std::make_unique<V8CpuProfilerConnection>(env));
|
||||||
env->cpu_profiler_connection()->Start();
|
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) {
|
static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
@ -107,6 +107,26 @@ class V8CpuProfilerConnection : public V8ProfilerConnection {
|
|||||||
bool ending_ = false;
|
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 profiler
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
|
@ -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);
|
debug_options_.CheckOptions(errors);
|
||||||
#endif // HAVE_INSPECTOR
|
#endif // HAVE_INSPECTOR
|
||||||
}
|
}
|
||||||
@ -369,6 +382,24 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
|||||||
"Directory where the V8 profiles generated by --cpu-prof will be "
|
"Directory where the V8 profiles generated by --cpu-prof will be "
|
||||||
"placed. Does not affect --prof.",
|
"placed. Does not affect --prof.",
|
||||||
&EnvironmentOptions::cpu_prof_dir);
|
&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
|
#endif // HAVE_INSPECTOR
|
||||||
AddOption("--redirect-warnings",
|
AddOption("--redirect-warnings",
|
||||||
"write warnings to file instead of stderr",
|
"write warnings to file instead of stderr",
|
||||||
|
@ -115,6 +115,11 @@ class EnvironmentOptions : public Options {
|
|||||||
uint64_t cpu_prof_interval = kDefaultCpuProfInterval;
|
uint64_t cpu_prof_interval = kDefaultCpuProfInterval;
|
||||||
std::string cpu_prof_name;
|
std::string cpu_prof_name;
|
||||||
bool cpu_prof = false;
|
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
|
#endif // HAVE_INSPECTOR
|
||||||
std::string redirect_warnings;
|
std::string redirect_warnings;
|
||||||
bool throw_deprecation = false;
|
bool throw_deprecation = false;
|
||||||
|
17
test/fixtures/workload/allocation-exit.js
vendored
Normal file
17
test/fixtures/workload/allocation-exit.js
vendored
Normal file
@ -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
test/fixtures/workload/allocation-sigint.js
vendored
Normal file
17
test/fixtures/workload/allocation-sigint.js
vendored
Normal file
@ -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
test/fixtures/workload/allocation-worker-argv.js
vendored
Normal file
11
test/fixtures/workload/allocation-worker-argv.js
vendored
Normal file
@ -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
test/fixtures/workload/allocation-worker.js
vendored
Normal file
5
test/fixtures/workload/allocation-worker.js
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Worker } = require('worker_threads');
|
||||||
|
const path = require('path');
|
||||||
|
new Worker(path.join(__dirname, 'allocation.js'));
|
16
test/fixtures/workload/allocation.js
vendored
Normal file
16
test/fixtures/workload/allocation.js
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
'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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(runAllocation, 1);
|
375
test/sequential/test-heap-prof.js
Normal file
375
test/sequential/test-heap-prof.js
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// This tests that --heap-prof, --heap-prof-dir and --heap-prof-name works.
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
|
||||||
|
const fixtures = require('../common/fixtures');
|
||||||
|
common.skipIfInspectorDisabled();
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
|
||||||
|
const tmpdir = require('../common/tmpdir');
|
||||||
|
|
||||||
|
function getHeapProfiles(dir) {
|
||||||
|
const list = fs.readdirSync(dir);
|
||||||
|
return list
|
||||||
|
.filter((file) => file.endsWith('.heapprofile'))
|
||||||
|
.map((file) => path.join(dir, file));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFirstFrameInNode(root, func) {
|
||||||
|
const first = root.children.find(
|
||||||
|
(child) => child.callFrame.functionName === func
|
||||||
|
);
|
||||||
|
if (first) {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
for (const child of root.children) {
|
||||||
|
const first = findFirstFrameInNode(child, func);
|
||||||
|
if (first) {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFirstFrame(file, func) {
|
||||||
|
const data = fs.readFileSync(file, 'utf8');
|
||||||
|
const profile = JSON.parse(data);
|
||||||
|
const first = findFirstFrameInNode(profile.head, func);
|
||||||
|
return { frame: first, roots: profile.head.children };
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyFrames(output, file, func) {
|
||||||
|
const { frame, roots } = findFirstFrame(file, func);
|
||||||
|
if (!frame) {
|
||||||
|
// Show native debug output and the profile for debugging.
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
console.log(roots);
|
||||||
|
}
|
||||||
|
assert.notDeepStrictEqual(frame, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to set --heap-prof-interval to a small enough value to make
|
||||||
|
// sure we can find our workload in the samples, so we need to set
|
||||||
|
// TEST_ALLOCATION > kHeapProfInterval.
|
||||||
|
const kHeapProfInterval = 128;
|
||||||
|
const TEST_ALLOCATION = kHeapProfInterval * 2;
|
||||||
|
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
TEST_ALLOCATION,
|
||||||
|
NODE_DEBUG_NATIVE: 'INSPECTOR_PROFILER'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test --heap-prof without --heap-prof-interval. Here we just verify that
|
||||||
|
// we manage to generate a profile.
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof',
|
||||||
|
fixtures.path('workload', 'allocation.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
if (output.status !== 0) {
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
console.log(output);
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 0);
|
||||||
|
const profiles = getHeapProfiles(tmpdir.path);
|
||||||
|
assert.strictEqual(profiles.length, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outputs heap profile when event loop is drained.
|
||||||
|
// TODO(joyeecheung): share the fixutres with v8 coverage tests
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof',
|
||||||
|
'--heap-prof-interval',
|
||||||
|
kHeapProfInterval,
|
||||||
|
fixtures.path('workload', 'allocation.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
if (output.status !== 0) {
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
console.log(output);
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 0);
|
||||||
|
const profiles = getHeapProfiles(tmpdir.path);
|
||||||
|
assert.strictEqual(profiles.length, 1);
|
||||||
|
verifyFrames(output, profiles[0], 'runAllocation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outputs heap profile when process.exit(55) exits process.
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof',
|
||||||
|
'--heap-prof-interval',
|
||||||
|
kHeapProfInterval,
|
||||||
|
fixtures.path('workload', 'allocation-exit.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
if (output.status !== 55) {
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 55);
|
||||||
|
const profiles = getHeapProfiles(tmpdir.path);
|
||||||
|
assert.strictEqual(profiles.length, 1);
|
||||||
|
verifyFrames(output, profiles[0], 'runAllocation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outputs heap profile when process.kill(process.pid, "SIGINT"); exits process.
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof',
|
||||||
|
'--heap-prof-interval',
|
||||||
|
kHeapProfInterval,
|
||||||
|
fixtures.path('workload', 'allocation-sigint.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
if (!common.isWindows) {
|
||||||
|
if (output.signal !== 'SIGINT') {
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.signal, 'SIGINT');
|
||||||
|
}
|
||||||
|
const profiles = getHeapProfiles(tmpdir.path);
|
||||||
|
assert.strictEqual(profiles.length, 1);
|
||||||
|
verifyFrames(output, profiles[0], 'runAllocation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outputs heap profile from worker when execArgv is set.
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
fixtures.path('workload', 'allocation-worker-argv.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
HEAP_PROF_INTERVAL: '128'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (output.status !== 0) {
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 0);
|
||||||
|
const profiles = getHeapProfiles(tmpdir.path);
|
||||||
|
assert.strictEqual(profiles.length, 1);
|
||||||
|
verifyFrames(output, profiles[0], 'runAllocation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --heap-prof-name without --heap-prof
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof-name',
|
||||||
|
'test.heapprofile',
|
||||||
|
fixtures.path('workload', 'allocation.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
const stderr = output.stderr.toString().trim();
|
||||||
|
if (output.status !== 9) {
|
||||||
|
console.log(stderr);
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 9);
|
||||||
|
assert.strictEqual(
|
||||||
|
stderr,
|
||||||
|
`${process.execPath}: --heap-prof-name must be used with --heap-prof`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --heap-prof-dir without --heap-prof
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof-dir',
|
||||||
|
'prof',
|
||||||
|
fixtures.path('workload', 'allocation.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
const stderr = output.stderr.toString().trim();
|
||||||
|
if (output.status !== 9) {
|
||||||
|
console.log(stderr);
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 9);
|
||||||
|
assert.strictEqual(
|
||||||
|
stderr,
|
||||||
|
`${process.execPath}: --heap-prof-dir must be used with --heap-prof`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --heap-prof-interval without --heap-prof
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof-interval',
|
||||||
|
kHeapProfInterval,
|
||||||
|
fixtures.path('workload', 'allocation.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
const stderr = output.stderr.toString().trim();
|
||||||
|
if (output.status !== 9) {
|
||||||
|
console.log(stderr);
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 9);
|
||||||
|
assert.strictEqual(
|
||||||
|
stderr,
|
||||||
|
`${process.execPath}: ` +
|
||||||
|
'--heap-prof-interval must be used with --heap-prof');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --heap-prof-name
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const file = path.join(tmpdir.path, 'test.heapprofile');
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof',
|
||||||
|
'--heap-prof-name',
|
||||||
|
'test.heapprofile',
|
||||||
|
'--heap-prof-interval',
|
||||||
|
kHeapProfInterval,
|
||||||
|
fixtures.path('workload', 'allocation.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
if (output.status !== 0) {
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 0);
|
||||||
|
const profiles = getHeapProfiles(tmpdir.path);
|
||||||
|
assert.deepStrictEqual(profiles, [file]);
|
||||||
|
verifyFrames(output, file, 'runAllocation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// relative --heap-prof-dir
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof',
|
||||||
|
'--heap-prof-dir',
|
||||||
|
'prof',
|
||||||
|
'--heap-prof-interval',
|
||||||
|
kHeapProfInterval,
|
||||||
|
fixtures.path('workload', 'allocation.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
if (output.status !== 0) {
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 0);
|
||||||
|
const dir = path.join(tmpdir.path, 'prof');
|
||||||
|
assert(fs.existsSync(dir));
|
||||||
|
const profiles = getHeapProfiles(dir);
|
||||||
|
assert.strictEqual(profiles.length, 1);
|
||||||
|
verifyFrames(output, profiles[0], 'runAllocation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// absolute --heap-prof-dir
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const dir = path.join(tmpdir.path, 'prof');
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof',
|
||||||
|
'--heap-prof-dir',
|
||||||
|
dir,
|
||||||
|
'--heap-prof-interval',
|
||||||
|
kHeapProfInterval,
|
||||||
|
fixtures.path('workload', 'allocation.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
if (output.status !== 0) {
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 0);
|
||||||
|
assert(fs.existsSync(dir));
|
||||||
|
const profiles = getHeapProfiles(dir);
|
||||||
|
assert.strictEqual(profiles.length, 1);
|
||||||
|
verifyFrames(output, profiles[0], 'runAllocation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --heap-prof-dir and --heap-prof-name
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const dir = path.join(tmpdir.path, 'prof');
|
||||||
|
const file = path.join(dir, 'test.heapprofile');
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof',
|
||||||
|
'--heap-prof-name',
|
||||||
|
'test.heapprofile',
|
||||||
|
'--heap-prof-dir',
|
||||||
|
dir,
|
||||||
|
'--heap-prof-interval',
|
||||||
|
kHeapProfInterval,
|
||||||
|
fixtures.path('workload', 'allocation.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
if (output.status !== 0) {
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 0);
|
||||||
|
assert(fs.existsSync(dir));
|
||||||
|
const profiles = getHeapProfiles(dir);
|
||||||
|
assert.deepStrictEqual(profiles, [file]);
|
||||||
|
verifyFrames(output, file, 'runAllocation');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
tmpdir.refresh();
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
'--heap-prof-interval',
|
||||||
|
kHeapProfInterval,
|
||||||
|
'--heap-prof-dir',
|
||||||
|
'prof',
|
||||||
|
'--heap-prof',
|
||||||
|
fixtures.path('workload', 'allocation-worker.js'),
|
||||||
|
], {
|
||||||
|
cwd: tmpdir.path,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
if (output.status !== 0) {
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
}
|
||||||
|
assert.strictEqual(output.status, 0);
|
||||||
|
const dir = path.join(tmpdir.path, 'prof');
|
||||||
|
assert(fs.existsSync(dir));
|
||||||
|
const profiles = getHeapProfiles(dir);
|
||||||
|
assert.strictEqual(profiles.length, 2);
|
||||||
|
const profile1 = findFirstFrame(profiles[0], 'runAllocation');
|
||||||
|
const profile2 = findFirstFrame(profiles[1], 'runAllocation');
|
||||||
|
if (!profile1.frame && !profile2.frame) {
|
||||||
|
// Show native debug output and the profile for debugging.
|
||||||
|
console.log(output.stderr.toString());
|
||||||
|
console.log('heap path: ', profiles[0]);
|
||||||
|
console.log(profile1.roots);
|
||||||
|
console.log('heap path: ', profiles[1]);
|
||||||
|
console.log(profile2.roots);
|
||||||
|
}
|
||||||
|
assert(profile1.frame || profile2.frame);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user