| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1596,6 +1596,19 @@ added: v10.5.0 | |||
| 1596 | 1596 | The `'online'` event is emitted when the worker thread has started executing | |
| 1597 | 1597 | JavaScript code. | |
| 1598 | 1598 | ||
| 1599 | + ### `worker.cpuUsage([prev])` | ||
| 1600 | + | ||
| 1601 | + <!-- YAML | ||
| 1602 | + added: | ||
| 1603 | + - REPLACEME | ||
| 1604 | + --> | ||
| 1605 | + | ||
| 1606 | + * Returns: {Promise} | ||
| 1607 | + | ||
| 1608 | + This method returns a `Promise` that will resolve to an object identical to [`process.threadCpuUsage()`][], | ||
| 1609 | + or reject with an [`ERR_WORKER_NOT_RUNNING`][] error if the worker is no longer running. | ||
| 1610 | + This methods allows the statistics to be observed from outside the actual thread. | ||
| 1611 | + | ||
| 1599 | 1612 | ### `worker.getHeapSnapshot([options])` | |
| 1600 | 1613 | ||
| 1601 | 1614 | <!-- YAML | |
@@ -1949,6 +1962,7 @@ thread spawned will spawn another until the application crashes. | |||
| 1949 | 1962 | [`process.stderr`]: process.md#processstderr | |
| 1950 | 1963 | [`process.stdin`]: process.md#processstdin | |
| 1951 | 1964 | [`process.stdout`]: process.md#processstdout | |
| 1965 | + [`process.threadCpuUsage()`]: process.md#processthreadcpuusagepreviousvalue | ||
| 1952 | 1966 | [`process.title`]: process.md#processtitle | |
| 1953 | 1967 | [`require('node:worker_threads').isMainThread`]: #workerismainthread | |
| 1954 | 1968 | [`require('node:worker_threads').parentPort.on('message')`]: #event-message | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,7 @@ const { | |||
| 8 | 8 | Float64Array, | |
| 9 | 9 | FunctionPrototypeBind, | |
| 10 | 10 | MathMax, | |
| 11 | + NumberMAX_SAFE_INTEGER, | ||
| 11 | 12 | ObjectEntries, | |
| 12 | 13 | Promise, | |
| 13 | 14 | PromiseResolve, | |
@@ -39,6 +40,7 @@ const { | |||
| 39 | 40 | ERR_WORKER_INVALID_EXEC_ARGV, | |
| 40 | 41 | ERR_INVALID_ARG_TYPE, | |
| 41 | 42 | ERR_INVALID_ARG_VALUE, | |
| 43 | + ERR_OPERATION_FAILED, | ||
| 42 | 44 | } = errorCodes; | |
| 43 | 45 | ||
| 44 | 46 | const workerIo = require('internal/worker/io'); | |
@@ -59,7 +61,7 @@ const { createMainThreadPort, destroyMainThreadPort } = require('internal/worker | |||
| 59 | 61 | const { deserializeError } = require('internal/error_serdes'); | |
| 60 | 62 | const { fileURLToPath, isURL, pathToFileURL } = require('internal/url'); | |
| 61 | 63 | const { kEmptyObject, SymbolAsyncDispose } = require('internal/util'); | |
| 62 | - const { validateArray, validateString } = require('internal/validators'); | ||
| 64 | + const { validateArray, validateString, validateObject, validateNumber } = require('internal/validators'); | ||
| 63 | 65 | const { | |
| 64 | 66 | throwIfBuildingSnapshot, | |
| 65 | 67 | } = require('internal/v8/startup_snapshot'); | |
@@ -473,6 +475,37 @@ class Worker extends EventEmitter { | |||
| 473 | 475 | }; | |
| 474 | 476 | }); | |
| 475 | 477 | } | |
| 478 | + | ||
| 479 | + cpuUsage(prev) { | ||
| 480 | + if (prev) { | ||
| 481 | + validateObject(prev, 'prev'); | ||
| 482 | + validateNumber(prev.user, 'prev.user', 0, NumberMAX_SAFE_INTEGER); | ||
| 483 | + validateNumber(prev.system, 'prev.system', 0, NumberMAX_SAFE_INTEGER); | ||
| 484 | + } | ||
| 485 | + if (process.platform === 'sunos') { | ||
| 486 | + throw new ERR_OPERATION_FAILED('worker.cpuUsage() is not available on SunOS'); | ||
| 487 | + } | ||
| 488 | + const taker = this[kHandle]?.cpuUsage(); | ||
| 489 | + return new Promise((resolve, reject) => { | ||
| 490 | + if (!taker) return reject(new ERR_WORKER_NOT_RUNNING()); | ||
| 491 | + taker.ondone = (err, current) => { | ||
| 492 | + if (err !== null) { | ||
| 493 | + return reject(err); | ||
| 494 | + } | ||
| 495 | + if (prev) { | ||
| 496 | + resolve({ | ||
| 497 | + user: current.user - prev.user, | ||
| 498 | + system: current.system - prev.system, | ||
| 499 | + }); | ||
| 500 | + } else { | ||
| 501 | + resolve({ | ||
| 502 | + user: current.user, | ||
| 503 | + system: current.system, | ||
| 504 | + }); | ||
| 505 | + } | ||
| 506 | + }; | ||
| 507 | + }); | ||
| 508 | + } | ||
| 476 | 509 | } | |
| 477 | 510 | ||
| 478 | 511 | /** | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -78,6 +78,7 @@ namespace node { | |||
| 78 | 78 | V(UDPWRAP) \ | |
| 79 | 79 | V(SIGINTWATCHDOG) \ | |
| 80 | 80 | V(WORKER) \ | |
| 81 | + V(WORKERCPUUSAGE) \ | ||
| 81 | 82 | V(WORKERHEAPSNAPSHOT) \ | |
| 82 | 83 | V(WORKERHEAPSTATISTICS) \ | |
| 83 | 84 | V(WRITEWRAP) \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -461,6 +461,7 @@ | |||
| 461 | 461 | V(tcp_constructor_template, v8::FunctionTemplate) \ | |
| 462 | 462 | V(tty_constructor_template, v8::FunctionTemplate) \ | |
| 463 | 463 | V(write_wrap_template, v8::ObjectTemplate) \ | |
| 464 | + V(worker_cpu_usage_taker_template, v8::ObjectTemplate) \ | ||
| 464 | 465 | V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \ | |
| 465 | 466 | V(worker_heap_statistics_taker_template, v8::ObjectTemplate) \ | |
| 466 | 467 | V(x509_constructor_template, v8::FunctionTemplate) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -32,6 +32,7 @@ using v8::Isolate; | |||
| 32 | 32 | using v8::Local; | |
| 33 | 33 | using v8::Locker; | |
| 34 | 34 | using v8::Maybe; | |
| 35 | + using v8::Name; | ||
| 35 | 36 | using v8::Null; | |
| 36 | 37 | using v8::Number; | |
| 37 | 38 | using v8::Object; | |
@@ -811,6 +812,81 @@ void Worker::Unref(const FunctionCallbackInfo<Value>& args) { | |||
| 811 | 812 | } | |
| 812 | 813 | } | |
| 813 | 814 | ||
| 815 | + class WorkerCpuUsageTaker : public AsyncWrap { | ||
| 816 | + public: | ||
| 817 | + WorkerCpuUsageTaker(Environment* env, Local<Object> obj) | ||
| 818 | + : AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERCPUUSAGE) {} | ||
| 819 | + | ||
| 820 | + SET_NO_MEMORY_INFO() | ||
| 821 | + SET_MEMORY_INFO_NAME(WorkerCpuUsageTaker) | ||
| 822 | + SET_SELF_SIZE(WorkerCpuUsageTaker) | ||
| 823 | + }; | ||
| 824 | + | ||
| 825 | + void Worker::CpuUsage(const FunctionCallbackInfo<Value>& args) { | ||
| 826 | + Worker* w; | ||
| 827 | + ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); | ||
| 828 | + | ||
| 829 | + Environment* env = w->env(); | ||
| 830 | + AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); | ||
| 831 | + Local<Object> wrap; | ||
| 832 | + if (!env->worker_cpu_usage_taker_template() | ||
| 833 | + ->NewInstance(env->context()) | ||
| 834 | + .ToLocal(&wrap)) { | ||
| 835 | + return; | ||
| 836 | + } | ||
| 837 | + | ||
| 838 | + BaseObjectPtr<WorkerCpuUsageTaker> taker = | ||
| 839 | + MakeDetachedBaseObject<WorkerCpuUsageTaker>(env, wrap); | ||
| 840 | + | ||
| 841 | + bool scheduled = w->RequestInterrupt([taker = std::move(taker), | ||
| 842 | + env](Environment* worker_env) mutable { | ||
| 843 | + auto cpu_usage_stats = std::make_unique<uv_rusage_t>(); | ||
| 844 | + int err = uv_getrusage_thread(cpu_usage_stats.get()); | ||
| 845 | + | ||
| 846 | + env->SetImmediateThreadsafe( | ||
| 847 | + [taker = std::move(taker), | ||
| 848 | + cpu_usage_stats = std::move(cpu_usage_stats), | ||
| 849 | + err = err](Environment* env) mutable { | ||
| 850 | + Isolate* isolate = env->isolate(); | ||
| 851 | + HandleScope handle_scope(isolate); | ||
| 852 | + Context::Scope context_scope(env->context()); | ||
| 853 | + AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get()); | ||
| 854 | + | ||
| 855 | + Local<Value> argv[] = { | ||
| 856 | + Null(isolate), | ||
| 857 | + Undefined(isolate), | ||
| 858 | + }; | ||
| 859 | + | ||
| 860 | + if (err) { | ||
| 861 | + argv[0] = UVException( | ||
| 862 | + isolate, err, "uv_getrusage_thread", nullptr, nullptr, nullptr); | ||
| 863 | + } else { | ||
| 864 | + Local<Name> names[] = { | ||
| 865 | + FIXED_ONE_BYTE_STRING(isolate, "user"), | ||
| 866 | + FIXED_ONE_BYTE_STRING(isolate, "system"), | ||
| 867 | + }; | ||
| 868 | + Local<Value> values[] = { | ||
| 869 | + Number::New(isolate, | ||
| 870 | + 1e6 * cpu_usage_stats->ru_utime.tv_sec + | ||
| 871 | + cpu_usage_stats->ru_utime.tv_usec), | ||
| 872 | + Number::New(isolate, | ||
| 873 | + 1e6 * cpu_usage_stats->ru_stime.tv_sec + | ||
| 874 | + cpu_usage_stats->ru_stime.tv_usec), | ||
| 875 | + }; | ||
| 876 | + argv[1] = Object::New( | ||
| 877 | + isolate, Null(isolate), names, values, arraysize(names)); | ||
| 878 | + } | ||
| 879 | + | ||
| 880 | + taker->MakeCallback(env->ondone_string(), arraysize(argv), argv); | ||
| 881 | + }, | ||
| 882 | + CallbackFlags::kUnrefed); | ||
| 883 | + }); | ||
| 884 | + | ||
| 885 | + if (scheduled) { | ||
| 886 | + args.GetReturnValue().Set(wrap); | ||
| 887 | + } | ||
| 888 | + } | ||
| 889 | + | ||
| 814 | 890 | class WorkerHeapStatisticsTaker : public AsyncWrap { | |
| 815 | 891 | public: | |
| 816 | 892 | WorkerHeapStatisticsTaker(Environment* env, Local<Object> obj) | |
@@ -1102,6 +1178,7 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data, | |||
| 1102 | 1178 | SetProtoMethod(isolate, w, "loopIdleTime", Worker::LoopIdleTime); | |
| 1103 | 1179 | SetProtoMethod(isolate, w, "loopStartTime", Worker::LoopStartTime); | |
| 1104 | 1180 | SetProtoMethod(isolate, w, "getHeapStatistics", Worker::GetHeapStatistics); | |
| 1181 | + SetProtoMethod(isolate, w, "cpuUsage", Worker::CpuUsage); | ||
| 1105 | 1182 | ||
| 1106 | 1183 | SetConstructorFunction(isolate, target, "Worker", w); | |
| 1107 | 1184 | } | |
@@ -1134,6 +1211,19 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data, | |||
| 1134 | 1211 | wst->InstanceTemplate()); | |
| 1135 | 1212 | } | |
| 1136 | 1213 | ||
| 1214 | + { | ||
| 1215 | + Local<FunctionTemplate> wst = NewFunctionTemplate(isolate, nullptr); | ||
| 1216 | + | ||
| 1217 | + wst->InstanceTemplate()->SetInternalFieldCount( | ||
| 1218 | + WorkerCpuUsageTaker::kInternalFieldCount); | ||
| 1219 | + wst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); | ||
| 1220 | + | ||
| 1221 | + Local<String> wst_string = | ||
| 1222 | + FIXED_ONE_BYTE_STRING(isolate, "WorkerCpuUsageTaker"); | ||
| 1223 | + wst->SetClassName(wst_string); | ||
| 1224 | + isolate_data->set_worker_cpu_usage_taker_template(wst->InstanceTemplate()); | ||
| 1225 | + } | ||
| 1226 | + | ||
| 1137 | 1227 | SetMethod(isolate, target, "getEnvMessagePort", GetEnvMessagePort); | |
| 1138 | 1228 | } | |
| 1139 | 1229 | ||
@@ -1200,6 +1290,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { | |||
| 1200 | 1290 | registry->Register(Worker::LoopIdleTime); | |
| 1201 | 1291 | registry->Register(Worker::LoopStartTime); | |
| 1202 | 1292 | registry->Register(Worker::GetHeapStatistics); | |
| 1293 | + registry->Register(Worker::CpuUsage); | ||
| 1203 | 1294 | } | |
| 1204 | 1295 | ||
| 1205 | 1296 | } // anonymous namespace | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -80,6 +80,7 @@ class Worker : public AsyncWrap { | |||
| 80 | 80 | static void LoopStartTime(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 81 | 81 | static void GetHeapStatistics( | |
| 82 | 82 | const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 83 | + static void CpuUsage(const v8::FunctionCallbackInfo<v8::Value>& args); | ||
| 83 | 84 | ||
| 84 | 85 | private: | |
| 85 | 86 | bool CreateEnvMessagePort(Environment* env); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,81 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const { isSunOS } = require('../common'); | ||
| 4 | + const assert = require('assert'); | ||
| 5 | + const { | ||
| 6 | + Worker, | ||
| 7 | + } = require('worker_threads'); | ||
| 8 | + | ||
| 9 | + function validate(result) { | ||
| 10 | + assert.ok(typeof result == 'object' && result !== null); | ||
| 11 | + assert.ok(result.user >= 0); | ||
| 12 | + assert.ok(result.system >= 0); | ||
| 13 | + assert.ok(Number.isFinite(result.user)); | ||
| 14 | + assert.ok(Number.isFinite(result.system)); | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + function check(worker) { | ||
| 18 | + [ | ||
| 19 | + -1, | ||
| 20 | + 1.1, | ||
| 21 | + NaN, | ||
| 22 | + undefined, | ||
| 23 | + {}, | ||
| 24 | + [], | ||
| 25 | + null, | ||
| 26 | + function() {}, | ||
| 27 | + Symbol(), | ||
| 28 | + true, | ||
| 29 | + Infinity, | ||
| 30 | + { user: -1, system: 1 }, | ||
| 31 | + { user: 1, system: -1 }, | ||
| 32 | + ].forEach((value) => { | ||
| 33 | + try { | ||
| 34 | + worker.cpuUsage(value); | ||
| 35 | + } catch (e) { | ||
| 36 | + assert.ok(/ERR_OUT_OF_RANGE|ERR_INVALID_ARG_TYPE/i.test(e.code)); | ||
| 37 | + } | ||
| 38 | + }); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + const worker = new Worker(` | ||
| 42 | + const { parentPort } = require('worker_threads'); | ||
| 43 | + parentPort.on('message', () => {}); | ||
| 44 | + `, { eval: true }); | ||
| 45 | + | ||
| 46 | + // See test-process-threadCpuUsage-main-thread.js | ||
| 47 | + if (isSunOS) { | ||
| 48 | + assert.throws( | ||
| 49 | + () => worker.cpuUsage(), | ||
| 50 | + { | ||
| 51 | + code: 'ERR_OPERATION_FAILED', | ||
| 52 | + name: 'Error', | ||
| 53 | + message: 'Operation failed: worker.cpuUsage() is not available on SunOS' | ||
| 54 | + } | ||
| 55 | + ); | ||
| 56 | + worker.terminate(); | ||
| 57 | + } else { | ||
| 58 | + worker.on('online', common.mustCall(async () => { | ||
| 59 | + check(worker); | ||
| 60 | + | ||
| 61 | + const prev = await worker.cpuUsage(); | ||
| 62 | + validate(prev); | ||
| 63 | + | ||
| 64 | + const curr = await worker.cpuUsage(); | ||
| 65 | + validate(curr); | ||
| 66 | + | ||
| 67 | + assert.ok(curr.user >= prev.user); | ||
| 68 | + assert.ok(curr.system >= prev.system); | ||
| 69 | + | ||
| 70 | + const delta = await worker.cpuUsage(curr); | ||
| 71 | + validate(delta); | ||
| 72 | + | ||
| 73 | + worker.terminate(); | ||
| 74 | + })); | ||
| 75 | + | ||
| 76 | + worker.once('exit', common.mustCall(async () => { | ||
| 77 | + await assert.rejects(worker.cpuUsage(), { | ||
| 78 | + code: 'ERR_WORKER_NOT_RUNNING' | ||
| 79 | + }); | ||
| 80 | + })); | ||
| 81 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -62,6 +62,7 @@ const { getSystemErrorName } = require('util'); | |||
| 62 | 62 | delete providers.SIGINTWATCHDOG; | |
| 63 | 63 | delete providers.WORKERHEAPSNAPSHOT; | |
| 64 | 64 | delete providers.WORKERHEAPSTATISTICS; | |
| 65 | + delete providers.WORKERCPUUSAGE; | ||
| 65 | 66 | delete providers.BLOBREADER; | |
| 66 | 67 | delete providers.RANDOMPRIMEREQUEST; | |
| 67 | 68 | delete providers.CHECKPRIMEREQUEST; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,6 +16,7 @@ declare namespace InternalWorkerBinding { | |||
| 16 | 16 | getResourceLimits(): Float64Array; | |
| 17 | 17 | takeHeapSnapshot(): object; | |
| 18 | 18 | getHeapStatistics(): Promise<object>; | |
| 19 | + cpuUsage(): Promise<object>; | ||
| 19 | 20 | loopIdleTime(): number; | |
| 20 | 21 | loopStartTime(): number; | |
| 21 | 22 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments