Skip to content

Commit 9e2aa23

Browse files
theanarkhrichardlau
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 b95ff56 commit 9e2aa23

File tree

14 files changed

+391
-1
lines changed

14 files changed

+391
-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_CUSTOM_ENGINE_NOT_SUPPORTED"></a>
830860

831861
### `ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED`

doc/api/worker_threads.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,6 +1791,36 @@ this matches its values.
17911791
17921792
If the worker has stopped, the return value is an empty object.
17931793
1794+
### `worker.startCpuProfile(name)`
1795+
1796+
<!-- YAML
1797+
added: REPLACEME
1798+
-->
1799+
1800+
* name: {string}
1801+
* Returns: {Promise}
1802+
1803+
Starting a CPU profile with the given `name`, then return a Promise that fulfills
1804+
with an error or an object which has a `stop` method. Calling the `stop` method will
1805+
stop collecting the profile, then return a Promise that fulfills with an error or the
1806+
profile data.
1807+
1808+
```cjs
1809+
const { Worker } = require('node:worker_threads');
1810+
1811+
const worker = new Worker(`
1812+
const { parentPort } = require('worker_threads');
1813+
parentPort.on('message', () => {});
1814+
`, { eval: true });
1815+
1816+
worker.on('online', async () => {
1817+
const handle = await worker.startCpuProfile('demo');
1818+
const profile = await handle.stop();
1819+
console.log(profile);
1820+
worker.terminate();
1821+
});
1822+
```
1823+
17941824
### `worker.stderr`
17951825
17961826
<!-- YAML

lib/internal/worker.js

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

518554
/**

src/async_wrap.h

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

src/env.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,14 @@ Environment::~Environment() {
10801080
addon.Close();
10811081
}
10821082
}
1083+
1084+
if (cpu_profiler_) {
1085+
for (auto& it : pending_profiles_) {
1086+
cpu_profiler_->Stop(it.second);
1087+
}
1088+
cpu_profiler_->Dispose();
1089+
cpu_profiler_ = nullptr;
1090+
}
10831091
}
10841092

10851093
void Environment::InitializeLibuv() {
@@ -2243,4 +2251,33 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {
22432251
void Environment::RunWeakRefCleanup() {
22442252
isolate()->ClearKeptObjects();
22452253
}
2254+
2255+
v8::CpuProfilingResult Environment::StartCpuProfile(std::string_view name) {
2256+
HandleScope handle_scope(isolate());
2257+
if (!cpu_profiler_) {
2258+
cpu_profiler_ = v8::CpuProfiler::New(isolate());
2259+
}
2260+
Local<Value> title =
2261+
node::ToV8Value(context(), name, isolate()).ToLocalChecked();
2262+
v8::CpuProfilingResult result =
2263+
cpu_profiler_->Start(title.As<String>(), true);
2264+
if (result.status == v8::CpuProfilingStatus::kStarted) {
2265+
pending_profiles_.emplace(name, result.id);
2266+
}
2267+
return result;
2268+
}
2269+
2270+
v8::CpuProfile* Environment::StopCpuProfile(std::string_view name) {
2271+
if (!cpu_profiler_) {
2272+
return nullptr;
2273+
}
2274+
auto it = pending_profiles_.find(std::string(name));
2275+
if (it == pending_profiles_.end()) {
2276+
return nullptr;
2277+
}
2278+
v8::CpuProfile* profile = cpu_profiler_->Stop(it->second);
2279+
pending_profiles_.erase(it);
2280+
return profile;
2281+
}
2282+
22462283
} // namespace node

src/env.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#include "req_wrap.h"
4949
#include "util.h"
5050
#include "uv.h"
51+
#include "v8-profiler.h"
5152
#include "v8.h"
5253

5354
#if HAVE_OPENSSL
@@ -1070,6 +1071,9 @@ class Environment final : public MemoryRetainer {
10701071

10711072
inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit);
10721073

1074+
v8::CpuProfilingResult StartCpuProfile(std::string_view name);
1075+
v8::CpuProfile* StopCpuProfile(std::string_view name);
1076+
10731077
// Field identifiers for exit_info_
10741078
enum ExitInfoField {
10751079
kExiting = 0,
@@ -1276,6 +1280,9 @@ class Environment final : public MemoryRetainer {
12761280

12771281
std::unordered_map<std::uintptr_t, v8::Global<v8::Value>>
12781282
async_resource_context_frames_;
1283+
1284+
v8::CpuProfiler* cpu_profiler_ = nullptr;
1285+
std::unordered_map<std::string, v8::ProfilerId> pending_profiles_;
12791286
};
12801287

12811288
} // namespace node

src/env_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@
462462
V(tcp_constructor_template, v8::FunctionTemplate) \
463463
V(tty_constructor_template, v8::FunctionTemplate) \
464464
V(write_wrap_template, v8::ObjectTemplate) \
465+
V(worker_cpu_profile_taker_template, v8::ObjectTemplate) \
465466
V(worker_cpu_usage_taker_template, v8::ObjectTemplate) \
466467
V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \
467468
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_AUTH_TAG, TypeError) \

0 commit comments

Comments
 (0)