mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
worker: add cpuUsage for worker
PR-URL: https://github.com/nodejs/node/pull/59177 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ilyas Shabi <ilyasshabi94@gmail.com>
This commit is contained in:
parent
0bbe7c36c9
commit
0ba6e0d7ce
|
|
@ -1760,6 +1760,19 @@ added: v10.5.0
|
||||||
The `'online'` event is emitted when the worker thread has started executing
|
The `'online'` event is emitted when the worker thread has started executing
|
||||||
JavaScript code.
|
JavaScript code.
|
||||||
|
|
||||||
|
### `worker.cpuUsage([prev])`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added:
|
||||||
|
- REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {Promise}
|
||||||
|
|
||||||
|
This method returns a `Promise` that will resolve to an object identical to [`process.threadCpuUsage()`][],
|
||||||
|
or reject with an [`ERR_WORKER_NOT_RUNNING`][] error if the worker is no longer running.
|
||||||
|
This methods allows the statistics to be observed from outside the actual thread.
|
||||||
|
|
||||||
### `worker.getHeapSnapshot([options])`
|
### `worker.getHeapSnapshot([options])`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
|
@ -2123,6 +2136,7 @@ thread spawned will spawn another until the application crashes.
|
||||||
[`process.stderr`]: process.md#processstderr
|
[`process.stderr`]: process.md#processstderr
|
||||||
[`process.stdin`]: process.md#processstdin
|
[`process.stdin`]: process.md#processstdin
|
||||||
[`process.stdout`]: process.md#processstdout
|
[`process.stdout`]: process.md#processstdout
|
||||||
|
[`process.threadCpuUsage()`]: process.md#processthreadcpuusagepreviousvalue
|
||||||
[`process.title`]: process.md#processtitle
|
[`process.title`]: process.md#processtitle
|
||||||
[`require('node:worker_threads').isMainThread`]: #workerismainthread
|
[`require('node:worker_threads').isMainThread`]: #workerismainthread
|
||||||
[`require('node:worker_threads').parentPort.on('message')`]: #event-message
|
[`require('node:worker_threads').parentPort.on('message')`]: #event-message
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const {
|
||||||
Float64Array,
|
Float64Array,
|
||||||
FunctionPrototypeBind,
|
FunctionPrototypeBind,
|
||||||
MathMax,
|
MathMax,
|
||||||
|
NumberMAX_SAFE_INTEGER,
|
||||||
ObjectEntries,
|
ObjectEntries,
|
||||||
Promise,
|
Promise,
|
||||||
PromiseResolve,
|
PromiseResolve,
|
||||||
|
|
@ -41,6 +42,7 @@ const {
|
||||||
ERR_WORKER_INVALID_EXEC_ARGV,
|
ERR_WORKER_INVALID_EXEC_ARGV,
|
||||||
ERR_INVALID_ARG_TYPE,
|
ERR_INVALID_ARG_TYPE,
|
||||||
ERR_INVALID_ARG_VALUE,
|
ERR_INVALID_ARG_VALUE,
|
||||||
|
ERR_OPERATION_FAILED,
|
||||||
} = errorCodes;
|
} = errorCodes;
|
||||||
|
|
||||||
const workerIo = require('internal/worker/io');
|
const workerIo = require('internal/worker/io');
|
||||||
|
|
@ -61,7 +63,7 @@ const { createMainThreadPort, destroyMainThreadPort } = require('internal/worker
|
||||||
const { deserializeError } = require('internal/error_serdes');
|
const { deserializeError } = require('internal/error_serdes');
|
||||||
const { fileURLToPath, isURL, pathToFileURL } = require('internal/url');
|
const { fileURLToPath, isURL, pathToFileURL } = require('internal/url');
|
||||||
const { kEmptyObject } = require('internal/util');
|
const { kEmptyObject } = require('internal/util');
|
||||||
const { validateArray, validateString } = require('internal/validators');
|
const { validateArray, validateString, validateObject, validateNumber } = require('internal/validators');
|
||||||
const {
|
const {
|
||||||
throwIfBuildingSnapshot,
|
throwIfBuildingSnapshot,
|
||||||
} = require('internal/v8/startup_snapshot');
|
} = require('internal/v8/startup_snapshot');
|
||||||
|
|
@ -466,6 +468,37 @@ class Worker extends EventEmitter {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cpuUsage(prev) {
|
||||||
|
if (prev) {
|
||||||
|
validateObject(prev, 'prev');
|
||||||
|
validateNumber(prev.user, 'prev.user', 0, NumberMAX_SAFE_INTEGER);
|
||||||
|
validateNumber(prev.system, 'prev.system', 0, NumberMAX_SAFE_INTEGER);
|
||||||
|
}
|
||||||
|
if (process.platform === 'sunos') {
|
||||||
|
throw new ERR_OPERATION_FAILED('worker.cpuUsage() is not available on SunOS');
|
||||||
|
}
|
||||||
|
const taker = this[kHandle]?.cpuUsage();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!taker) return reject(new ERR_WORKER_NOT_RUNNING());
|
||||||
|
taker.ondone = (err, current) => {
|
||||||
|
if (err !== null) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
if (prev) {
|
||||||
|
resolve({
|
||||||
|
user: current.user - prev.user,
|
||||||
|
system: current.system - prev.system,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve({
|
||||||
|
user: current.user,
|
||||||
|
system: current.system,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ namespace node {
|
||||||
V(UDPWRAP) \
|
V(UDPWRAP) \
|
||||||
V(SIGINTWATCHDOG) \
|
V(SIGINTWATCHDOG) \
|
||||||
V(WORKER) \
|
V(WORKER) \
|
||||||
|
V(WORKERCPUUSAGE) \
|
||||||
V(WORKERHEAPSNAPSHOT) \
|
V(WORKERHEAPSNAPSHOT) \
|
||||||
V(WORKERHEAPSTATISTICS) \
|
V(WORKERHEAPSTATISTICS) \
|
||||||
V(WRITEWRAP) \
|
V(WRITEWRAP) \
|
||||||
|
|
|
||||||
|
|
@ -475,6 +475,7 @@
|
||||||
V(tcp_constructor_template, v8::FunctionTemplate) \
|
V(tcp_constructor_template, v8::FunctionTemplate) \
|
||||||
V(tty_constructor_template, v8::FunctionTemplate) \
|
V(tty_constructor_template, v8::FunctionTemplate) \
|
||||||
V(write_wrap_template, v8::ObjectTemplate) \
|
V(write_wrap_template, v8::ObjectTemplate) \
|
||||||
|
V(worker_cpu_usage_taker_template, v8::ObjectTemplate) \
|
||||||
V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \
|
V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \
|
||||||
V(worker_heap_statistics_taker_template, v8::ObjectTemplate) \
|
V(worker_heap_statistics_taker_template, v8::ObjectTemplate) \
|
||||||
V(x509_constructor_template, v8::FunctionTemplate)
|
V(x509_constructor_template, v8::FunctionTemplate)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ using v8::Isolate;
|
||||||
using v8::Local;
|
using v8::Local;
|
||||||
using v8::Locker;
|
using v8::Locker;
|
||||||
using v8::Maybe;
|
using v8::Maybe;
|
||||||
|
using v8::Name;
|
||||||
using v8::Null;
|
using v8::Null;
|
||||||
using v8::Number;
|
using v8::Number;
|
||||||
using v8::Object;
|
using v8::Object;
|
||||||
|
|
@ -810,6 +811,81 @@ void Worker::Unref(const FunctionCallbackInfo<Value>& args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WorkerCpuUsageTaker : public AsyncWrap {
|
||||||
|
public:
|
||||||
|
WorkerCpuUsageTaker(Environment* env, Local<Object> obj)
|
||||||
|
: AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERCPUUSAGE) {}
|
||||||
|
|
||||||
|
SET_NO_MEMORY_INFO()
|
||||||
|
SET_MEMORY_INFO_NAME(WorkerCpuUsageTaker)
|
||||||
|
SET_SELF_SIZE(WorkerCpuUsageTaker)
|
||||||
|
};
|
||||||
|
|
||||||
|
void Worker::CpuUsage(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Worker* w;
|
||||||
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
|
||||||
|
|
||||||
|
Environment* env = w->env();
|
||||||
|
AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w);
|
||||||
|
Local<Object> wrap;
|
||||||
|
if (!env->worker_cpu_usage_taker_template()
|
||||||
|
->NewInstance(env->context())
|
||||||
|
.ToLocal(&wrap)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseObjectPtr<WorkerCpuUsageTaker> taker =
|
||||||
|
MakeDetachedBaseObject<WorkerCpuUsageTaker>(env, wrap);
|
||||||
|
|
||||||
|
bool scheduled = w->RequestInterrupt([taker = std::move(taker),
|
||||||
|
env](Environment* worker_env) mutable {
|
||||||
|
auto cpu_usage_stats = std::make_unique<uv_rusage_t>();
|
||||||
|
int err = uv_getrusage_thread(cpu_usage_stats.get());
|
||||||
|
|
||||||
|
env->SetImmediateThreadsafe(
|
||||||
|
[taker = std::move(taker),
|
||||||
|
cpu_usage_stats = std::move(cpu_usage_stats),
|
||||||
|
err = err](Environment* env) mutable {
|
||||||
|
Isolate* isolate = env->isolate();
|
||||||
|
HandleScope handle_scope(isolate);
|
||||||
|
Context::Scope context_scope(env->context());
|
||||||
|
AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get());
|
||||||
|
|
||||||
|
Local<Value> argv[] = {
|
||||||
|
Null(isolate),
|
||||||
|
Undefined(isolate),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
argv[0] = UVException(
|
||||||
|
isolate, err, "uv_getrusage_thread", nullptr, nullptr, nullptr);
|
||||||
|
} else {
|
||||||
|
Local<Name> names[] = {
|
||||||
|
FIXED_ONE_BYTE_STRING(isolate, "user"),
|
||||||
|
FIXED_ONE_BYTE_STRING(isolate, "system"),
|
||||||
|
};
|
||||||
|
Local<Value> values[] = {
|
||||||
|
Number::New(isolate,
|
||||||
|
1e6 * cpu_usage_stats->ru_utime.tv_sec +
|
||||||
|
cpu_usage_stats->ru_utime.tv_usec),
|
||||||
|
Number::New(isolate,
|
||||||
|
1e6 * cpu_usage_stats->ru_stime.tv_sec +
|
||||||
|
cpu_usage_stats->ru_stime.tv_usec),
|
||||||
|
};
|
||||||
|
argv[1] = Object::New(
|
||||||
|
isolate, Null(isolate), names, values, arraysize(names));
|
||||||
|
}
|
||||||
|
|
||||||
|
taker->MakeCallback(env->ondone_string(), arraysize(argv), argv);
|
||||||
|
},
|
||||||
|
CallbackFlags::kUnrefed);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (scheduled) {
|
||||||
|
args.GetReturnValue().Set(wrap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class WorkerHeapStatisticsTaker : public AsyncWrap {
|
class WorkerHeapStatisticsTaker : public AsyncWrap {
|
||||||
public:
|
public:
|
||||||
WorkerHeapStatisticsTaker(Environment* env, Local<Object> obj)
|
WorkerHeapStatisticsTaker(Environment* env, Local<Object> obj)
|
||||||
|
|
@ -1101,6 +1177,7 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data,
|
||||||
SetProtoMethod(isolate, w, "loopIdleTime", Worker::LoopIdleTime);
|
SetProtoMethod(isolate, w, "loopIdleTime", Worker::LoopIdleTime);
|
||||||
SetProtoMethod(isolate, w, "loopStartTime", Worker::LoopStartTime);
|
SetProtoMethod(isolate, w, "loopStartTime", Worker::LoopStartTime);
|
||||||
SetProtoMethod(isolate, w, "getHeapStatistics", Worker::GetHeapStatistics);
|
SetProtoMethod(isolate, w, "getHeapStatistics", Worker::GetHeapStatistics);
|
||||||
|
SetProtoMethod(isolate, w, "cpuUsage", Worker::CpuUsage);
|
||||||
|
|
||||||
SetConstructorFunction(isolate, target, "Worker", w);
|
SetConstructorFunction(isolate, target, "Worker", w);
|
||||||
}
|
}
|
||||||
|
|
@ -1133,6 +1210,19 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data,
|
||||||
wst->InstanceTemplate());
|
wst->InstanceTemplate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Local<FunctionTemplate> wst = NewFunctionTemplate(isolate, nullptr);
|
||||||
|
|
||||||
|
wst->InstanceTemplate()->SetInternalFieldCount(
|
||||||
|
WorkerCpuUsageTaker::kInternalFieldCount);
|
||||||
|
wst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
|
||||||
|
|
||||||
|
Local<String> wst_string =
|
||||||
|
FIXED_ONE_BYTE_STRING(isolate, "WorkerCpuUsageTaker");
|
||||||
|
wst->SetClassName(wst_string);
|
||||||
|
isolate_data->set_worker_cpu_usage_taker_template(wst->InstanceTemplate());
|
||||||
|
}
|
||||||
|
|
||||||
SetMethod(isolate, target, "getEnvMessagePort", GetEnvMessagePort);
|
SetMethod(isolate, target, "getEnvMessagePort", GetEnvMessagePort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1199,6 +1289,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||||
registry->Register(Worker::LoopIdleTime);
|
registry->Register(Worker::LoopIdleTime);
|
||||||
registry->Register(Worker::LoopStartTime);
|
registry->Register(Worker::LoopStartTime);
|
||||||
registry->Register(Worker::GetHeapStatistics);
|
registry->Register(Worker::GetHeapStatistics);
|
||||||
|
registry->Register(Worker::CpuUsage);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ class Worker : public AsyncWrap {
|
||||||
static void LoopStartTime(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void LoopStartTime(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void GetHeapStatistics(
|
static void GetHeapStatistics(
|
||||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void CpuUsage(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool CreateEnvMessagePort(Environment* env);
|
bool CreateEnvMessagePort(Environment* env);
|
||||||
|
|
|
||||||
81
test/parallel/test-worker-cpu-usage.js
Normal file
81
test/parallel/test-worker-cpu-usage.js
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const { isSunOS } = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const {
|
||||||
|
Worker,
|
||||||
|
} = require('worker_threads');
|
||||||
|
|
||||||
|
function validate(result) {
|
||||||
|
assert.ok(typeof result == 'object' && result !== null);
|
||||||
|
assert.ok(result.user >= 0);
|
||||||
|
assert.ok(result.system >= 0);
|
||||||
|
assert.ok(Number.isFinite(result.user));
|
||||||
|
assert.ok(Number.isFinite(result.system));
|
||||||
|
}
|
||||||
|
|
||||||
|
function check(worker) {
|
||||||
|
[
|
||||||
|
-1,
|
||||||
|
1.1,
|
||||||
|
NaN,
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
function() {},
|
||||||
|
Symbol(),
|
||||||
|
true,
|
||||||
|
Infinity,
|
||||||
|
{ user: -1, system: 1 },
|
||||||
|
{ user: 1, system: -1 },
|
||||||
|
].forEach((value) => {
|
||||||
|
try {
|
||||||
|
worker.cpuUsage(value);
|
||||||
|
} catch (e) {
|
||||||
|
assert.ok(/ERR_OUT_OF_RANGE|ERR_INVALID_ARG_TYPE/i.test(e.code));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const worker = new Worker(`
|
||||||
|
const { parentPort } = require('worker_threads');
|
||||||
|
parentPort.on('message', () => {});
|
||||||
|
`, { eval: true });
|
||||||
|
|
||||||
|
// See test-process-threadCpuUsage-main-thread.js
|
||||||
|
if (isSunOS) {
|
||||||
|
assert.throws(
|
||||||
|
() => worker.cpuUsage(),
|
||||||
|
{
|
||||||
|
code: 'ERR_OPERATION_FAILED',
|
||||||
|
name: 'Error',
|
||||||
|
message: 'Operation failed: worker.cpuUsage() is not available on SunOS'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
worker.terminate();
|
||||||
|
} else {
|
||||||
|
worker.on('online', common.mustCall(async () => {
|
||||||
|
check(worker);
|
||||||
|
|
||||||
|
const prev = await worker.cpuUsage();
|
||||||
|
validate(prev);
|
||||||
|
|
||||||
|
const curr = await worker.cpuUsage();
|
||||||
|
validate(curr);
|
||||||
|
|
||||||
|
assert.ok(curr.user >= prev.user);
|
||||||
|
assert.ok(curr.system >= prev.system);
|
||||||
|
|
||||||
|
const delta = await worker.cpuUsage(curr);
|
||||||
|
validate(delta);
|
||||||
|
|
||||||
|
worker.terminate();
|
||||||
|
}));
|
||||||
|
|
||||||
|
worker.once('exit', common.mustCall(async () => {
|
||||||
|
await assert.rejects(worker.cpuUsage(), {
|
||||||
|
code: 'ERR_WORKER_NOT_RUNNING'
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
@ -62,6 +62,7 @@ const { getSystemErrorName } = require('util');
|
||||||
delete providers.SIGINTWATCHDOG;
|
delete providers.SIGINTWATCHDOG;
|
||||||
delete providers.WORKERHEAPSNAPSHOT;
|
delete providers.WORKERHEAPSNAPSHOT;
|
||||||
delete providers.WORKERHEAPSTATISTICS;
|
delete providers.WORKERHEAPSTATISTICS;
|
||||||
|
delete providers.WORKERCPUUSAGE;
|
||||||
delete providers.BLOBREADER;
|
delete providers.BLOBREADER;
|
||||||
delete providers.RANDOMPRIMEREQUEST;
|
delete providers.RANDOMPRIMEREQUEST;
|
||||||
delete providers.CHECKPRIMEREQUEST;
|
delete providers.CHECKPRIMEREQUEST;
|
||||||
|
|
|
||||||
1
typings/internalBinding/worker.d.ts
vendored
1
typings/internalBinding/worker.d.ts
vendored
|
|
@ -16,6 +16,7 @@ declare namespace InternalWorkerBinding {
|
||||||
getResourceLimits(): Float64Array;
|
getResourceLimits(): Float64Array;
|
||||||
takeHeapSnapshot(): object;
|
takeHeapSnapshot(): object;
|
||||||
getHeapStatistics(): Promise<object>;
|
getHeapStatistics(): Promise<object>;
|
||||||
|
cpuUsage(): Promise<object>;
|
||||||
loopIdleTime(): number;
|
loopIdleTime(): number;
|
||||||
loopStartTime(): number;
|
loopStartTime(): number;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user