Skip to content

Commit 177d5dc

Browse files
committed
worker: optimize cpu profile implement
1 parent fb614c4 commit 177d5dc

File tree

10 files changed

+119
-100
lines changed

10 files changed

+119
-100
lines changed

doc/api/v8.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,6 +1398,33 @@ setTimeout(() => {
13981398
}, 1000);
13991399
```
14001400

1401+
## Class: `CPUProfileHandle`
1402+
1403+
<!-- YAML
1404+
added: REPLACEME
1405+
-->
1406+
1407+
### `cpuProfileHandle.stop()`
1408+
1409+
<!-- YAML
1410+
added: REPLACEME
1411+
-->
1412+
1413+
* Returns: {Promise}
1414+
1415+
Stopping collecting the profile, then return a Promise that fulfills with an error or the
1416+
profile data.
1417+
1418+
### `cpuProfileHandle[Symbol.asyncDispose]()`
1419+
1420+
<!-- YAML
1421+
added: REPLACEME
1422+
-->
1423+
1424+
* Returns: {Promise}
1425+
1426+
Stopping collecting the profile and the profile will be discarded.
1427+
14011428
## `v8.isStringOneByteRepresentation(content)`
14021429

14031430
<!-- YAML

doc/api/worker_threads.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1958,19 +1958,16 @@ this matches its values.
19581958
19591959
If the worker has stopped, the return value is an empty object.
19601960
1961-
### `worker.startCpuProfile(name)`
1961+
### `worker.startCpuProfile()`
19621962
19631963
<!-- YAML
19641964
added: REPLACEME
19651965
-->
19661966
1967-
* name: {string}
19681967
* Returns: {Promise}
19691968
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.
1969+
Starting a CPU profile then return a Promise that fulfills with an error
1970+
or an `CPUProfileHandle` object. This API supports `await using` syntax.
19741971
19751972
```cjs
19761973
const { Worker } = require('node:worker_threads');
@@ -1981,13 +1978,29 @@ const worker = new Worker(`
19811978
`, { eval: true });
19821979
19831980
worker.on('online', async () => {
1984-
const handle = await worker.startCpuProfile('demo');
1981+
const handle = await worker.startCpuProfile();
19851982
const profile = await handle.stop();
19861983
console.log(profile);
19871984
worker.terminate();
19881985
});
19891986
```
19901987
1988+
`await using` example.
1989+
1990+
```cjs
1991+
const { Worker } = require('node::worker_threads');
1992+
1993+
const w = new Worker(`
1994+
const { parentPort } = require('worker_threads');
1995+
parentPort.on('message', () => {});
1996+
`, { eval: true });
1997+
1998+
w.on('online', async () => {
1999+
// Stop profile automatically when return and profile will be discarded
2000+
await using handle = await w.startCpuProfile();
2001+
});
2002+
```
2003+
19912004
### `worker.stderr`
19922005
19932006
<!-- YAML

lib/internal/worker.js

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,37 @@ function assignEnvironmentData(data) {
134134
});
135135
}
136136

137+
class CPUProfileHandle {
138+
#worker = null;
139+
#id = null;
140+
#promise = null;
141+
142+
constructor(worker, id) {
143+
this.#worker = worker;
144+
this.#id = id;
145+
}
146+
147+
stop() {
148+
if (this.#promise) {
149+
return this.#promise;
150+
}
151+
const stopTaker = this.#worker[kHandle]?.stopCpuProfile(this.#id);
152+
return this.#promise = new Promise((resolve, reject) => {
153+
if (!stopTaker) return reject(new ERR_WORKER_NOT_RUNNING());
154+
stopTaker.ondone = (err, profile) => {
155+
if (err) {
156+
return reject(err);
157+
}
158+
resolve(profile);
159+
};
160+
});
161+
};
162+
163+
async [SymbolAsyncDispose]() {
164+
await this.stop();
165+
}
166+
}
167+
137168
class Worker extends EventEmitter {
138169
constructor(filename, options = kEmptyObject) {
139170
throwIfBuildingSnapshot('Creating workers');
@@ -508,37 +539,15 @@ class Worker extends EventEmitter {
508539
}
509540

510541
// TODO(theanarkh): add options, such as sample_interval, CpuProfilingMode
511-
startCpuProfile(name) {
512-
validateString(name, 'name');
513-
const startTaker = this[kHandle]?.startCpuProfile(name);
542+
startCpuProfile() {
543+
const startTaker = this[kHandle]?.startCpuProfile();
514544
return new Promise((resolve, reject) => {
515545
if (!startTaker) return reject(new ERR_WORKER_NOT_RUNNING());
516-
startTaker.ondone = (err) => {
546+
startTaker.ondone = (err, id) => {
517547
if (err) {
518548
return reject(err);
519549
}
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-
});
550+
resolve(new CPUProfileHandle(this, id));
542551
};
543552
});
544553
}

src/env.cc

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,7 +1064,7 @@ Environment::~Environment() {
10641064
delete external_memory_accounter_;
10651065
if (cpu_profiler_) {
10661066
for (auto& it : pending_profiles_) {
1067-
cpu_profiler_->Stop(it.second);
1067+
cpu_profiler_->Stop(it);
10681068
}
10691069
cpu_profiler_->Dispose();
10701070
cpu_profiler_ = nullptr;
@@ -2233,30 +2233,30 @@ void Environment::RunWeakRefCleanup() {
22332233
isolate()->ClearKeptObjects();
22342234
}
22352235

2236-
v8::CpuProfilingResult Environment::StartCpuProfile(std::string_view name) {
2236+
v8::CpuProfilingResult Environment::StartCpuProfile() {
22372237
HandleScope handle_scope(isolate());
22382238
if (!cpu_profiler_) {
22392239
cpu_profiler_ = v8::CpuProfiler::New(isolate());
22402240
}
2241-
Local<Value> title =
2242-
node::ToV8Value(context(), name, isolate()).ToLocalChecked();
2243-
v8::CpuProfilingResult result =
2244-
cpu_profiler_->Start(title.As<String>(), true);
2241+
v8::CpuProfilingResult result = cpu_profiler_->Start(
2242+
v8::CpuProfilingOptions{v8::CpuProfilingMode::kLeafNodeLineNumbers,
2243+
v8::CpuProfilingOptions::kNoSampleLimit});
22452244
if (result.status == v8::CpuProfilingStatus::kStarted) {
2246-
pending_profiles_.emplace(name, result.id);
2245+
pending_profiles_.push_back(result.id);
22472246
}
22482247
return result;
22492248
}
22502249

2251-
v8::CpuProfile* Environment::StopCpuProfile(std::string_view name) {
2250+
v8::CpuProfile* Environment::StopCpuProfile(v8::ProfilerId profile_id) {
22522251
if (!cpu_profiler_) {
22532252
return nullptr;
22542253
}
2255-
auto it = pending_profiles_.find(std::string(name));
2254+
auto it =
2255+
std::find(pending_profiles_.begin(), pending_profiles_.end(), profile_id);
22562256
if (it == pending_profiles_.end()) {
22572257
return nullptr;
22582258
}
2259-
v8::CpuProfile* profile = cpu_profiler_->Stop(it->second);
2259+
v8::CpuProfile* profile = cpu_profiler_->Stop(*it);
22602260
pending_profiles_.erase(it);
22612261
return profile;
22622262
}

src/env.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,8 +1049,8 @@ class Environment final : public MemoryRetainer {
10491049

10501050
inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit);
10511051

1052-
v8::CpuProfilingResult StartCpuProfile(std::string_view name);
1053-
v8::CpuProfile* StopCpuProfile(std::string_view name);
1052+
v8::CpuProfilingResult StartCpuProfile();
1053+
v8::CpuProfile* StopCpuProfile(v8::ProfilerId profile_id);
10541054

10551055
// Field identifiers for exit_info_
10561056
enum ExitInfoField {
@@ -1250,7 +1250,7 @@ class Environment final : public MemoryRetainer {
12501250
released_allocated_buffers_;
12511251

12521252
v8::CpuProfiler* cpu_profiler_ = nullptr;
1253-
std::unordered_map<std::string, v8::ProfilerId> pending_profiles_;
1253+
std::vector<v8::ProfilerId> pending_profiles_;
12541254
};
12551255

12561256
} // namespace node

src/node_errors.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ 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) \
5251
V(ERR_CPU_PROFILE_NOT_STARTED, Error) \
5352
V(ERR_CPU_PROFILE_TOO_MANY, Error) \
5453
V(ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, Error) \

src/node_worker.cc

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -915,9 +915,6 @@ void Worker::StartCpuProfile(const FunctionCallbackInfo<Value>& args) {
915915
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
916916
Environment* env = w->env();
917917

918-
CHECK(args[0]->IsString());
919-
node::Utf8Value name(env->isolate(), args[0]);
920-
921918
AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w);
922919
Local<Object> wrap;
923920
if (!env->worker_cpu_profile_taker_template()
@@ -930,25 +927,23 @@ void Worker::StartCpuProfile(const FunctionCallbackInfo<Value>& args) {
930927
MakeDetachedBaseObject<WorkerCpuProfileTaker>(env, wrap);
931928

932929
bool scheduled = w->RequestInterrupt([taker = std::move(taker),
933-
name = name.ToString(),
934930
env](Environment* worker_env) mutable {
935-
CpuProfilingResult result = worker_env->StartCpuProfile(name);
931+
CpuProfilingResult result = worker_env->StartCpuProfile();
936932
env->SetImmediateThreadsafe(
937-
[taker = std::move(taker),
938-
status = result.status](Environment* env) mutable {
933+
[taker = std::move(taker), result = result](Environment* env) mutable {
939934
Isolate* isolate = env->isolate();
940935
HandleScope handle_scope(isolate);
941936
Context::Scope context_scope(env->context());
942937
AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get());
943938
Local<Value> argv[] = {
944-
Null(isolate), // error
939+
Null(isolate), // error
940+
Undefined(isolate), // profile id
945941
};
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) {
950943
argv[0] = ERR_CPU_PROFILE_TOO_MANY(
951944
isolate, "There are too many CPU profiles");
945+
} else if (result.status == CpuProfilingStatus::kStarted) {
946+
argv[1] = Number::New(isolate, result.id);
952947
}
953948
taker->MakeCallback(env->ondone_string(), arraysize(argv), argv);
954949
},
@@ -965,8 +960,8 @@ void Worker::StopCpuProfile(const FunctionCallbackInfo<Value>& args) {
965960
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
966961

967962
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();
970965

971966
AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w);
972967
Local<Object> wrap;
@@ -980,11 +975,11 @@ void Worker::StopCpuProfile(const FunctionCallbackInfo<Value>& args) {
980975
MakeDetachedBaseObject<WorkerCpuProfileTaker>(env, wrap);
981976

982977
bool scheduled = w->RequestInterrupt([taker = std::move(taker),
983-
name = name.ToString(),
978+
profile_id = profile_id,
984979
env](Environment* worker_env) mutable {
985980
bool found = false;
986981
auto json_out_stream = std::make_unique<node::JSONOutputStream>();
987-
CpuProfile* profile = worker_env->StopCpuProfile(name);
982+
CpuProfile* profile = worker_env->StopCpuProfile(profile_id);
988983
if (profile) {
989984
profile->Serialize(json_out_stream.get(),
990985
CpuProfile::SerializationFormat::kJSON);

test/parallel/test-worker-cpu-profile.js

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,18 @@ const worker = new Worker(`
88
parentPort.on('message', () => {});
99
`, { eval: true });
1010

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-
3311
worker.on('online', common.mustCall(async () => {
3412
{
35-
const handle = await worker.startCpuProfile(name);
13+
const handle = await worker.startCpuProfile();
3614
JSON.parse(await handle.stop());
3715
// Stop again
3816
JSON.parse(await handle.stop());
3917
}
4018

4119
{
4220
const [handle1, handle2] = await Promise.all([
43-
worker.startCpuProfile('demo1'),
44-
worker.startCpuProfile('demo2'),
21+
worker.startCpuProfile(),
22+
worker.startCpuProfile(),
4523
]);
4624
const [profile1, profile2] = await Promise.all([
4725
handle1.stop(),
@@ -52,22 +30,14 @@ worker.on('online', common.mustCall(async () => {
5230
}
5331

5432
{
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
6435
}
65-
6636
worker.terminate();
6737
}));
6838

6939
worker.once('exit', common.mustCall(async () => {
70-
await assert.rejects(worker.startCpuProfile(name), {
40+
await assert.rejects(worker.startCpuProfile(), {
7141
code: 'ERR_WORKER_NOT_RUNNING'
7242
});
7343
}));

tools/doc/type-parser.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ const customTypesMap = {
344344
'Lock': 'worker_threads.html#class-lock',
345345
'LockManager': 'worker_threads.html#class-lockmanager',
346346
'LockManagerSnapshot': 'https://developer.mozilla.org/en-US/docs/Web/API/LockManagerSnapshot',
347+
'CPUProfileHandle': 'v8.html#class-cpuprofilehandle',
347348
};
348349

349350
const arrayPart = /(?:\[])+$/;

0 commit comments

Comments
 (0)