inspector: split --cpu-prof-path to --cpu-prof-dir and --cpu-prof-name

To improve the integration of `--cpu-prof` with workers, this patch
splits `--cpu-prof-path` into `--cpu-prof-dir` and `--cpu-prof-name`,
so when a worker is launched from a thread that enables
`--cpu-prof`, if the parent thread sets `--cpu-prof-dir`, then the
profile of both thread would be generated to the specified directory.
If they end up specifying the same `--cpu-prof-name` the behavior
is undefined the last profile will overwritten the first one.

PR-URL: https://github.com/nodejs/node/pull/27306
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
Joyee Cheung 2019-04-19 17:15:04 +08:00
parent a3d1922958
commit 49d3d11ba7
No known key found for this signature in database
GPG Key ID: 92B78A53C8303B8D
12 changed files with 240 additions and 63 deletions

View File

@ -83,8 +83,13 @@ added: REPLACEME
> Stability: 1 - Experimental > Stability: 1 - Experimental
Starts the V8 CPU profiler on start up, and writes the CPU profile to disk Starts the V8 CPU profiler on start up, and writes the CPU profile to disk
before exit. If `--cpu-prof-path` is not specified, the profile will be before exit.
written to `${cwd}/CPU.${yyyymmdd}.${hhmmss}.${pid}.${tid}.${seq}.cpuprofile`.
If `--cpu-prof-dir` is not specified, the generated profile will be placed
in the current working directory.
If `--cpu-prof-name` is not specified, the generated profile will be
named `CPU.${yyyymmdd}.${hhmmss}.${pid}.${tid}.${seq}.cpuprofile`.
```console ```console
$ node --cpu-prof index.js $ node --cpu-prof index.js
@ -92,19 +97,24 @@ $ ls *.cpuprofile
CPU.20190409.202950.15293.0.0.cpuprofile CPU.20190409.202950.15293.0.0.cpuprofile
``` ```
### `--cpu-prof-path` ### `--cpu-prof-dir`
<!-- YAML <!-- YAML
added: REPLACEME added: REPLACEME
--> -->
> Stability: 1 - Experimental > Stability: 1 - Experimental
Location where the CPU profile generated by `--cpu-prof` Specify the directory where the CPU profiles generated by `--cpu-prof` will
should be written to. When used alone, it implies `--cpu-prof`. be placed.
```console ### `--cpu-prof-name`
$ node --cpu-prof-path /tmp/test.cpuprofile index.js <!-- YAML
``` added: REPLACEME
-->
> Stability: 1 - Experimental
Specify the file name of the CPU profile generated by `--cpu-prof`.
### `--enable-fips` ### `--enable-fips`
<!-- YAML <!-- YAML

View File

@ -81,13 +81,17 @@ Print source-able bash completion script for Node.js.
.It Fl -cpu-prof .It Fl -cpu-prof
Start the V8 CPU profiler on start up, and write the CPU profile to disk Start the V8 CPU profiler on start up, and write the CPU profile to disk
before exit. If before exit. If
.Fl -cpu-prof-path .Fl -cpu-prof-dir
is not specified, the profile will be written to the current working directory. is not specified, the profile will be written to the current working directory
with a generated file name.
. .
.It Fl -cpu-prof-path .It Fl -cpu-prof-dir
Path the V8 CPU profile generated with The directory where the CPU profiles generated by
.Fl -cpu-prof .Fl -cpu-prof
will be written to. When used alone, it implies will be placed.
.
.It Fl -cpu-prof-name
File name of the V8 CPU profile generated with
.Fl -cpu-prof .Fl -cpu-prof
. .
.It Fl -enable-fips .It Fl -enable-fips

View File

