◐ Shell
clean mode source ↗

worker: add cpu profile APIs for worker · nodejs/node@d6d05ba

Original file line numberDiff line numberDiff line change

@@ -826,6 +826,36 @@ 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+
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+
829859

<a id="ERR_CRYPTO_ARGON2_NOT_SUPPORTED"></a>

830860
831861

### `ERR_CRYPTO_ARGON2_NOT_SUPPORTED`

Original file line numberDiff line numberDiff line change

@@ -1953,6 +1953,36 @@ this matches its values.

19531953
19541954

If the worker has stopped, the return value is an empty object.

19551955
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+
19561986

### `worker.stderr`

19571987
19581988

<!-- YAML

Original file line numberDiff line numberDiff line change

@@ -514,6 +514,42 @@ class Worker extends EventEmitter {

514514

};

515515

});

516516

}

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+

}

517553

}

518554
519555

/**

Original file line numberDiff line numberDiff line change

@@ -79,6 +79,7 @@ namespace node {

7979

V(UDPWRAP) \

8080

V(SIGINTWATCHDOG) \

8181

V(WORKER) \

82+

V(WORKERCPUPROFILE) \

8283

V(WORKERCPUUSAGE) \

8384

V(WORKERHEAPSNAPSHOT) \

8485

V(WORKERHEAPSTATISTICS) \

Original file line numberDiff line numberDiff line change

@@ -1058,6 +1058,13 @@ Environment::~Environment() {

10581058

}

10591059
10601060

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+

}

10611068

}

10621069
10631070

void Environment::InitializeLibuv() {

@@ -2221,4 +2228,33 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {

22212228

void Environment::RunWeakRefCleanup() {

22222229

isolate()->ClearKeptObjects();

22232230

}

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+
22242260

} // namespace node

Original file line numberDiff line numberDiff line change

@@ -49,6 +49,7 @@

4949

#include "util.h"

5050

#include "uv.h"

5151

#include "v8-external-memory-accounter.h"

52+

#include "v8-profiler.h"

5253

#include "v8.h"

5354
5455

#if HAVE_OPENSSL

@@ -1051,6 +1052,9 @@ class Environment final : public MemoryRetainer {

10511052
10521053

inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit);

10531054
1055+

v8::CpuProfilingResult StartCpuProfile(std::string_view name);

1056+

v8::CpuProfile* StopCpuProfile(std::string_view name);

1057+
10541058

// Field identifiers for exit_info_

10551059

enum ExitInfoField {

10561060

kExiting = 0,

@@ -1248,6 +1252,9 @@ class Environment final : public MemoryRetainer {

12481252

// track of the BackingStore for a given pointer.

12491253

std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>

12501254

released_allocated_buffers_;

1255+
1256+

v8::CpuProfiler* cpu_profiler_ = nullptr;

1257+

std::unordered_map<std::string, v8::ProfilerId> pending_profiles_;

12511258

};

12521259
12531260

} // namespace node

Original file line numberDiff line numberDiff line change

@@ -498,6 +498,7 @@

498498

V(tcp_constructor_template, v8::FunctionTemplate) \

499499

V(tty_constructor_template, v8::FunctionTemplate) \

500500

V(write_wrap_template, v8::ObjectTemplate) \

501+

V(worker_cpu_profile_taker_template, v8::ObjectTemplate) \

501502

V(worker_cpu_usage_taker_template, v8::ObjectTemplate) \

502503

V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \

503504

V(worker_heap_statistics_taker_template, v8::ObjectTemplate) \

Original file line numberDiff line numberDiff line change

@@ -48,6 +48,9 @@ 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) \

52+

V(ERR_CPU_PROFILE_NOT_STARTED, Error) \

53+

V(ERR_CPU_PROFILE_TOO_MANY, Error) \

5154

V(ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, Error) \

5255

V(ERR_CRYPTO_INITIALIZATION_FAILED, Error) \

5356

V(ERR_CRYPTO_INVALID_ARGON2_PARAMS, TypeError) \