GeezerVison is showing github.com — worker: optimize cpu profile implement · nodejs/node@2e54411
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/v8.md +27Lines changed: 27 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1394,6 +1394,33 @@ setTimeout(() => { | 1394 | 1394 | }, 1000); | 1395 | 1395 | ``` | 1396 | 1396 |
| | 1397 | +## Class: `CPUProfileHandle` | | 1398 | + | | 1399 | +<!-- YAML | | 1400 | +added: REPLACEME | | 1401 | +--> | | 1402 | + | | 1403 | +### `cpuProfileHandle.stop()` | | 1404 | + | | 1405 | +<!-- YAML | | 1406 | +added: REPLACEME | | 1407 | +--> | | 1408 | + | | 1409 | +* Returns: {Promise} | | 1410 | + | | 1411 | +Stopping collecting the profile, then return a Promise that fulfills with an error or the | | 1412 | +profile data. | | 1413 | + | | 1414 | +### `cpuProfileHandle[Symbol.asyncDispose]()` | | 1415 | + | | 1416 | +<!-- YAML | | 1417 | +added: REPLACEME | | 1418 | +--> | | 1419 | + | | 1420 | +* Returns: {Promise} | | 1421 | + | | 1422 | +Stopping collecting the profile and the profile will be discarded. | | 1423 | + | 1397 | 1424 | ## `v8.isStringOneByteRepresentation(content)` | 1398 | 1425 |
| 1399 | 1426 | <!-- YAML | |
Collapse file Copy file name to clipboardExpand all lines: doc/api/worker_threads.md +20-7Lines changed: 20 additions & 7 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1953,19 +1953,16 @@ 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)` | | 1956 | +### `worker.startCpuProfile()` | 1957 | 1957 |
| 1958 | 1958 | <!-- YAML | 1959 | 1959 | added: REPLACEME | 1960 | 1960 | --> | 1961 | 1961 |
| 1962 | | -* name: {string} | 1963 | 1962 | * Returns: {Promise} | 1964 | 1963 |
| 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. | | 1964 | +Starting a CPU profile then return a Promise that fulfills with an error | | 1965 | +or an `CPUProfileHandle` object. This API supports `await using` syntax. | 1969 | 1966 |
| 1970 | 1967 | ```cjs | 1971 | 1968 | const { Worker } = require('node:worker_threads'); | @@ -1976,13 +1973,29 @@ const worker = new Worker(` | 1976 | 1973 | `, { eval: true }); | 1977 | 1974 |
| 1978 | 1975 | worker.on('online', async () => { | 1979 | | - const handle = await worker.startCpuProfile('demo'); | | 1976 | + const handle = await worker.startCpuProfile(); | 1980 | 1977 | const profile = await handle.stop(); | 1981 | 1978 | console.log(profile); | 1982 | 1979 | worker.terminate(); | 1983 | 1980 | }); | 1984 | 1981 | ``` | 1985 | 1982 |
| | 1983 | +`await using` example. | | 1984 | +
| | 1985 | +```cjs | | 1986 | +const { Worker } = require('node::worker_threads'); | | 1987 | +
| | 1988 | +const w = new Worker(` | | 1989 | + const { parentPort } = require('worker_threads'); | | 1990 | + parentPort.on('message', () => {}); | | 1991 | + `, { eval: true }); | | 1992 | +
| | 1993 | +w.on('online', async () => { | | 1994 | + // Stop profile automatically when return and profile will be discarded | | 1995 | + await using handle = await w.startCpuProfile(); | | 1996 | +}); | | 1997 | +``` | | 1998 | +
| 1986 | 1999 | ### `worker.stderr` | 1987 | 2000 |
| 1988 | 2001 | <!-- YAML | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/worker.js +35-26Lines changed: 35 additions & 26 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -133,6 +133,37 @@ function assignEnvironmentData(data) { | 133 | 133 | }); | 134 | 134 | } | 135 | 135 |
| | 136 | +class CPUProfileHandle { | | 137 | + #worker = null; | | 138 | + #id = null; | | 139 | + #promise = null; | | 140 | + | | 141 | + constructor(worker, id) { | | 142 | + this.#worker = worker; | | 143 | + this.#id = id; | | 144 | + } | | 145 | + | | 146 | + stop() { | | 147 | + if (this.#promise) { | | 148 | + return this.#promise; | | 149 | + } | | 150 | + const stopTaker = this.#worker[kHandle]?.stopCpuProfile(this.#id); | | 151 | + return this.#promise = new Promise((resolve, reject) => { | | 152 | + if (!stopTaker) return reject(new ERR_WORKER_NOT_RUNNING()); | | 153 | + stopTaker.ondone = (err, profile) => { | | 154 | + if (err) { | | 155 | + return reject(err); | | 156 | + } | | 157 | + resolve(profile); | | 158 | + }; | | 159 | + }); | | 160 | + }; | | 161 | + | | 162 | + async [SymbolAsyncDispose]() { | | 163 | + await this.stop(); | | 164 | + } | | 165 | +} | | 166 | + | 136 | 167 | class Worker extends EventEmitter { | 137 | 168 | constructor(filename, options = kEmptyObject) { | 138 | 169 | throwIfBuildingSnapshot('Creating workers'); | @@ -516,37 +547,15 @@ class Worker extends EventEmitter { | 516 | 547 | } | 517 | 548 |
| 518 | 549 | // TODO(theanarkh): add options, such as sample_interval, CpuProfilingMode | 519 | | - startCpuProfile(name) { | 520 | | - validateString(name, 'name'); | 521 | | - const startTaker = this[kHandle]?.startCpuProfile(name); | | 550 | + startCpuProfile() { | | 551 | + const startTaker = this[kHandle]?.startCpuProfile(); | 522 | 552 | return new Promise((resolve, reject) => { | 523 | 553 | if (!startTaker) return reject(new ERR_WORKER_NOT_RUNNING()); | 524 | | - startTaker.ondone = (err) => { | | 554 | + startTaker.ondone = (err, id) => { | 525 | 555 | if (err) { | 526 | 556 | return reject(err); | 527 | 557 | } | 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 | | - }); | | 558 | + resolve(new CPUProfileHandle(this, id)); | 550 | 559 | }; | 551 | 560 | }); | 552 | 561 | } | |
Collapse file Copy file name to clipboardExpand all lines: src/env.cc +10-10Lines changed: 10 additions & 10 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1060,7 +1060,7 @@ Environment::~Environment() { | 1060 | 1060 | delete external_memory_accounter_; | 1061 | 1061 | if (cpu_profiler_) { | 1062 | 1062 | for (auto& it : pending_profiles_) { | 1063 | | - cpu_profiler_->Stop(it.second); | | 1063 | + cpu_profiler_->Stop(it); | 1064 | 1064 | } | 1065 | 1065 | cpu_profiler_->Dispose(); | 1066 | 1066 | cpu_profiler_ = nullptr; | @@ -2229,30 +2229,30 @@ void Environment::RunWeakRefCleanup() { | 2229 | 2229 | isolate()->ClearKeptObjects(); | 2230 | 2230 | } | 2231 | 2231 |
| 2232 | | -v8::CpuProfilingResult Environment::StartCpuProfile(std::string_view name) { | | 2232 | +v8::CpuProfilingResult Environment::StartCpuProfile() { | 2233 | 2233 | HandleScope handle_scope(isolate()); | 2234 | 2234 | if (!cpu_profiler_) { | 2235 | 2235 | cpu_profiler_ = v8::CpuProfiler::New(isolate()); | 2236 | 2236 | } | 2237 | | - Local<Value> title = | 2238 | | - node::ToV8Value(context(), name, isolate()).ToLocalChecked(); | 2239 | | - v8::CpuProfilingResult result = | 2240 | | - cpu_profiler_->Start(title.As<String>(), true); | | 2237 | + v8::CpuProfilingResult result = cpu_profiler_->Start( | | 2238 | + v8::CpuProfilingOptions{v8::CpuProfilingMode::kLeafNodeLineNumbers, | | 2239 | + v8::CpuProfilingOptions::kNoSampleLimit}); | 2241 | 2240 | if (result.status == v8::CpuProfilingStatus::kStarted) { | 2242 | | - pending_profiles_.emplace(name, result.id); | | 2241 | + pending_profiles_.push_back(result.id); | 2243 | 2242 | } | 2244 | 2243 | return result; | 2245 | 2244 | } | 2246 | 2245 |
| 2247 | | -v8::CpuProfile* Environment::StopCpuProfile(std::string_view name) { | | 2246 | +v8::CpuProfile* Environment::StopCpuProfile(v8::ProfilerId profile_id) { | 2248 | 2247 | if (!cpu_profiler_) { | 2249 | 2248 | return nullptr; | 2250 | 2249 | } | 2251 | | - auto it = pending_profiles_.find(std::string(name)); | | 2250 | + auto it = | | 2251 | + std::find(pending_profiles_.begin(), pending_profiles_.end(), profile_id); | 2252 | 2252 | if (it == pending_profiles_.end()) { | 2253 | 2253 | return nullptr; | 2254 | 2254 | } | 2255 | | - v8::CpuProfile* profile = cpu_profiler_->Stop(it->second); | | 2255 | + v8::CpuProfile* profile = cpu_profiler_->Stop(*it); | 2256 | 2256 | pending_profiles_.erase(it); | 2257 | 2257 | return profile; | 2258 | 2258 | } | |
Collapse file Copy file name to clipboardExpand all lines: src/env.h +3-3Lines changed: 3 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1052,8 +1052,8 @@ class Environment final : public MemoryRetainer { | 1052 | 1052 |
| 1053 | 1053 | inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit); | 1054 | 1054 |
| 1055 | | - v8::CpuProfilingResult StartCpuProfile(std::string_view name); | 1056 | | - v8::CpuProfile* StopCpuProfile(std::string_view name); | | 1055 | + v8::CpuProfilingResult StartCpuProfile(); | | 1056 | + v8::CpuProfile* StopCpuProfile(v8::ProfilerId profile_id); | 1057 | 1057 |
| 1058 | 1058 | // Field identifiers for exit_info_ | 1059 | 1059 | enum ExitInfoField { | @@ -1254,7 +1254,7 @@ class Environment final : public MemoryRetainer { | 1254 | 1254 | released_allocated_buffers_; | 1255 | 1255 |
| 1256 | 1256 | v8::CpuProfiler* cpu_profiler_ = nullptr; | 1257 | | - std::unordered_map<std::string, v8::ProfilerId> pending_profiles_; | | 1257 | + std::vector<v8::ProfilerId> pending_profiles_; | 1258 | 1258 | }; | 1259 | 1259 |
| 1260 | 1260 | } // namespace node | |
Collapse file Copy file name to clipboardExpand all lines: src/node_errors.h -1Lines changed: 0 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -48,7 +48,6 @@ 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 | 51 | V(ERR_CPU_PROFILE_NOT_STARTED, Error) \ | 53 | 52 | V(ERR_CPU_PROFILE_TOO_MANY, Error) \ | 54 | 53 | V(ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, Error) \ | |
Collapse file Copy file name to clipboardExpand all lines: src/node_worker.cc +11-16Lines changed: 11 additions & 16 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -915,9 +915,6 @@ void Worker::StartCpuProfile(const FunctionCallbackInfo<Value>& args) { | 915 | 915 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); | 916 | 916 | Environment* env = w->env(); | 917 | 917 |
| 918 | | - CHECK(args[0]->IsString()); | 919 | | - node::Utf8Value name(env->isolate(), args[0]); | 920 | | - | 921 | 918 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); | 922 | 919 | Local<Object> wrap; | 923 | 920 | if (!env->worker_cpu_profile_taker_template() | @@ -930,25 +927,23 @@ void Worker::StartCpuProfile(const FunctionCallbackInfo<Value>& args) { | 930 | 927 | MakeDetachedBaseObject<WorkerCpuProfileTaker>(env, wrap); | 931 | 928 |
| 932 | 929 | bool scheduled = w->RequestInterrupt([taker = std::move(taker), | 933 | | - name = name.ToString(), | 934 | 930 | env](Environment* worker_env) mutable { | 935 | | - CpuProfilingResult result = worker_env->StartCpuProfile(name); | | 931 | + CpuProfilingResult result = worker_env->StartCpuProfile(); | 936 | 932 | env->SetImmediateThreadsafe( | 937 | | - [taker = std::move(taker), | 938 | | - status = result.status](Environment* env) mutable { | | 933 | + [taker = std::move(taker), result = result](Environment* env) mutable { | 939 | 934 | Isolate* isolate = env->isolate(); | 940 | 935 | HandleScope handle_scope(isolate); | 941 | 936 | Context::Scope context_scope(env->context()); | 942 | 937 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get()); | 943 | 938 | Local<Value> argv[] = { | 944 | | - Null(isolate), // error | | 939 | + Null(isolate), // error | | 940 | + Undefined(isolate), // profile id | 945 | 941 | }; | 946 | | - if (status == CpuProfilingStatus::kAlreadyStarted) { | 947 | | - argv[0] = ERR_CPU_PROFILE_ALREADY_STARTED( | 948 | | - isolate, "CPU profile already started"); | 949 | | - } else if (status == CpuProfilingStatus::kErrorTooManyProfilers) { | | 942 | + if (result.status == CpuProfilingStatus::kErrorTooManyProfilers) { | 950 | 943 | argv[0] = ERR_CPU_PROFILE_TOO_MANY( | 951 | 944 | isolate, "There are too many CPU profiles"); | | 945 | + } else if (result.status == CpuProfilingStatus::kStarted) { | | 946 | + argv[1] = Number::New(isolate, result.id); | 952 | 947 | } | 953 | 948 | taker->MakeCallback(env->ondone_string(), arraysize(argv), argv); | 954 | 949 | }, | @@ -965,8 +960,8 @@ void Worker::StopCpuProfile(const FunctionCallbackInfo<Value>& args) { | 965 | 960 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); | 966 | 961 |
| 967 | 962 | Environment* env = w->env(); | 968 | | - CHECK(args[0]->IsString()); | 969 | | - node::Utf8Value name(env->isolate(), args[0]); | | 963 | + CHECK(args[0]->IsUint32()); | | 964 | + uint32_t profile_id = args[0]->Uint32Value(env->context()).FromJust(); | 970 | 965 |
| 971 | 966 | AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); | 972 | 967 | Local<Object> wrap; | @@ -980,11 +975,11 @@ void Worker::StopCpuProfile(const FunctionCallbackInfo<Value>& args) { | 980 | 975 | MakeDetachedBaseObject<WorkerCpuProfileTaker>(env, wrap); | 981 | 976 |
| 982 | 977 | bool scheduled = w->RequestInterrupt([taker = std::move(taker), | 983 | | - name = name.ToString(), | | 978 | + profile_id = profile_id, | 984 | 979 | env](Environment* worker_env) mutable { | 985 | 980 | bool found = false; | 986 | 981 | auto json_out_stream = std::make_unique<node::JSONOutputStream>(); | 987 | | - CpuProfile* profile = worker_env->StopCpuProfile(name); | | 982 | + CpuProfile* profile = worker_env->StopCpuProfile(profile_id); | 988 | 983 | if (profile) { | 989 | 984 | profile->Serialize(json_out_stream.get(), | 990 | 985 | CpuProfile::SerializationFormat::kJSON); | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-worker-cpu-profile.js +6-36Lines changed: 6 additions & 36 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -8,40 +8,18 @@ const worker = new Worker(` | 8 | 8 | parentPort.on('message', () => {}); | 9 | 9 | `, { eval: true }); | 10 | 10 |
| 11 | | -[ | 12 | | - -1, | 13 | | - 1.1, | 14 | | - NaN, | 15 | | - undefined, | 16 | | - {}, | 17 | | - [], | 18 | | - null, | 19 | | - function() {}, | 20 | | - Symbol(), | 21 | | - true, | 22 | | - Infinity, | 23 | | -].forEach((name) => { | 24 | | - try { | 25 | | - worker.startCpuProfile(name); | 26 | | - } catch (e) { | 27 | | - assert.ok(/ERR_INVALID_ARG_TYPE/i.test(e.code)); | 28 | | - } | 29 | | -}); | 30 | | - | 31 | | -const name = 'demo'; | 32 | | - | 33 | 11 | worker.on('online', common.mustCall(async () => { | 34 | 12 | { | 35 | | - const handle = await worker.startCpuProfile(name); | | 13 | + const handle = await worker.startCpuProfile(); | 36 | 14 | JSON.parse(await handle.stop()); | 37 | 15 | // Stop again | 38 | 16 | JSON.parse(await handle.stop()); | 39 | 17 | } | 40 | 18 |
| 41 | 19 | { | 42 | 20 | const [handle1, handle2] = await Promise.all([ | 43 | | - worker.startCpuProfile('demo1'), | 44 | | - worker.startCpuProfile('demo2'), | | 21 | + worker.startCpuProfile(), | | 22 | + worker.startCpuProfile(), | 45 | 23 | ]); | 46 | 24 | const [profile1, profile2] = await Promise.all([ | 47 | 25 | handle1.stop(), | @@ -52,22 +30,14 @@ worker.on('online', common.mustCall(async () => { | 52 | 30 | } | 53 | 31 |
| 54 | 32 | { | 55 | | - // Calling startCpuProfile twice with same name will throw an error | 56 | | - await worker.startCpuProfile(name); | 57 | | - try { | 58 | | - await worker.startCpuProfile(name); | 59 | | - } catch (e) { | 60 | | - assert.ok(/ERR_CPU_PROFILE_ALREADY_STARTED/i.test(e.code)); | 61 | | - } | 62 | | - // Does not need to stop the profile because it will be stopped | 63 | | - // automatically when the worker is terminated | | 33 | + await worker.startCpuProfile(); | | 34 | + // It will be stopped automatically when the worker is terminated | 64 | 35 | } | 65 | | - | 66 | 36 | worker.terminate(); | 67 | 37 | })); | 68 | 38 |
| 69 | 39 | worker.once('exit', common.mustCall(async () => { | 70 | | - await assert.rejects(worker.startCpuProfile(name), { | | 40 | + await assert.rejects(worker.startCpuProfile(), { | 71 | 41 | code: 'ERR_WORKER_NOT_RUNNING' | 72 | 42 | }); | 73 | 43 | })); |
Collapse file Copy file name to clipboardExpand all lines: tools/doc/type-parser.mjs +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -343,6 +343,7 @@ const customTypesMap = { | 343 | 343 | 'Lock': 'worker_threads.html#class-lock', | 344 | 344 | 'LockManager': 'worker_threads.html#class-lockmanager', | 345 | 345 | 'LockManagerSnapshot': 'https://developer.mozilla.org/en-US/docs/Web/API/LockManagerSnapshot', | | 346 | + 'CPUProfileHandle': 'v8.html#class-cpuprofilehandle', | 346 | 347 | }; | 347 | 348 |
| 348 | 349 | const arrayPart = /(?:\[])+$/; | |
|