Skip to content

Commit 75c53a8

Browse files
committed
worker: optimize cpu profile implement
1 parent fb614c4 commit 75c53a8

File tree

9 files changed

+93
-108
lines changed

9 files changed

+93
-108
lines changed

doc/api/errors.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -826,16 +826,6 @@ 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-
839829
<a id="ERR_CPU_PROFILE_NOT_STARTED"></a>
840830

841831
### `ERR_CPU_PROFILE_NOT_STARTED`

doc/api/worker_threads.md

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,23 @@ Calling `unref()` on a BroadcastChannel allows the thread to exit if this
10381038
is the only active handle in the event system. If the BroadcastChannel is
10391039
already `unref()`ed calling `unref()` again has no effect.
10401040
1041+
## Class: `CPUProfileHandle`
1042+
1043+
<!-- YAML
1044+
added: REPLACEME
1045+
-->
1046+
1047+
### `cpuProfileHandle.stop()`
1048+
1049+
<!-- YAML
1050+
added: REPLACEME
1051+
-->
1052+
1053+
* Returns: {Promise}
1054+
1055+
Stopping collecting the profile, then return a Promise that fulfills with an error or the
1056+
profile data.
1057+
10411058
## Class: `MessageChannel`
10421059
10431060
<!-- YAML
@@ -1958,19 +1975,16 @@ this matches its values.
19581975
19591976
If the worker has stopped, the return value is an empty object.
19601977
1961-
### `worker.startCpuProfile(name)`
1978+
### `worker.startCpuProfile()`
19621979
19631980
<!-- YAML
19641981
added: REPLACEME
19651982
-->
19661983
1967-
* name: {string}
19681984
* Returns: {Promise}
19691985
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.
1986+
Starting a CPU profile then return a Promise that fulfills with an error
1987+
or an [`CPUProfileHandle`][] object.
19741988
19751989
```cjs
19761990
const { Worker } = require('node:worker_threads');
@@ -1981,7 +1995,7 @@ const worker = new Worker(`
19811995
`, { eval: true });
19821996
19831997
worker.on('online', async () => {
1984-
const handle = await worker.startCpuProfile('demo');
1998+
const handle = await worker.startCpuProfile();
19851999
const profile = await handle.stop();
19862000
console.log(profile);
19872001
worker.terminate();
@@ -2162,6 +2176,7 @@ thread spawned will spawn another until the application crashes.
21622176
[`--max-semi-space-size`]: cli.md#--max-semi-space-sizesize-in-mib
21632177
[`AsyncResource`]: async_hooks.md#class-asyncresource
21642178
[`Buffer.allocUnsafe()`]: buffer.md#static-method-bufferallocunsafesize
2179+
[`CPUProfileHandle`]: #workercpuprofilehandle
21652180
[`ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST`]: errors.md#err_missing_message_port_in_transfer_list
21662181
[`ERR_WORKER_MESSAGING_ERRORED`]: errors.md#err_worker_messaging_errored
21672182
[`ERR_WORKER_MESSAGING_FAILED`]: errors.md#err_worker_messaging_failed

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: 12 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,32 @@ 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(v8::CpuProfilingOptions{
2242+
v8::CpuProfilingMode::kLeafNodeLineNumbers,
2243+
v8::CpuProfilingOptions::kNoSampleLimit
2244+
});
22452245
if (result.status == v8::CpuProfilingStatus::kStarted) {
2246-
pending_profiles_.emplace(name, result.id);
2246+
pending_profiles_.push_back(result.id);
22472247
}
22482248
return result;
22492249
}
22502250

2251-
v8::CpuProfile* Environment::StopCpuProfile(std::string_view name) {
2251+
v8::CpuProfile* Environment::StopCpuProfile(v8::ProfilerId profile_id) {
22522252
if (!cpu_profiler_) {
22532253
return nullptr;
22542254
}
2255-
auto it = pending_profiles_.find(std::string(name));
2255+
auto it = std::find(pending_profiles_.begin(),
2256+
pending_profiles_.end(),
2257+
profile_id);
22562258
if (it == pending_profiles_.end()) {
22572259
return nullptr;
22582260
}
2259-
v8::CpuProfile* profile = cpu_profiler_->Stop(it->second);
2261+
v8::CpuProfile* profile = cpu_profiler_->Stop(*it);
22602262
pending_profiles_.erase(it);
22612263
return profile;
22622264
}

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: 10 additions & 14 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,24 @@ 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(
937933
[taker = std::move(taker),
938-
status = result.status](Environment* env) mutable {
934+
result = result](Environment* env) mutable {
939935
Isolate* isolate = env->isolate();
940936
HandleScope handle_scope(isolate);
941937
Context::Scope context_scope(env->context());
942938
AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get());
943939
Local<Value> argv[] = {
944940
Null(isolate), // error
941+
Undefined(isolate), // profile id
945942
};
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) {
943+
if (result.status == CpuProfilingStatus::kErrorTooManyProfilers) {
950944
argv[0] = ERR_CPU_PROFILE_TOO_MANY(
951945
isolate, "There are too many CPU profiles");
946+
} else if (result.status == CpuProfilingStatus::kStarted) {
947+
argv[1] = Number::New(isolate, static_cast<uint32_t>(result.id));
952948
}
953949
taker->MakeCallback(env->ondone_string(), arraysize(argv), argv);
954950
},
@@ -965,8 +961,8 @@ void Worker::StopCpuProfile(const FunctionCallbackInfo<Value>& args) {
965961
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
966962

967963
Environment* env = w->env();
968-
CHECK(args[0]->IsString());
969-
node::Utf8Value name(env->isolate(), args[0]);
964+
CHECK(args[0]->IsUint32());
965+
uint32_t profile_id = args[0]->Uint32Value(env->context()).FromJust();
970966

971967
AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w);
972968
Local<Object> wrap;
@@ -980,11 +976,11 @@ void Worker::StopCpuProfile(const FunctionCallbackInfo<Value>& args) {
980976
MakeDetachedBaseObject<WorkerCpuProfileTaker>(env, wrap);
981977

982978
bool scheduled = w->RequestInterrupt([taker = std::move(taker),
983-
name = name.ToString(),
979+
profile_id = profile_id,
984980
env](Environment* worker_env) mutable {
985981
bool found = false;
986982
auto json_out_stream = std::make_unique<node::JSONOutputStream>();
987-
CpuProfile* profile = worker_env->StopCpuProfile(name);
983+
CpuProfile* profile = worker_env->StopCpuProfile(profile_id);
988984
if (profile) {
989985
profile->Serialize(json_out_stream.get(),
990986
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
}));

typings/internalBinding/worker.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ declare namespace InternalWorkerBinding {
1717
takeHeapSnapshot(): object;
1818
getHeapStatistics(): Promise<object>;
1919
cpuUsage(): Promise<object>;
20-
startCpuProfile(name): Promise<object>;
20+
startCpuProfile(): Promise<CPUProfileHandle>;
2121
loopIdleTime(): number;
2222
loopStartTime(): number;
2323
}
2424
}
2525

26+
export interface CPUProfileHandle {
27+
stop(): Promise<string>;
28+
}
29+
2630
export interface WorkerBinding {
2731
Worker: typeof InternalWorkerBinding.Worker;
2832
getEnvMessagePort(): InternalMessagingBinding.MessagePort;

0 commit comments

Comments
 (0)