@ -38,6 +38,14 @@
#include <utility> #include <utility>
#ifdef _WIN32
/* MAX_PATH is in characters, not bytes. Make sure we have enough headroom. */
#define CWD_BUFSIZE (MAX_PATH * 4)
#else
#include <climits> // PATH_MAX on Solaris.
#define CWD_BUFSIZE (PATH_MAX)
#endif
namespace node { namespace node {
inline v8::Isolate* IsolateData::isolate() const { inline v8::Isolate* IsolateData::isolate() const {
@ -678,6 +686,15 @@ inline void Environment::set_cpu_profile_path(const std::string& path) {
inline const std::string& Environment::cpu_profile_path() const { inline const std::string& Environment::cpu_profile_path() const {
return cpu_profile_path_; return cpu_profile_path_;
} }
inline void Environment::set_cpu_prof_dir(const std::string& path) {
cpu_prof_dir_ = path;
}
inline const std::string& Environment::cpu_prof_dir() const {
return cpu_prof_dir_;
}
#endif // HAVE_INSPECTOR #endif // HAVE_INSPECTOR
inline std::shared_ptr<HostPort> Environment::inspector_host_port() { inline std::shared_ptr<HostPort> Environment::inspector_host_port() {

View File

@ -845,6 +845,24 @@ void Environment::stop_sub_worker_contexts() {
} }
} }
#if HAVE_INSPECTOR
void Environment::InitializeCPUProfDir(const std::string& dir) {
if (!dir.empty()) {
cpu_prof_dir_ = dir;
return;
}
char cwd[CWD_BUFSIZE];
size_t size = CWD_BUFSIZE;
int err = uv_cwd(cwd, &size);
// TODO(joyeecheung): fallback to exec path / argv[0]
CHECK_EQ(err, 0);
CHECK_GT(size, 0);
cpu_prof_dir_ = cwd;
}
#endif // HAVE_INSPECTOR
void MemoryTracker::TrackField(const char* edge_name, void MemoryTracker::TrackField(const char* edge_name,
const CleanupHookCallback& value, const CleanupHookCallback& value,
const char* node_name) { const char* node_name) {

View File

@ -1137,6 +1137,11 @@ class Environment : public MemoryRetainer {
inline void set_cpu_profile_path(const std::string& path); inline void set_cpu_profile_path(const std::string& path);
inline const std::string& cpu_profile_path() const; inline const std::string& cpu_profile_path() const;
inline void set_cpu_prof_dir(const std::string& path);
inline const std::string& cpu_prof_dir() const;
void InitializeCPUProfDir(const std::string& dir);
#endif // HAVE_INSPECTOR #endif // HAVE_INSPECTOR
private: private:
@ -1173,6 +1178,7 @@ class Environment : public MemoryRetainer {
std::unique_ptr<profiler::V8CoverageConnection> coverage_connection_; std::unique_ptr<profiler::V8CoverageConnection> coverage_connection_;
std::unique_ptr<profiler::V8CpuProfilerConnection> cpu_profiler_connection_; std::unique_ptr<profiler::V8CpuProfilerConnection> cpu_profiler_connection_;
std::string coverage_directory_; std::string coverage_directory_;
std::string cpu_prof_dir_;
std::string cpu_profile_path_; std::string cpu_profile_path_;
#endif // HAVE_INSPECTOR #endif // HAVE_INSPECTOR

View File

@ -318,19 +318,13 @@ void StartCoverageCollection(Environment* env) {
env->coverage_connection()->Start(); env->coverage_connection()->Start();
} }
void StartCpuProfiling(Environment* env, const std::string& profile_path) { void StartCpuProfiling(Environment* env, const std::string& profile_name) {
std::string path; std::string path = env->cpu_prof_dir() + std::string(kPathSeparator);
if (profile_path.empty()) { if (profile_name.empty()) {
char cwd[CWD_BUFSIZE];
size_t size = CWD_BUFSIZE;
int err = uv_cwd(cwd, &size);
// TODO(joyeecheung): fallback to exec path / argv[0]
CHECK_EQ(err, 0);
CHECK_GT(size, 0);
DiagnosticFilename filename(env, "CPU", "cpuprofile"); DiagnosticFilename filename(env, "CPU", "cpuprofile");
path = cwd + std::string(kPathSeparator) + (*filename); path += *filename;
} else { } else {
path = profile_path; path += profile_name;
} }
env->set_cpu_profile_path(std::move(path)); env->set_cpu_profile_path(std::move(path));
env->set_cpu_profiler_connection( env->set_cpu_profiler_connection(

View File

@ -239,7 +239,8 @@ MaybeLocal<Value> RunBootstrapping(Environment* env) {
#if HAVE_INSPECTOR #if HAVE_INSPECTOR
if (env->options()->cpu_prof) { if (env->options()->cpu_prof) {
profiler::StartCpuProfiling(env, env->options()->cpu_prof_path); env->InitializeCPUProfDir(env->options()->cpu_prof_dir);
profiler::StartCpuProfiling(env, env->options()->cpu_prof_name);
} }
#endif // HAVE_INSPECTOR #endif // HAVE_INSPECTOR

View File

@ -149,6 +149,15 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
} }
#if HAVE_INSPECTOR #if HAVE_INSPECTOR
if (!cpu_prof) {
if (!cpu_prof_name.empty()) {
errors->push_back("--cpu-prof-name must be used with --cpu-prof");
}
if (!cpu_prof_dir.empty()) {
errors->push_back("--cpu-prof-dir must be used with --cpu-prof");
}
}
debug_options_.CheckOptions(errors); debug_options_.CheckOptions(errors);
#endif // HAVE_INSPECTOR #endif // HAVE_INSPECTOR
} }
@ -335,14 +344,17 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
#if HAVE_INSPECTOR #if HAVE_INSPECTOR
AddOption("--cpu-prof", AddOption("--cpu-prof",
"Start the V8 CPU profiler on start up, and write the CPU profile " "Start the V8 CPU profiler on start up, and write the CPU profile "
"to disk before exit. If --cpu-prof-path is not specified, write " "to disk before exit. If --cpu-prof-dir is not specified, write "
"the profile to the current working directory.", "the profile to the current working directory.",
&EnvironmentOptions::cpu_prof); &EnvironmentOptions::cpu_prof);
AddOption("--cpu-prof-path", AddOption("--cpu-prof-name",
"Path the V8 CPU profile generated with --cpu-prof will be " "specified file name of the V8 CPU profile generated with "
"written to.", "--cpu-prof",
&EnvironmentOptions::cpu_prof_path); &EnvironmentOptions::cpu_prof_name);
Implies("--cpu-prof-path", "--cpu-prof"); AddOption("--cpu-prof-dir",
"Directory where the V8 profiles generated by --cpu-prof will be "
"placed. Does not affect --prof.",
&EnvironmentOptions::cpu_prof_dir);
#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",

View File

@ -110,7 +110,8 @@ class EnvironmentOptions : public Options {
bool preserve_symlinks_main = false; bool preserve_symlinks_main = false;
bool prof_process = false; bool prof_process = false;
#if HAVE_INSPECTOR #if HAVE_INSPECTOR
std::string cpu_prof_path; std::string cpu_prof_dir;
std::string cpu_prof_name;
bool cpu_prof = false; bool cpu_prof = false;
#endif // HAVE_INSPECTOR #endif // HAVE_INSPECTOR
std::string redirect_warnings; std::string redirect_warnings;

View File

@ -0,0 +1,7 @@
'use strict';
const { Worker } = require('worker_threads');
const path = require('path');
new Worker(path.join(__dirname, 'fibonacci.js'), {
execArgv: ['--cpu-prof']
});

View File

@ -2,6 +2,4 @@
const { Worker } = require('worker_threads'); const { Worker } = require('worker_threads');
const path = require('path'); const path = require('path');
new Worker(path.join(__dirname, 'fibonacci.js'), { new Worker(path.join(__dirname, 'fibonacci.js'));
execArgv: ['--cpu-prof']
});

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
// This tests that --cpu-prof and --cpu-prof-path works. // This tests that --cpu-prof, --cpu-prof-dir and --cpu-prof-name works.
const common = require('../common'); const common = require('../common');
if (process.features.debug && if (process.features.debug &&
@ -27,17 +27,22 @@ function getCpuProfiles(dir) {
.map((file) => path.join(dir, file)); .map((file) => path.join(dir, file));
} }
function verifyFrames(output, file, suffix) { function getFrames(output, file, suffix) {
const data = fs.readFileSync(file, 'utf8'); const data = fs.readFileSync(file, 'utf8');
const profile = JSON.parse(data); const profile = JSON.parse(data);
const frames = profile.nodes.filter((i) => { const frames = profile.nodes.filter((i) => {
const frame = i.callFrame; const frame = i.callFrame;
return frame.url.endsWith(suffix); return frame.url.endsWith(suffix);
}); });
return { frames, nodes: profile.nodes };
}
function verifyFrames(output, file, suffix) {
const { frames, nodes } = getFrames(output, file, suffix);
if (frames.length === 0) { if (frames.length === 0) {
// Show native debug output and the profile for debugging. // Show native debug output and the profile for debugging.
console.log(output.stderr.toString()); console.log(output.stderr.toString());
console.log(profile.nodes); console.log(nodes);
} }
assert.notDeepStrictEqual(frames, []); assert.notDeepStrictEqual(frames, []);
} }
@ -118,11 +123,11 @@ const env = {
verifyFrames(output, profiles[0], 'fibonacci-sigint.js'); verifyFrames(output, profiles[0], 'fibonacci-sigint.js');
} }
// Outputs CPU profile from worker. // Outputs CPU profile from worker when execArgv is set.
{ {
tmpdir.refresh(); tmpdir.refresh();
const output = spawnSync(process.execPath, [ const output = spawnSync(process.execPath, [
fixtures.path('workload', 'fibonacci-worker.js'), fixtures.path('workload', 'fibonacci-worker-argv.js'),
], { ], {
cwd: tmpdir.path, cwd: tmpdir.path,
env env
@ -136,56 +141,55 @@ const env = {
verifyFrames(output, profiles[0], 'fibonacci.js'); verifyFrames(output, profiles[0], 'fibonacci.js');
} }
// Output to specified --cpu-prof-path without --cpu-prof // --cpu-prof-name without --cpu-prof
{ {
tmpdir.refresh(); tmpdir.refresh();
const file = path.join(tmpdir.path, 'test.cpuprofile');
const output = spawnSync(process.execPath, [ const output = spawnSync(process.execPath, [
'--cpu-prof-path', '--cpu-prof-name',
file, 'test.cpuprofile',
fixtures.path('workload', 'fibonacci.js'), fixtures.path('workload', 'fibonacci.js'),
], { ], {
cwd: tmpdir.path, cwd: tmpdir.path,
env env
}); });
if (output.status !== 0) { const stderr = output.stderr.toString().trim();
console.log(output.stderr.toString()); if (output.status !== 9) {
console.log(stderr);
} }
assert.strictEqual(output.status, 0); assert.strictEqual(output.status, 9);
const profiles = getCpuProfiles(tmpdir.path); assert.strictEqual(
assert.deepStrictEqual(profiles, [file]); stderr,
verifyFrames(output, file, 'fibonacci.js'); `${process.execPath}: --cpu-prof-name must be used with --cpu-prof`);
} }
// Output to specified --cpu-prof-path with --cpu-prof // --cpu-prof-dir without --cpu-prof
{ {
tmpdir.refresh(); tmpdir.refresh();
const file = path.join(tmpdir.path, 'test.cpuprofile');
const output = spawnSync(process.execPath, [ const output = spawnSync(process.execPath, [
'--cpu-prof', '--cpu-prof-dir',
'--cpu-prof-path', 'prof',
file,
fixtures.path('workload', 'fibonacci.js'), fixtures.path('workload', 'fibonacci.js'),
], { ], {
cwd: tmpdir.path, cwd: tmpdir.path,
env env
}); });
if (output.status !== 0) { const stderr = output.stderr.toString().trim();
console.log(output.stderr.toString()); if (output.status !== 9) {
console.log(stderr);
} }
assert.strictEqual(output.status, 0); assert.strictEqual(output.status, 9);
const profiles = getCpuProfiles(tmpdir.path); assert.strictEqual(
assert.deepStrictEqual(profiles, [file]); stderr,
verifyFrames(output, file, 'fibonacci.js'); `${process.execPath}: --cpu-prof-dir must be used with --cpu-prof`);
} }
// Output to specified --cpu-prof-path when it's not absolute // --cpu-prof-name
{ {
tmpdir.refresh(); tmpdir.refresh();
const file = path.join(tmpdir.path, 'test.cpuprofile'); const file = path.join(tmpdir.path, 'test.cpuprofile');
const output = spawnSync(process.execPath, [ const output = spawnSync(process.execPath, [
'--cpu-prof', '--cpu-prof',
'--cpu-prof-path', '--cpu-prof-name',
'test.cpuprofile', 'test.cpuprofile',
fixtures.path('workload', 'fibonacci.js'), fixtures.path('workload', 'fibonacci.js'),
], { ], {
@ -200,3 +204,108 @@ const env = {
assert.deepStrictEqual(profiles, [file]); assert.deepStrictEqual(profiles, [file]);
verifyFrames(output, file, 'fibonacci.js'); verifyFrames(output, file, 'fibonacci.js');
} }
// relative --cpu-prof-dir
{
tmpdir.refresh();
const output = spawnSync(process.execPath, [
'--cpu-prof',
'--cpu-prof-dir',
'prof',
fixtures.path('workload', 'fibonacci.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 = getCpuProfiles(dir);
assert.strictEqual(profiles.length, 1);
verifyFrames(output, profiles[0], 'fibonacci.js');
}
// absolute --cpu-prof-dir
{
tmpdir.refresh();
const dir = path.join(tmpdir.path, 'prof');
const output = spawnSync(process.execPath, [
'--cpu-prof',
'--cpu-prof-dir',
dir,
fixtures.path('workload', 'fibonacci.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 = getCpuProfiles(dir);
assert.strictEqual(profiles.length, 1);
verifyFrames(output, profiles[0], 'fibonacci.js');
}
// --cpu-prof-dir and --cpu-prof-name
{
tmpdir.refresh();
const dir = path.join(tmpdir.path, 'prof');
const file = path.join(dir, 'test.cpuprofile');
const output = spawnSync(process.execPath, [
'--cpu-prof',
'--cpu-prof-name',
'test.cpuprofile',
'--cpu-prof-dir',
dir,
fixtures.path('workload', 'fibonacci.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 = getCpuProfiles(dir);
assert.deepStrictEqual(profiles, [file]);
verifyFrames(output, file, 'fibonacci.js');
}
// --cpu-prof-dir with worker
{
tmpdir.refresh();
const output = spawnSync(process.execPath, [
'--cpu-prof-dir',
'prof',
'--cpu-prof',
fixtures.path('workload', 'fibonacci-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 = getCpuProfiles(dir);
assert.strictEqual(profiles.length, 2);
const profile1 = getFrames(output, profiles[0], 'fibonacci.js');
const profile2 = getFrames(output, profiles[1], 'fibonacci.js');
if (profile1.frames.length === 0 && profile2.frames.length === 0) {
// Show native debug output and the profile for debugging.
console.log(output.stderr.toString());
console.log('CPU path: ', profiles[0]);
console.log(profile1.nodes);
console.log('CPU path: ', profiles[1]);
console.log(profile2.nodes);
}
assert(profile1.frames.length > 0 || profile2.frames.length > 0);
}