Skip to content

Commit 2e54411

Browse files
theanarkhtargos
authored andcommitted
worker: optimize cpu profile implement
PR-URL: #59683 Reviewed-By: Juan José Arboleda <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 15cbd39 commit 2e54411

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
@@ -1394,6 +1394,33 @@ setTimeout(() => {
13941394
}, 1000);
13951395
```
13961396

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+
13971424
## `v8.isStringOneByteRepresentation(content)`
13981425

13991426
<!-- YAML

doc/api/worker_threads.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,19 +1953,16 @@ this matches its values.
19531953
19541954
If the worker has stopped, the return value is an empty object.
19551955
1956-
### `worker.startCpuProfile(name)`
1956+
### `worker.startCpuProfile()`
19571957
19581958
<!-- YAML
19591959
added: REPLACEME
19601960
-->
19611961
1962-
* name: {string}
19631962
* Returns: {Promise}
19641963
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.
19691966
19701967
```cjs
19711968
const { Worker } = require('node:worker_threads');
@@ -1976,13 +1973,29 @@ const worker = new Worker(`
19761973
`, { eval: true });
19771974
19781975
worker.on('online', async () => {
1979-
const handle = await worker.startCpuProfile('demo');
1976+
const handle = await worker.startCpuProfile();
19801977
const profile = await handle.stop();
19811978
console.log(profile);
19821979
worker.terminate();
19831980
});
19841981
```
19851982
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+
19861999
### `worker.stderr`
19872000
19882001
<!-- YAML

lib/internal/worker.js

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

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+
136167
class Worker extends EventEmitter {
137168
constructor(filename, options = kEmptyObject) {
138169
throwIfBuildingSnapshot('Creating workers');
@@ -516,37 +547,15 @@ class Worker extends EventEmitter {
516547
}
517548

518549
// 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();
522552
return new Promise((resolve, reject) => {
523553
if (!startTaker) return reject(new ERR_WORKER_NOT_RUNNING());
524-
startTaker.ondone = (err) => {
554+
startTaker.ondone = (err, id) => {
525555
if (err) {
526556
return reject(err);
527557
}
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));
550559
};
551560
});
552561
}

src/env.cc

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,7 +1060,7 @@ Environment::~Environment() {
10601060
delete external_memory_accounter_;
10611061
if (cpu_profiler_) {
10621062
for (auto& it : pending_profiles_) {
1063-
cpu_profiler_->Stop(it.second);
1063+
cpu_profiler_->Stop(it);
10641064
}
10651065
cpu_profiler_->Dispose();
10661066
cpu_profiler_ = nullptr;
@@ -2229,30 +2229,30 @@ void Environment::RunWeakRefCleanup() {
22292229
isolate()->ClearKeptObjects();
22302230
}
22312231

2232-
v8::CpuProfilingResult Environment::StartCpuProfile(std::string_view name) {
2232+
v8::CpuProfilingResult Environment::StartCpuProfile() {
22332233
HandleScope handle_scope(isolate());
22342234
if (!cpu_profiler_) {
22352235
cpu_profiler_ = v8::CpuProfiler::New(isolate());
22362236
}
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});
22412240
if (result.status == v8::CpuProfilingStatus::kStarted) {
2242-
pending_profiles_.emplace(name, result.id);
2241+
pending_profiles_.push_back(result.id);
22432242
}
22442243
return result;
22452244
}
22462245

2247-
v8::CpuProfile* Environment::StopCpuProfile(std::string_view name) {
2246+
v8::CpuProfile* Environment::StopCpuProfile(v8::ProfilerId profile_id) {
22482247
if (!cpu_profiler_) {
22492248
return nullptr;
22502249
}
2251-
auto it = pending_profiles_.find(std::string(name));
2250+
auto it =
2251+
std::find(pending_profiles_.begin(), pending_profiles_.end(), profile_id);
22522252
if (it == pending_profiles_.end()) {
22532253
return nullptr;
22542254
}
2255-
v8::CpuProfile* profile = cpu_profiler_->Stop(it->second);
2255+
v8::CpuProfile* profile = cpu_profiler_->Stop(*it);
22562256
pending_profiles_.erase(it);
22572257
return profile;
22582258
}

src/env.h

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

10531053
inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit);
10541054

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);
10571057

10581058
// Field identifiers for exit_info_
10591059
enum ExitInfoField {
@@ -1254,7 +1254,7 @@ class Environment final : public MemoryRetainer {
12541254
released_allocated_buffers_;
12551255

12561256
v8::CpuProfiler* cpu_profiler_ = nullptr;
1257-
std::unordered_map<std::string, v8::ProfilerId> pending_profiles_;
1257+
std::vector<v8::ProfilerId> pending_profiles_;
12581258
};
12591259

12601260
} // 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
@@ -343,6 +343,7 @@ const customTypesMap = {
343343
'Lock': 'worker_threads.html#class-lock',
344344
'LockManager': 'worker_threads.html#class-lockmanager',
345345
'LockManagerSnapshot': 'https://developer.mozilla.org/en-US/docs/Web/API/LockManagerSnapshot',
346+
'CPUProfileHandle': 'v8.html#class-cpuprofilehandle',
346347
};
347348

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

0 commit comments

Comments
 (0)