Skip to content

Commit ac92c92

Browse files
authored
worker: add cpu profile APIs for worker
PR-URL: #59428 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
1 parent a240a9c commit ac92c92

File tree

14 files changed

+390
-1
lines changed

14 files changed

+390
-1
lines changed

doc/api/errors.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,36 @@ when an error occurs (and is caught) during the creation of the
826826
context, for example, when the allocation fails or the maximum call stack
827827
size is reached when the context is created.
828828

829+
<a id="ERR_CPU_PROFILE_ALREADY_STARTED"></a>
830+
831+
### `ERR_CPU_PROFILE_ALREADY_STARTED`
832+
833+
<!-- YAML
834+
added: REPLACEME
835+
-->
836+
837+
The CPU profile with the given name is already started.
838+
839+
<a id="ERR_CPU_PROFILE_NOT_STARTED"></a>
840+
841+
### `ERR_CPU_PROFILE_NOT_STARTED`
842+
843+
<!-- YAML
844+
added: REPLACEME
845+
-->
846+
847+
The CPU profile with the given name is not started.
848+
849+
<a id="ERR_CPU_PROFILE_TOO_MANY"></a>
850+
851+
### `ERR_CPU_PROFILE_TOO_MANY`
852+
853+
<!-- YAML
854+
added: REPLACEME
855+
-->
856+
857+
There are too many CPU profiles being collected.
858+
829859
<a id="ERR_CRYPTO_ARGON2_NOT_SUPPORTED"></a>
830860

831861
### `ERR_CRYPTO_ARGON2_NOT_SUPPORTED`

doc/api/worker_threads.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1958,6 +1958,36 @@ this matches its values.
19581958
19591959
If the worker has stopped, the return value is an empty object.
19601960
1961+
### `worker.startCpuProfile(name)`
1962+
1963+
<!-- YAML
1964+
added: REPLACEME
1965+
-->
1966+
1967+
* name: {string}
1968+
* Returns: {Promise}
1969+
1970+
Starting a CPU profile with the given `name`, then return a Promise that fulfills
1971+
with an error or an object which has a `stop` method. Calling the `stop` method will
1972+
stop collecting the profile, then return a Promise that fulfills with an error or the
1973+
profile data.
1974+
1975+
```cjs
1976+
const { Worker } = require('node:worker_threads');
1977+
1978+
const worker = new Worker(`
1979+
const { parentPort } = require('worker_threads');
1980+
parentPort.on('message', () => {});
1981+
`, { eval: true });
1982+
1983+
worker.on('online', async () => {
1984+
const handle = await worker.startCpuProfile('demo');
1985+
const profile = await handle.stop();
1986+
console.log(profile);
1987+
worker.terminate();
1988+
});
1989+
```
1990+
19611991
### `worker.stderr`
19621992
19631993
<!-- YAML

lib/internal/worker.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,42 @@ class Worker extends EventEmitter {
506506
};
507507
});
508508
}
509+
510+
// TODO(theanarkh): add options, such as sample_interval, CpuProfilingMode
511+
startCpuProfile(name) {
512+
validateString(name, 'name');
513+
const startTaker = this[kHandle]?.startCpuProfile(name);
514+
return new Promise((resolve, reject) => {
515+
if (!startTaker) return reject(new ERR_WORKER_NOT_RUNNING());
516+
startTaker.ondone = (err) => {
517+
if (err) {
518+
return reject(err);
519+
}
520+
let promise = null;
521+
const stop = () => {
522+
if (promise) {
523+
return promise;
524+
}
525+
const stopTaker = this[kHandle]?.stopCpuProfile(name);
526+
return promise = new Promise((resolve, reject) => {
527+
if (!stopTaker) return reject(new ERR_WORKER_NOT_RUNNING());
528+
stopTaker.ondone = (status, profile) => {
529+
if (err) {
530+
return reject(err);
531+
}
532+
resolve(profile);
533+
};
534+
});
535+
};
536+
resolve({
537+
stop,
538+
async [SymbolAsyncDispose]() {
539+
await stop();
540+
},
541+
});
542+
};
543+
});
544+
}
509545
}
510546

