Skip to content

Commit d6d05ba

Browse files
theanarkhtargos
authored andcommitted
worker: add cpu profile APIs for worker
PR-URL: #59428 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Stephen Belanger <[email protected]>
1 parent 6ab9306 commit d6d05ba

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
@@ -1953,6 +1953,36 @@ this matches its values.
19531953
19541954
If the worker has stopped, the return value is an empty object.
19551955
1956+
### `worker.startCpuProfile(name)`
1957+
1958+
<!-- YAML
1959+
added: REPLACEME
1960+
-->
1961+
1962+
* name: {string}
1963+
* Returns: {Promise}
1964+
1965+
Starting a CPU profile with the given `name`, then return a Promise that fulfills
1966+
with an error or an object which has a `stop` method. Calling the `stop` method will
1967+
stop collecting the profile, then return a Promise that fulfills with an error or the
1968+
profile data.
1969+
1970+
```cjs
1971+
const { Worker } = require('node:worker_threads');
1972+
1973+
const worker = new Worker(`
1974+
const { parentPort } = require('worker_threads');
1975+
parentPort.on('message', () => {});
1976+
`, { eval: true });
1977+
1978+
worker.on('online', async () => {
1979+
const handle = await worker.startCpuProfile('demo');
1980+
const profile = await handle.stop();
1981+
console.log(profile);
1982+
worker.terminate();
1983+
});
1984+
```
1985+
19561986
### `worker.stderr`
19571987
19581988
<!-- YAML

lib/internal/worker.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,42 @@ class Worker extends EventEmitter {
514514
};
515515
});
516516
}
517+
518+
// TODO(theanarkh): add options, such as sample_interval, CpuProfilingMode
519+
startCpuProfile(name) {
520+
validateString(name, 'name');
521+
const startTaker = this[kHandle]?.startCpuProfile(name);
522+
return new Promise((resolve, reject) => {
523+
if (!startTaker) return reject(new ERR_WORKER_NOT_RUNNING());
524+
startTaker.ondone = (err) => {
525+
if (err) {
526+
return reject(err);
527+
}
528+
let promise = null;
529+
const stop = () => {
530+
if (promise) {
531+
return promise;
532+
}
533+
const stopTaker = this[kHandle]?.stopCpuProfile(name);
534+
return promise = new Promise((resolve, reject) => {
535+
if (!stopTaker) return reject(new ERR_WORKER_NOT_RUNNING());
536+
stopTaker.ondone = (status, profile) => {
537+
if (err) {
538+
return reject(err);
539+
}
540+
resolve(profile);
541+
};
542+
});
543+
};
544+
resolve({
545+
stop,
546+
async [SymbolAsyncDispose]() {
547+
await stop();
548+
},
549+
});
550+
};
551+
});
552+
}
517553
}
518554

519555
/**

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
@@ -1058,6 +1058,13 @@ Environment::~Environment() {
10581058
}
10591059

10601060
delete external_memory_accounter_;
1061+
if (cpu_profiler_) {
1062+
for (auto& it : pending_profiles_) {
1063+
cpu_profiler_->Stop(it.second);
1064+
}
1065+
cpu_profiler_->Dispose();
1066+
cpu_profiler_ = nullptr;
1067+
}
10611068
}
10621069

10631070
void Environment::InitializeLibuv() {
@@ -2221,4 +2228,33 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {
22212228
void Environment::RunWeakRefCleanup() {
22222229
isolate()->ClearKeptObjects();
22232230
}
2231+
2232+
v8::CpuProfilingResult Environment::StartCpuProfile(std::string_view name) {
2233+
HandleScope handle_scope(isolate());
2234+
if (!cpu_profiler_) {
2235+
cpu_profiler_ = v8::CpuProfiler::New(isolate());
2236+
}
2237+
Local<Value> title =
2238+
node::ToV8Value(context(), name, isolate()).ToLocalChecked();
2239+
v8::CpuProfilingResult result =
2240+
cpu_profiler_->Start(title.As<String>(), true);
2241+
if (result.status == v8::CpuProfilingStatus::kStarted) {
2242+
pending_profiles_.emplace(name, result.id);
2243+
}
2244+
return result;
2245+
}
2246+
2247+
v8::CpuProfile* Environment::StopCpuProfile(std::string_view name) {
2248+
if (!cpu_profiler_) {
2249+
return nullptr;
2250+
}
2251+
auto it = pending_profiles_.find(std::string(name));
2252+
if (it == pending_profiles_.end()) {
2253+
return nullptr;
2254+
}
2255+
v8::CpuProfile* profile = cpu_profiler_->Stop(it->second);
2256+
pending_profiles_.erase(it);
2257+
return profile;
2258+
}
2259+
22242260
} // 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
@@ -1051,6 +1052,9 @@ class Environment final : public MemoryRetainer {
10511052

10521053
inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit);
10531054

1055+
v8::CpuProfilingResult StartCpuProfile(std::string_view name);
1056+
v8::CpuProfile* StopCpuProfile(std::string_view name);
1057+
10541058
// Field identifiers for exit_info_
10551059
enum ExitInfoField {
10561060
kExiting = 0,
@@ -1248,6 +1252,9 @@ class Environment final : public MemoryRetainer {
12481252
// track of the BackingStore for a given pointer.
12491253
std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>
12501254
released_allocated_buffers_;
1255+
1256+
v8::CpuProfiler* cpu_profiler_ = nullptr;
1257+
std::unordered_map<std::string, v8::ProfilerId> pending_profiles_;
12511258
};
12521259

12531260
} // 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)