Skip to content

cli: support ${pid} placeholder in --cpu-prof-name #59072

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

Merged
merged 11 commits into from
Jul 24, 2025
9 changes: 6 additions & 3 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -540,13 +540,16 @@ $ ls *.cpuprofile
CPU.20190409.202950.15293.0.0.cpuprofile
```

If `--cpu-prof-name` is specified, the provided value will be used as-is; patterns such as
`${hhmmss}` or `${pid}` are not supported.
If `--cpu-prof-name` is specified, the provided value is used as a template
for the file name. The following placeholder is supported and will be
substituted at runtime:

* `${pid}` — the current process ID

```console
$ node --cpu-prof --cpu-prof-name 'CPU.${pid}.cpuprofile' index.js
$ ls *.cpuprofile
'CPU.${pid}.cpuprofile'
CPU.15293.cpuprofile
```

### `--cpu-prof-dir`
Expand Down
26 changes: 25 additions & 1 deletion src/inspector_profiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "node_file.h"
#include "node_internals.h"
#include "util-inl.h"
#include "uv.h"
#include "v8-inspector.h"

#include <cinttypes>
Expand Down Expand Up @@ -465,6 +466,27 @@ static void EndStartedProfilers(Environment* env) {
}
}

static std::string ReplacePlaceholders(const std::string& pattern) {
std::string result = pattern;

static const std::unordered_map<std::string, std::function<std::string()>>
kPlaceholderMap = {
{"${pid}", []() { return std::to_string(uv_os_getpid()); }},
// TODO(haramj): Add more placeholders as needed.
};

for (const auto& [placeholder, getter] : kPlaceholderMap) {
size_t pos = 0;
while ((pos = result.find(placeholder, pos)) != std::string::npos) {
const std::string value = getter();
result.replace(pos, placeholder.length(), value);
pos += value.length();
}
}

return result;
}

void StartProfilers(Environment* env) {
AtExit(env, [](void* env) {
EndStartedProfilers(static_cast<Environment*>(env));
Expand All @@ -486,7 +508,9 @@ void StartProfilers(Environment* env) {
DiagnosticFilename filename(env, "CPU", "cpuprofile");
env->set_cpu_prof_name(*filename);
} else {
env->set_cpu_prof_name(env->options()->cpu_prof_name);
std::string resolved_name =
ReplacePlaceholders(env->options()->cpu_prof_name);
env->set_cpu_prof_name(resolved_name);
}
CHECK_NULL(env->cpu_profiler_connection());
env->set_cpu_profiler_connection(
Expand Down
35 changes: 35 additions & 0 deletions test/sequential/test-cpu-prof-name.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ 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');
Expand Down Expand Up @@ -41,3 +43,36 @@ const {
assert.deepStrictEqual(profiles, [file]);
verifyFrames(output, file, 'fibonacci.js');
}

// --cpu-prof-name with ${pid} placeholder
{
tmpdir.refresh();
// eslint-disable-next-line no-template-curly-in-string
const profName = 'CPU.${pid}.cpuprofile';
const dir = tmpdir.path;

const output = spawnSync(process.execPath, [
'--cpu-prof',
'--cpu-prof-interval',
kCpuProfInterval,
'--cpu-prof-name',
profName,
fixtures.path('workload', 'fibonacci.js'),
], {
cwd: dir,
env
});

if (output.status !== 0) {
console.error(output.stderr.toString());
}

assert.strictEqual(output.status, 0);

const expectedFile = path.join(dir, `CPU.${output.pid}.cpuprofile`);
assert.ok(fs.existsSync(expectedFile), `Expected file ${expectedFile} not found.`);

verifyFrames(output, expectedFile, 'fibonacci.js');

fs.unlinkSync(expectedFile);
}
Loading