511547
/**

src/async_wrap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ namespace node {
7979
V(UDPWRAP) \
8080
V(SIGINTWATCHDOG) \
8181
V(WORKER) \
82+
V(WORKERCPUPROFILE) \
8283
V(WORKERCPUUSAGE) \
8384
V(WORKERHEAPSNAPSHOT) \
8485
V(WORKERHEAPSTATISTICS) \

src/env.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,13 @@ Environment::~Environment() {
10621062
}
10631063

10641064
delete external_memory_accounter_;
1065+
if (cpu_profiler_) {
1066+
for (auto& it : pending_profiles_) {
1067+
cpu_profiler_->Stop(it.second);
1068+
}
1069+
cpu_profiler_->Dispose();
1070+
cpu_profiler_ = nullptr;
1071+
}
10651072
}
10661073

10671074
void Environment::InitializeLibuv() {
@@ -2225,4 +2232,33 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {
22252232
void Environment::RunWeakRefCleanup() {
22262233
isolate()->ClearKeptObjects();
22272234
}
2235+
2236+
v8::CpuProfilingResult Environment::StartCpuProfile(std::string_view name) {
2237+
HandleScope handle_scope(isolate());
2238+
if (!cpu_profiler_) {
2239+
cpu_profiler_ = v8::CpuProfiler::New(isolate());
2240+
}
2241+
Local<Value> title =
2242+
node::ToV8Value(context(), name, isolate()).ToLocalChecked();
2243+
v8::CpuProfilingResult result =
2244+
cpu_profiler_->Start(title.As<String>(), true);
2245+
if (result.status == v8::CpuProfilingStatus::kStarted) {
2246+
pending_profiles_.emplace(name, result.id);
2247+
}
2248+
return result;
2249+
}
2250+
2251+
v8::CpuProfile* Environment::StopCpuProfile(std::string_view name) {
2252+
if (!cpu_profiler_) {
2253+
return nullptr;
2254+
}
2255+
auto it = pending_profiles_.find(std::string(name));
2256+
if (it == pending_profiles_.end()) {
2257+
return nullptr;
2258+
}
2259+
v8::CpuProfile* profile = cpu_profiler_->Stop(it->second);
2260+
pending_profiles_.erase(it);
2261+
return profile;
2262+
}
2263+
22282264
} // namespace node

src/env.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
#include "util.h"
5050
#include "uv.h"
5151
#include "v8-external-memory-accounter.h"
52+
#include "v8-profiler.h"
5253
#include "v8.h"
5354

5455
#if HAVE_OPENSSL
@@ -1048,6 +1049,9 @@ class Environment final : public MemoryRetainer {
10481049

10491050
inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit);
10501051

1052+
v8::CpuProfilingResult StartCpuProfile(std::string_view name);
1053+
v8::CpuProfile* StopCpuProfile(std::string_view name);
1054+
10511055
// Field identifiers for exit_info_
10521056
enum ExitInfoField {
10531057
kExiting = 0,
@@ -1244,6 +1248,9 @@ class Environment final : public MemoryRetainer {
12441248
// track of the BackingStore for a given pointer.
12451249
std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>
12461250
released_allocated_buffers_;
1251+
1252+
v8::CpuProfiler* cpu_profiler_ = nullptr;
1253+
std::unordered_map<std::string, v8::ProfilerId> pending_profiles_;
12471254
};
12481255

12491256
} // namespace node

src/env_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@
498498
V(tcp_constructor_template, v8::FunctionTemplate) \
499499
V(tty_constructor_template, v8::FunctionTemplate) \
500500
V(write_wrap_template, v8::ObjectTemplate) \
501+
V(worker_cpu_profile_taker_template, v8::ObjectTemplate) \
501502
V(worker_cpu_usage_taker_template, v8::ObjectTemplate) \
502503
V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \
503504
V(worker_heap_statistics_taker_template, v8::ObjectTemplate) \

src/node_errors.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
4848
V(ERR_CLOSED_MESSAGE_PORT, Error) \
4949
V(ERR_CONSTRUCT_CALL_REQUIRED, TypeError) \
5050
V(ERR_CONSTRUCT_CALL_INVALID, TypeError) \
51+
V(ERR_CPU_PROFILE_ALREADY_STARTED, Error) \
52+
V(ERR_CPU_PROFILE_NOT_STARTED, Error) \
53+
V(ERR_CPU_PROFILE_TOO_MANY, Error) \
5154
V(ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, Error) \
5255
V(ERR_CRYPTO_INITIALIZATION_FAILED, Error) \
5356
V(ERR_CRYPTO_INVALID_ARGON2_PARAMS, TypeError) \

0 commit comments

Comments
 (0)