| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent f606352 commit dda6ca9
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1320,6 +1320,18 @@ If the Worker thread is no longer running, which may occur before the | |||
| 1320 | 1320 | [`'exit'` event][] is emitted, the returned `Promise` is rejected | |
| 1321 | 1321 | immediately with an [`ERR_WORKER_NOT_RUNNING`][] error. | |
| 1322 | 1322 | ||
| 1323 | + ### `worker.getHeapStatistics()` | ||
| 1324 | + | ||
| 1325 | + <!-- YAML | ||
| 1326 | + added: REPLACEME | ||
| 1327 | + --> | ||
| 1328 | + | ||
| 1329 | + * Returns: {Promise} | ||
| 1330 | + | ||
| 1331 | + This method returns a `Promise` that will resolve to an object identical to [`v8.getHeapStatistics()`][], | ||
| 1332 | + or reject with an [`ERR_WORKER_NOT_RUNNING`][] error if the worker is no longer running. | ||
| 1333 | + This methods allows the statistics to be observed from outside the actual thread. | ||
| 1334 | + | ||
| 1323 | 1335 | ### `worker.performance` | |
| 1324 | 1336 | ||
| 1325 | 1337 | <!-- YAML | |
@@ -1614,6 +1626,7 @@ thread spawned will spawn another until the application crashes. | |||
| 1614 | 1626 | [`require('node:worker_threads').workerData`]: #workerworkerdata | |
| 1615 | 1627 | [`trace_events`]: tracing.md | |
| 1616 | 1628 | [`v8.getHeapSnapshot()`]: v8.md#v8getheapsnapshotoptions | |
| 1629 | + [`v8.getHeapStatistics()`]: v8.md#v8getheapstatistics | ||
| 1617 | 1630 | [`vm`]: vm.md | |
| 1618 | 1631 | [`worker.SHARE_ENV`]: #workershare_env | |
| 1619 | 1632 | [`worker.on('message')`]: #event-message_1 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -459,6 +459,17 @@ class Worker extends EventEmitter { | |||
| 459 | 459 | }; | |
| 460 | 460 | }); | |
| 461 | 461 | } | |
| 462 | + | ||
| 463 | + getHeapStatistics() { | ||
| 464 | + const taker = this[kHandle]?.getHeapStatistics(); | ||
| 465 | + | ||
| 466 | + return new Promise((resolve, reject) => { | ||
| 467 | + if (!taker) return reject(new ERR_WORKER_NOT_RUNNING()); | ||
| 468 | + taker.ondone = (handle) => { | ||
| 469 | + resolve(handle); | ||
| 470 | + }; | ||
| 471 | + }); | ||
| 472 | + } | ||
| 462 | 473 | } | |
| 463 | 474 | ||
| 464 | 475 | /** | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -79,6 +79,7 @@ namespace node { | |||
| 79 | 79 | V(SIGINTWATCHDOG) \ | |
| 80 | 80 | V(WORKER) \ | |
| 81 | 81 | V(WORKERHEAPSNAPSHOT) \ | |
| 82 | + V(WORKERHEAPSTATISTICS) \ | ||
| 82 | 83 | V(WRITEWRAP) \ | |
| 83 | 84 | V(ZLIB) | |
| 84 | 85 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -443,6 +443,7 @@ | |||
| 443 | 443 | V(tty_constructor_template, v8::FunctionTemplate) \ | |
| 444 | 444 | V(write_wrap_template, v8::ObjectTemplate) \ | |
| 445 | 445 | V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \ | |
| 446 | + V(worker_heap_statistics_taker_template, v8::ObjectTemplate) \ | ||
| 446 | 447 | V(x509_constructor_template, v8::FunctionTemplate) | |
| 447 | 448 | ||
| 448 | 449 | #define PER_REALM_STRONG_PERSISTENT_VALUES(V) \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -811,6 +811,116 @@ void Worker::Unref(const FunctionCallbackInfo<Value>& args) { | |||
| 811 | 811 | } | |
| 812 | 812 | } | |
| 813 | 813 | ||
| 814 | + class WorkerHeapStatisticsTaker : public AsyncWrap { | ||
| 815 | + public: | ||
| 816 | + WorkerHeapStatisticsTaker(Environment* env, Local<Object> obj) | ||
| 817 | + : AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERHEAPSTATISTICS) {} | ||
| 818 | + | ||
| 819 | + SET_NO_MEMORY_INFO() | ||
| 820 | + SET_MEMORY_INFO_NAME(WorkerHeapStatisticsTaker) | ||
| 821 | + SET_SELF_SIZE(WorkerHeapStatisticsTaker) | ||
| 822 | + }; | ||
| 823 | + | ||
| 824 | + void Worker::GetHeapStatistics(const FunctionCallbackInfo<Value>& args) { | ||
| 825 | + Worker* w; | ||
| 826 | + ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); | ||
| 827 | + | ||
| 828 | + Environment* env = w->env(); | ||
| 829 | + AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); | ||
| 830 | + Local<Object> wrap; | ||
| 831 | + if (!env->worker_heap_statistics_taker_template() | ||
| 832 | + ->NewInstance(env->context()) | ||
| 833 | + .ToLocal(&wrap)) { | ||
| 834 | + return; | ||
| 835 | + } | ||
| 836 | + | ||
| 837 | + // The created WorkerHeapStatisticsTaker is an object owned by main | ||
| 838 | + // thread's Isolate, it can not be accessed by worker thread | ||
| 839 | + std::unique_ptr<BaseObjectPtr<WorkerHeapStatisticsTaker>> taker = | ||
| 840 | + std::make_unique<BaseObjectPtr<WorkerHeapStatisticsTaker>>( | ||
| 841 | + MakeDetachedBaseObject<WorkerHeapStatisticsTaker>(env, wrap)); | ||
| 842 | + | ||
| 843 | + // Interrupt the worker thread and take a snapshot, then schedule a call | ||
| 844 | + // on the parent thread that turns that snapshot into a readable stream. | ||
| 845 | + bool scheduled = w->RequestInterrupt([taker = std::move(taker), | ||
| 846 | + env](Environment* worker_env) mutable { | ||
| 847 | + // We create a unique pointer to HeapStatistics so that the actual object | ||
| 848 | + // it's not copied in the lambda, but only the pointer is. | ||
| 849 | + auto heap_stats = std::make_unique<v8::HeapStatistics>(); | ||
| 850 | + worker_env->isolate()->GetHeapStatistics(heap_stats.get()); | ||
| 851 | + | ||
| 852 | + // Here, the worker thread temporarily owns the WorkerHeapStatisticsTaker | ||
| 853 | + // object. | ||
| 854 | + | ||
| 855 | + env->SetImmediateThreadsafe( | ||
| 856 | + [taker = std::move(taker), | ||
| 857 | + heap_stats = std::move(heap_stats)](Environment* env) mutable { | ||
| 858 | + Isolate* isolate = env->isolate(); | ||
| 859 | + HandleScope handle_scope(isolate); | ||
| 860 | + Context::Scope context_scope(env->context()); | ||
| 861 | + | ||
| 862 | + AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker->get()); | ||
| 863 | + | ||
| 864 | + Local<v8::Name> heap_stats_names[] = { | ||
| 865 | + FIXED_ONE_BYTE_STRING(isolate, "total_heap_size"), | ||
| 866 | + FIXED_ONE_BYTE_STRING(isolate, "total_heap_size_executable"), | ||
| 867 | + FIXED_ONE_BYTE_STRING(isolate, "total_physical_size"), | ||
| 868 | + FIXED_ONE_BYTE_STRING(isolate, "total_available_size"), | ||
| 869 | + FIXED_ONE_BYTE_STRING(isolate, "used_heap_size"), | ||
| 870 | + FIXED_ONE_BYTE_STRING(isolate, "heap_size_limit"), | ||
| 871 | + FIXED_ONE_BYTE_STRING(isolate, "malloced_memory"), | ||
| 872 | + FIXED_ONE_BYTE_STRING(isolate, "peak_malloced_memory"), | ||
| 873 | + FIXED_ONE_BYTE_STRING(isolate, "does_zap_garbage"), | ||
| 874 | + FIXED_ONE_BYTE_STRING(isolate, "number_of_native_contexts"), | ||
| 875 | + FIXED_ONE_BYTE_STRING(isolate, "number_of_detached_contexts"), | ||
| 876 | + FIXED_ONE_BYTE_STRING(isolate, "total_global_handles_size"), | ||
| 877 | + FIXED_ONE_BYTE_STRING(isolate, "used_global_handles_size"), | ||
| 878 | + FIXED_ONE_BYTE_STRING(isolate, "external_memory")}; | ||
| 879 | + | ||
| 880 | + // Define an array of property values | ||
| 881 | + Local<Value> heap_stats_values[] = { | ||
| 882 | + Number::New(isolate, heap_stats->total_heap_size()), | ||
| 883 | + Number::New(isolate, heap_stats->total_heap_size_executable()), | ||
| 884 | + Number::New(isolate, heap_stats->total_physical_size()), | ||
| 885 | + Number::New(isolate, heap_stats->total_available_size()), | ||
| 886 | + Number::New(isolate, heap_stats->used_heap_size()), | ||
| 887 | + Number::New(isolate, heap_stats->heap_size_limit()), | ||
| 888 | + Number::New(isolate, heap_stats->malloced_memory()), | ||
| 889 | + Number::New(isolate, heap_stats->peak_malloced_memory()), | ||
| 890 | + Boolean::New(isolate, heap_stats->does_zap_garbage()), | ||
| 891 | + Number::New(isolate, heap_stats->number_of_native_contexts()), | ||
| 892 | + Number::New(isolate, heap_stats->number_of_detached_contexts()), | ||
| 893 | + Number::New(isolate, heap_stats->total_global_handles_size()), | ||
| 894 | + Number::New(isolate, heap_stats->used_global_handles_size()), | ||
| 895 | + Number::New(isolate, heap_stats->external_memory())}; | ||
| 896 | + | ||
| 897 | + DCHECK_EQ(arraysize(heap_stats_names), arraysize(heap_stats_values)); | ||
| 898 | + | ||
| 899 | + // Create the object with the property names and values | ||
| 900 | + Local<Object> stats = Object::New(isolate, | ||
| 901 | + Null(isolate), | ||
| 902 | + heap_stats_names, | ||
| 903 | + heap_stats_values, | ||
| 904 | + arraysize(heap_stats_names)); | ||
| 905 | + | ||
| 906 | + Local<Value> args[] = {stats}; | ||
| 907 | + taker->get()->MakeCallback( | ||
| 908 | + env->ondone_string(), arraysize(args), args); | ||
| 909 | + // implicitly delete `taker` | ||
| 910 | + }, | ||
| 911 | + CallbackFlags::kUnrefed); | ||
| 912 | + | ||
| 913 | + // Now, the lambda is delivered to the main thread, as a result, the | ||
| 914 | + // WorkerHeapStatisticsTaker object is delivered to the main thread, too. | ||
| 915 | + }); | ||
| 916 | + | ||
| 917 | + if (scheduled) { | ||
| 918 | + args.GetReturnValue().Set(wrap); | ||
| 919 | + } else { | ||
| 920 | + args.GetReturnValue().Set(Local<Object>()); | ||
| 921 | + } | ||
| 922 | + } | ||
| 923 | + | ||
| 814 | 924 | void Worker::GetResourceLimits(const FunctionCallbackInfo<Value>& args) { | |
| 815 | 925 | Worker* w; | |
| 816 | 926 | ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); | |
@@ -991,6 +1101,7 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data, | |||
| 991 | 1101 | SetProtoMethod(isolate, w, "takeHeapSnapshot", Worker::TakeHeapSnapshot); | |
| 992 | 1102 | SetProtoMethod(isolate, w, "loopIdleTime", Worker::LoopIdleTime); | |
| 993 | 1103 | SetProtoMethod(isolate, w, "loopStartTime", Worker::LoopStartTime); | |
| 1104 | + SetProtoMethod(isolate, w, "getHeapStatistics", Worker::GetHeapStatistics); | ||
| 994 | 1105 | ||
| 995 | 1106 | SetConstructorFunction(isolate, target, "Worker", w); | |
| 996 | 1107 | } | |
@@ -1009,6 +1120,20 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data, | |||
| 1009 | 1120 | wst->InstanceTemplate()); | |
| 1010 | 1121 | } | |
| 1011 | 1122 | ||
| 1123 | + { | ||
| 1124 | + Local<FunctionTemplate> wst = NewFunctionTemplate(isolate, nullptr); | ||
| 1125 | + | ||
| 1126 | + wst->InstanceTemplate()->SetInternalFieldCount( | ||
| 1127 | + WorkerHeapSnapshotTaker::kInternalFieldCount); | ||
| 1128 | + wst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); | ||
| 1129 | + | ||
| 1130 | + Local<String> wst_string = | ||
| 1131 | + FIXED_ONE_BYTE_STRING(isolate, "WorkerHeapStatisticsTaker"); | ||
| 1132 | + wst->SetClassName(wst_string); | ||
| 1133 | + isolate_data->set_worker_heap_statistics_taker_template( | ||
| 1134 | + wst->InstanceTemplate()); | ||
| 1135 | + } | ||
| 1136 | + | ||
| 1012 | 1137 | SetMethod(isolate, target, "getEnvMessagePort", GetEnvMessagePort); | |
| 1013 | 1138 | } | |
| 1014 | 1139 | ||
@@ -1074,6 +1199,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { | |||
| 1074 | 1199 | registry->Register(Worker::TakeHeapSnapshot); | |
| 1075 | 1200 | registry->Register(Worker::LoopIdleTime); | |
| 1076 | 1201 | registry->Register(Worker::LoopStartTime); | |
| 1202 | + registry->Register(Worker::GetHeapStatistics); | ||
| 1077 | 1203 | } | |
| 1078 | 1204 | ||
| 1079 | 1205 | } // anonymous namespace | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -78,6 +78,8 @@ class Worker : public AsyncWrap { | |||
| 78 | 78 | static void TakeHeapSnapshot(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 79 | 79 | static void LoopIdleTime(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 80 | 80 | static void LoopStartTime(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 81 | + static void GetHeapStatistics( | ||
| 82 | + const v8::FunctionCallbackInfo<v8::Value>& args); | ||
| 81 | 83 | ||
| 82 | 84 | private: | |
| 83 | 85 | bool CreateEnvMessagePort(Environment* env); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,63 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + const fixtures = require('../common/fixtures'); | ||
| 5 | + | ||
| 6 | + common.skipIfInspectorDisabled(); | ||
| 7 | + | ||
| 8 | + const { | ||
| 9 | + Worker, | ||
| 10 | + isMainThread, | ||
| 11 | + } = require('worker_threads'); | ||
| 12 | + | ||
| 13 | + if (!isMainThread) { | ||
| 14 | + common.skip('This test only works on a main thread'); | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + // Ensures that worker.getHeapStatistics() returns valid data | ||
| 18 | + | ||
| 19 | + const assert = require('assert'); | ||
| 20 | + | ||
| 21 | + if (isMainThread) { | ||
| 22 | + const name = 'Hello Thread'; | ||
| 23 | + const worker = new Worker(fixtures.path('worker-name.js'), { | ||
| 24 | + name, | ||
| 25 | + }); | ||
| 26 | + worker.once('message', common.mustCall(async (message) => { | ||
| 27 | + const stats = await worker.getHeapStatistics(); | ||
| 28 | + const keys = [ | ||
| 29 | + `total_heap_size`, | ||
| 30 | + `total_heap_size_executable`, | ||
| 31 | + `total_physical_size`, | ||
| 32 | + `total_available_size`, | ||
| 33 | + `used_heap_size`, | ||
| 34 | + `heap_size_limit`, | ||
| 35 | + `malloced_memory`, | ||
| 36 | + `peak_malloced_memory`, | ||
| 37 | + `does_zap_garbage`, | ||
| 38 | + `number_of_native_contexts`, | ||
| 39 | + `number_of_detached_contexts`, | ||
| 40 | + `total_global_handles_size`, | ||
| 41 | + `used_global_handles_size`, | ||
| 42 | + `external_memory`, | ||
| 43 | + ].sort(); | ||
| 44 | + assert.deepStrictEqual(keys, Object.keys(stats).sort()); | ||
| 45 | + for (const key of keys) { | ||
| 46 | + if (key === 'does_zap_garbage') { | ||
| 47 | + assert.strictEqual(typeof stats[key], 'boolean', `Expected ${key} to be a boolean`); | ||
| 48 | + continue; | ||
| 49 | + } | ||
| 50 | + assert.strictEqual(typeof stats[key], 'number', `Expected ${key} to be a number`); | ||
| 51 | + assert.ok(stats[key] >= 0, `Expected ${key} to be >= 0`); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + worker.postMessage('done'); | ||
| 55 | + })); | ||
| 56 | + | ||
| 57 | + worker.once('exit', common.mustCall(async (code) => { | ||
| 58 | + assert.strictEqual(code, 0); | ||
| 59 | + await assert.rejects(worker.getHeapStatistics(), { | ||
| 60 | + code: 'ERR_WORKER_NOT_RUNNING' | ||
| 61 | + }); | ||
| 62 | + })); | ||
| 63 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -61,6 +61,7 @@ const { getSystemErrorName } = require('util'); | |||
| 61 | 61 | delete providers.ELDHISTOGRAM; | |
| 62 | 62 | delete providers.SIGINTWATCHDOG; | |
| 63 | 63 | delete providers.WORKERHEAPSNAPSHOT; | |
| 64 | + delete providers.WORKERHEAPSTATISTICS; | ||
| 64 | 65 | delete providers.BLOBREADER; | |
| 65 | 66 | delete providers.RANDOMPRIMEREQUEST; | |
| 66 | 67 | delete providers.CHECKPRIMEREQUEST; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,6 +15,7 @@ declare namespace InternalWorkerBinding { | |||
| 15 | 15 | unref(): void; | |
| 16 | 16 | getResourceLimits(): Float64Array; | |
| 17 | 17 | takeHeapSnapshot(): object; | |
| 18 | + getHeapStatistics(): Promise<object>; | ||
| 18 | 19 | loopIdleTime(): number; | |
| 19 | 20 | loopStartTime(): number; | |
| 20 | 21 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments