GeezerVison is showing github.com — worker: add cpu profile APIs for worker · nodejs/node@d6d05ba
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/errors.md +30Lines changed: 30 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -826,6 +826,36 @@ when an error occurs (and is caught) during the creation of the | 826 | 826 | context, for example, when the allocation fails or the maximum call stack | 827 | 827 | size is reached when the context is created. | 828 | 828 |
| | 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 | + | 829 | 859 | <a id="ERR_CRYPTO_ARGON2_NOT_SUPPORTED"></a> | 830 | 860 |
| 831 | 861 | ### `ERR_CRYPTO_ARGON2_NOT_SUPPORTED` | |
Collapse file Copy file name to clipboardExpand all lines: doc/api/worker_threads.md +30Lines changed: 30 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1953,6 +1953,36 @@ this matches its values. | 1953 | 1953 |
| 1954 | 1954 | If the worker has stopped, the return value is an empty object. | 1955 | 1955 |
| | 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 | +
| 1956 | 1986 | ### `worker.stderr` | 1957 | 1987 |
| 1958 | 1988 | <!-- YAML | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/worker.js +36Lines changed: 36 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -514,6 +514,42 @@ class Worker extends EventEmitter { | 514 | 514 | }; | 515 | 515 | }); | 516 | 516 | } | | 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 | + } | 517 | 553 | } | 518 | 554 |
| 519 | 555 | /** | |
Collapse file Copy file name to clipboardExpand all lines: src/async_wrap.h +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -79,6 +79,7 @@ namespace node { | 79 | 79 | V(UDPWRAP) \ | 80 | 80 | V(SIGINTWATCHDOG) \ | 81 | 81 | V(WORKER) \ | | 82 | + V(WORKERCPUPROFILE) \ | 82 | 83 | V(WORKERCPUUSAGE) \ | 83 | 84 | V(WORKERHEAPSNAPSHOT) \ | 84 | 85 | V(WORKERHEAPSTATISTICS) \ | |
Collapse file Copy file name to clipboardExpand all lines: src/env.cc +36Lines changed: 36 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1058,6 +1058,13 @@ Environment::~Environment() { | 1058 | 1058 | } | 1059 | 1059 |
| 1060 | 1060 | 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 | + } | 1061 | 1068 | } | 1062 | 1069 |
| 1063 | 1070 | void Environment::InitializeLibuv() { | @@ -2221,4 +2228,33 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const { | 2221 | 2228 | void Environment::RunWeakRefCleanup() { | 2222 | 2229 | isolate()->ClearKeptObjects(); | 2223 | 2230 | } | | 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 | + | 2224 | 2260 | } // namespace node |
Collapse file Copy file name to clipboardExpand all lines: src/env.h +7Lines changed: 7 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 49 | 49 | #include "util.h" | 50 | 50 | #include "uv.h" | 51 | 51 | #include "v8-external-memory-accounter.h" | | 52 | +#include "v8-profiler.h" | 52 | 53 | #include "v8.h" | 53 | 54 |
| 54 | 55 | #if HAVE_OPENSSL | @@ -1051,6 +1052,9 @@ class Environment final : public MemoryRetainer { | 1051 | 1052 |
| 1052 | 1053 | inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit); | 1053 | 1054 |
| | 1055 | + v8::CpuProfilingResult StartCpuProfile(std::string_view name); | | 1056 | + v8::CpuProfile* StopCpuProfile(std::string_view name); | | 1057 | + | 1054 | 1058 | // Field identifiers for exit_info_ | 1055 | 1059 | enum ExitInfoField { | 1056 | 1060 | kExiting = 0, | @@ -1248,6 +1252,9 @@ class Environment final : public MemoryRetainer { | 1248 | 1252 | // track of the BackingStore for a given pointer. | 1249 | 1253 | std::unordered_map<char*, std::unique_ptr<v8::BackingStore>> | 1250 | 1254 | released_allocated_buffers_; | | 1255 | + | | 1256 | + v8::CpuProfiler* cpu_profiler_ = nullptr; | | 1257 | + std::unordered_map<std::string, v8::ProfilerId> pending_profiles_; | 1251 | 1258 | }; | 1252 | 1259 |
| 1253 | 1260 | } // namespace node | |
Collapse file Copy file name to clipboardExpand all lines: src/env_properties.h +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 498 | 498 | V(tcp_constructor_template, v8::FunctionTemplate) \ | 499 | 499 | V(tty_constructor_template, v8::FunctionTemplate) \ | 500 | 500 | V(write_wrap_template, v8::ObjectTemplate) \ | | 501 | + V(worker_cpu_profile_taker_template, v8::ObjectTemplate) \ | 501 | 502 | V(worker_cpu_usage_taker_template, v8::ObjectTemplate) \ | 502 | 503 | V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \ | 503 | 504 | V(worker_heap_statistics_taker_template, v8::ObjectTemplate) \ | |
Collapse file Copy file name to clipboardExpand all lines: src/node_errors.h +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -48,6 +48,9 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details); | 48 | 48 | V(ERR_CLOSED_MESSAGE_PORT, Error) \ | 49 | 49 | V(ERR_CONSTRUCT_CALL_REQUIRED, TypeError) \ | 50 | 50 | 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) \ | 51 | 54 | V(ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, Error) \ | 52 | 55 | V(ERR_CRYPTO_INITIALIZATION_FAILED, Error) \ | 53 | 56 | V(ERR_CRYPTO_INVALID_ARGON2_PARAMS, TypeError) \ | |
|