From 94ec4e62bf89f77bce2a76c8dcff48d4c972a4bc Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 12:27:05 +0200 Subject: [PATCH 01/19] worker: add worker.getHeapStatistics() Adds worker.getHeapStatistics() so that the heap usage of the worker could be observer from the parent thread. Signed-off-by: Matteo Collina --- doc/api/worker_threads.md | 28 ++++++++- lib/internal/worker.js | 6 ++ src/node_worker.cc | 64 ++++++++++++++++++++ src/node_worker.h | 2 + test/parallel/test-worker-heap-statistics.js | 57 +++++++++++++++++ 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-worker-heap-statistics.js diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index e9219bbea0d6e5..af1ab5c97f9202 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -1337,6 +1337,31 @@ If the Worker thread is no longer running, which may occur before the [`'exit'` event][] is emitted, the returned `Promise` is rejected immediately with an [`ERR_WORKER_NOT_RUNNING`][] error. +### `worker.getHeapStatistics()` + + + +* returns: {object} + * `total_heap_size` {number} + * `total_heap_size_executable` {number} + * `total_physical_size` {number} + * `total_available_size` {number} + * `used_heap_size` {number} + * `heap_size_limit` {number} + * `malloced_memory` {number} + * `peak_malloced_memory` {number} + * `does_zap_garbage` {number} + * `number_of_native_contexts` {number} + * `number_of_detached_contexts` {number} + * `total_global_handles_size` {number} + * `used_global_handles_size` {number} + * `external_memory` {number} + +This method is identical to [`v8.getHeapStatistics()`][] but it allows the +statistics to be observed from outside the actual thread. + ### `worker.performance` -* returns: {object} - * `total_heap_size` {number} - * `total_heap_size_executable` {number} - * `total_physical_size` {number} - * `total_available_size` {number} - * `used_heap_size` {number} - * `heap_size_limit` {number} - * `malloced_memory` {number} - * `peak_malloced_memory` {number} - * `does_zap_garbage` {number} - * `number_of_native_contexts` {number} - * `number_of_detached_contexts` {number} - * `total_global_handles_size` {number} - * `used_global_handles_size` {number} - * `external_memory` {number} +* Returns: {Object} This method is identical to [`v8.getHeapStatistics()`][] but it allows the statistics to be observed from outside the actual thread. @@ -1387,7 +1373,7 @@ added: `eventLoopUtilization()`. * `utilization2` {Object} The result of a previous call to `eventLoopUtilization()` prior to `utilization1`. -* returns: {object} +* returns: {Object} * `idle` {number} * `active` {number} * `utilization` {number} From c73d1b632a8daad3d1f26ab8a9b53106e05c95ab Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 12:44:04 +0200 Subject: [PATCH 03/19] fixup Signed-off-by: Matteo Collina --- doc/api/worker_threads.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index 27441994b456ef..a2306174c6ba0b 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -1373,7 +1373,7 @@ added: `eventLoopUtilization()`. * `utilization2` {Object} The result of a previous call to `eventLoopUtilization()` prior to `utilization1`. -* returns: {Object} +* Returns: {Object} * `idle` {number} * `active` {number} * `utilization` {number} From 599fb2c7ee7a5c7751b75016e8d0cad88864fec3 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 13:05:32 +0200 Subject: [PATCH 04/19] fixup Signed-off-by: Matteo Collina --- src/node_worker.cc | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/node_worker.cc b/src/node_worker.cc index e21985f920a314..5088e32bf36dca 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -830,50 +830,50 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { stats->Set(currentContext, String::NewFromUtf8(isolate, "total_heap_size").ToLocalChecked(), - Number::New(isolate, heap_stats.total_heap_size())); + Number::New(isolate, heap_stats.total_heap_size())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "total_heap_size_executable") .ToLocalChecked(), - Number::New(isolate, heap_stats.total_heap_size_executable())); + Number::New(isolate, heap_stats.total_heap_size_executable())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "total_physical_size").ToLocalChecked(), - Number::New(isolate, heap_stats.total_physical_size())); + Number::New(isolate, heap_stats.total_physical_size())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "total_available_size").ToLocalChecked(), - Number::New(isolate, heap_stats.total_available_size())); + Number::New(isolate, heap_stats.total_available_size())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "used_heap_size").ToLocalChecked(), - Number::New(isolate, heap_stats.used_heap_size())); + Number::New(isolate, heap_stats.used_heap_size())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "heap_size_limit").ToLocalChecked(), - Number::New(isolate, heap_stats.heap_size_limit())); + Number::New(isolate, heap_stats.heap_size_limit())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "malloced_memory").ToLocalChecked(), - Number::New(isolate, heap_stats.malloced_memory())); + Number::New(isolate, heap_stats.malloced_memory())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "peak_malloced_memory").ToLocalChecked(), - Number::New(isolate, heap_stats.peak_malloced_memory())); + Number::New(isolate, heap_stats.peak_malloced_memory())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "does_zap_garbage").ToLocalChecked(), - Boolean::New(isolate, heap_stats.does_zap_garbage())); + Boolean::New(isolate, heap_stats.does_zap_garbage())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "number_of_native_contexts") .ToLocalChecked(), - Number::New(isolate, heap_stats.number_of_native_contexts())); + Number::New(isolate, heap_stats.number_of_native_contexts())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "number_of_detached_contexts") .ToLocalChecked(), - Number::New(isolate, heap_stats.number_of_detached_contexts())); + Number::New(isolate, heap_stats.number_of_detached_contexts())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "total_global_handles_size") .ToLocalChecked(), - Number::New(isolate, heap_stats.total_global_handles_size())); + Number::New(isolate, heap_stats.total_global_handles_size())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "used_global_handles_size").ToLocalChecked(), - Number::New(isolate, heap_stats.used_global_handles_size())); + Number::New(isolate, heap_stats.used_global_handles_size())).Check(); stats->Set(currentContext, String::NewFromUtf8(isolate, "external_memory").ToLocalChecked(), - Number::New(isolate, heap_stats.external_memory())); + Number::New(isolate, heap_stats.external_memory())).Check(); args.GetReturnValue().Set(stats); } @@ -1060,7 +1060,6 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data, SetProtoMethod(isolate, w, "loopStartTime", Worker::LoopStartTime); SetProtoMethod(isolate, w, "getHeapStatistics", Worker::GetHeapStatistics); - SetConstructorFunction(isolate, target, "Worker", w); } From d219327538db8f2b638a94ac3f9d7452f756320b Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 13:06:35 +0200 Subject: [PATCH 05/19] fixup Signed-off-by: Matteo Collina --- src/node_worker.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node_worker.cc b/src/node_worker.cc index 5088e32bf36dca..39cd032e3a2f7b 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -1142,6 +1142,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(Worker::TakeHeapSnapshot); registry->Register(Worker::LoopIdleTime); registry->Register(Worker::LoopStartTime); + registry->Register(Worker::GetHeapStatistics); } } // anonymous namespace From 8b1266a054ec86189f2914f3f4b446de8e30b9a1 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 13:10:34 +0200 Subject: [PATCH 06/19] fixup Signed-off-by: Matteo Collina --- src/node_worker.cc | 124 ++++++++++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 46 deletions(-) diff --git a/src/node_worker.cc b/src/node_worker.cc index 39cd032e3a2f7b..bf0b233dda41e8 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -828,52 +828,84 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { Local stats = Object::New(isolate); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "total_heap_size").ToLocalChecked(), - Number::New(isolate, heap_stats.total_heap_size())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "total_heap_size_executable") - .ToLocalChecked(), - Number::New(isolate, heap_stats.total_heap_size_executable())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "total_physical_size").ToLocalChecked(), - Number::New(isolate, heap_stats.total_physical_size())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "total_available_size").ToLocalChecked(), - Number::New(isolate, heap_stats.total_available_size())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "used_heap_size").ToLocalChecked(), - Number::New(isolate, heap_stats.used_heap_size())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "heap_size_limit").ToLocalChecked(), - Number::New(isolate, heap_stats.heap_size_limit())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "malloced_memory").ToLocalChecked(), - Number::New(isolate, heap_stats.malloced_memory())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "peak_malloced_memory").ToLocalChecked(), - Number::New(isolate, heap_stats.peak_malloced_memory())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "does_zap_garbage").ToLocalChecked(), - Boolean::New(isolate, heap_stats.does_zap_garbage())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "number_of_native_contexts") - .ToLocalChecked(), - Number::New(isolate, heap_stats.number_of_native_contexts())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "number_of_detached_contexts") - .ToLocalChecked(), - Number::New(isolate, heap_stats.number_of_detached_contexts())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "total_global_handles_size") - .ToLocalChecked(), - Number::New(isolate, heap_stats.total_global_handles_size())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "used_global_handles_size").ToLocalChecked(), - Number::New(isolate, heap_stats.used_global_handles_size())).Check(); - stats->Set(currentContext, - String::NewFromUtf8(isolate, "external_memory").ToLocalChecked(), - Number::New(isolate, heap_stats.external_memory())).Check(); + stats + ->Set(currentContext, + String::NewFromUtf8(isolate, "total_heap_size").ToLocalChecked(), + Number::New(isolate, heap_stats.total_heap_size())) + .Check(); + stats + ->Set(currentContext, + String::NewFromUtf8(isolate, "total_heap_size_executable") + .ToLocalChecked(), + Number::New(isolate, heap_stats.total_heap_size_executable())) + .Check(); + stats + ->Set( + currentContext, + String::NewFromUtf8(isolate, "total_physical_size").ToLocalChecked(), + Number::New(isolate, heap_stats.total_physical_size())) + .Check(); + stats + ->Set( + currentContext, + String::NewFromUtf8(isolate, "total_available_size").ToLocalChecked(), + Number::New(isolate, heap_stats.total_available_size())) + .Check(); + stats + ->Set(currentContext, + String::NewFromUtf8(isolate, "used_heap_size").ToLocalChecked(), + Number::New(isolate, heap_stats.used_heap_size())) + .Check(); + stats + ->Set(currentContext, + String::NewFromUtf8(isolate, "heap_size_limit").ToLocalChecked(), + Number::New(isolate, heap_stats.heap_size_limit())) + .Check(); + stats + ->Set(currentContext, + String::NewFromUtf8(isolate, "malloced_memory").ToLocalChecked(), + Number::New(isolate, heap_stats.malloced_memory())) + .Check(); + stats + ->Set( + currentContext, + String::NewFromUtf8(isolate, "peak_malloced_memory").ToLocalChecked(), + Number::New(isolate, heap_stats.peak_malloced_memory())) + .Check(); + stats + ->Set(currentContext, + String::NewFromUtf8(isolate, "does_zap_garbage").ToLocalChecked(), + Boolean::New(isolate, heap_stats.does_zap_garbage())) + .Check(); + stats + ->Set(currentContext, + String::NewFromUtf8(isolate, "number_of_native_contexts") + .ToLocalChecked(), + Number::New(isolate, heap_stats.number_of_native_contexts())) + .Check(); + stats + ->Set(currentContext, + String::NewFromUtf8(isolate, "number_of_detached_contexts") + .ToLocalChecked(), + Number::New(isolate, heap_stats.number_of_detached_contexts())) + .Check(); + stats + ->Set(currentContext, + String::NewFromUtf8(isolate, "total_global_handles_size") + .ToLocalChecked(), + Number::New(isolate, heap_stats.total_global_handles_size())) + .Check(); + stats + ->Set(currentContext, + String::NewFromUtf8(isolate, "used_global_handles_size") + .ToLocalChecked(), + Number::New(isolate, heap_stats.used_global_handles_size())) + .Check(); + stats + ->Set(currentContext, + String::NewFromUtf8(isolate, "external_memory").ToLocalChecked(), + Number::New(isolate, heap_stats.external_memory())) + .Check(); args.GetReturnValue().Set(stats); } From 09741f29ae258b596eef9bf2dfc7e18e30aca155 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 18:58:44 +0200 Subject: [PATCH 07/19] macros Signed-off-by: Matteo Collina --- src/node_worker.cc | 106 +++++++++++++-------------------------------- 1 file changed, 30 insertions(+), 76 deletions(-) diff --git a/src/node_worker.cc b/src/node_worker.cc index bf0b233dda41e8..af8225d0771c77 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -828,85 +828,39 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { Local stats = Object::New(isolate); - stats - ->Set(currentContext, - String::NewFromUtf8(isolate, "total_heap_size").ToLocalChecked(), - Number::New(isolate, heap_stats.total_heap_size())) + #define SET_HEAP_STAT_NUMBER(name) \ + stats->Set(currentContext, \ + String::NewFromUtf8(isolate, #name).ToLocalChecked(), \ + Number::New(isolate, heap_stats.name())) \ .Check(); - stats - ->Set(currentContext, - String::NewFromUtf8(isolate, "total_heap_size_executable") - .ToLocalChecked(), - Number::New(isolate, heap_stats.total_heap_size_executable())) - .Check(); - stats - ->Set( - currentContext, - String::NewFromUtf8(isolate, "total_physical_size").ToLocalChecked(), - Number::New(isolate, heap_stats.total_physical_size())) - .Check(); - stats - ->Set( - currentContext, - String::NewFromUtf8(isolate, "total_available_size").ToLocalChecked(), - Number::New(isolate, heap_stats.total_available_size())) - .Check(); - stats - ->Set(currentContext, - String::NewFromUtf8(isolate, "used_heap_size").ToLocalChecked(), - Number::New(isolate, heap_stats.used_heap_size())) - .Check(); - stats - ->Set(currentContext, - String::NewFromUtf8(isolate, "heap_size_limit").ToLocalChecked(), - Number::New(isolate, heap_stats.heap_size_limit())) - .Check(); - stats - ->Set(currentContext, - String::NewFromUtf8(isolate, "malloced_memory").ToLocalChecked(), - Number::New(isolate, heap_stats.malloced_memory())) - .Check(); - stats - ->Set( - currentContext, - String::NewFromUtf8(isolate, "peak_malloced_memory").ToLocalChecked(), - Number::New(isolate, heap_stats.peak_malloced_memory())) - .Check(); - stats - ->Set(currentContext, - String::NewFromUtf8(isolate, "does_zap_garbage").ToLocalChecked(), - Boolean::New(isolate, heap_stats.does_zap_garbage())) - .Check(); - stats - ->Set(currentContext, - String::NewFromUtf8(isolate, "number_of_native_contexts") - .ToLocalChecked(), - Number::New(isolate, heap_stats.number_of_native_contexts())) - .Check(); - stats - ->Set(currentContext, - String::NewFromUtf8(isolate, "number_of_detached_contexts") - .ToLocalChecked(), - Number::New(isolate, heap_stats.number_of_detached_contexts())) - .Check(); - stats - ->Set(currentContext, - String::NewFromUtf8(isolate, "total_global_handles_size") - .ToLocalChecked(), - Number::New(isolate, heap_stats.total_global_handles_size())) - .Check(); - stats - ->Set(currentContext, - String::NewFromUtf8(isolate, "used_global_handles_size") - .ToLocalChecked(), - Number::New(isolate, heap_stats.used_global_handles_size())) - .Check(); - stats - ->Set(currentContext, - String::NewFromUtf8(isolate, "external_memory").ToLocalChecked(), - Number::New(isolate, heap_stats.external_memory())) + + #define SET_HEAP_STAT_BOOLEAN(name) \ + stats->Set(currentContext, \ + String::NewFromUtf8(isolate, #name).ToLocalChecked(), \ + Boolean::New(isolate, heap_stats.name())) \ .Check(); + // Numeric stats + SET_HEAP_STAT_NUMBER(total_heap_size) + SET_HEAP_STAT_NUMBER(total_heap_size_executable) + SET_HEAP_STAT_NUMBER(total_physical_size) + SET_HEAP_STAT_NUMBER(total_available_size) + SET_HEAP_STAT_NUMBER(used_heap_size) + SET_HEAP_STAT_NUMBER(heap_size_limit) + SET_HEAP_STAT_NUMBER(malloced_memory) + SET_HEAP_STAT_NUMBER(peak_malloced_memory) + SET_HEAP_STAT_NUMBER(number_of_native_contexts) + SET_HEAP_STAT_NUMBER(number_of_detached_contexts) + SET_HEAP_STAT_NUMBER(total_global_handles_size) + SET_HEAP_STAT_NUMBER(used_global_handles_size) + SET_HEAP_STAT_NUMBER(external_memory) + + // Boolean stat + SET_HEAP_STAT_BOOLEAN(does_zap_garbage) + + #undef SET_HEAP_STAT_NUMBER + #undef SET_HEAP_STAT_BOOLEAN + args.GetReturnValue().Set(stats); } From 76d40273e3a90b959e3e226115683cb24ab9e103 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 19:01:39 +0200 Subject: [PATCH 08/19] object initialization Signed-off-by: Matteo Collina --- src/node_worker.cc | 76 +++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/src/node_worker.cc b/src/node_worker.cc index af8225d0771c77..ed4ecfe61c54de 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -826,40 +826,48 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { auto* isolate = args.GetIsolate(); Local currentContext = isolate->GetCurrentContext(); - Local stats = Object::New(isolate); - - #define SET_HEAP_STAT_NUMBER(name) \ - stats->Set(currentContext, \ - String::NewFromUtf8(isolate, #name).ToLocalChecked(), \ - Number::New(isolate, heap_stats.name())) \ - .Check(); - - #define SET_HEAP_STAT_BOOLEAN(name) \ - stats->Set(currentContext, \ - String::NewFromUtf8(isolate, #name).ToLocalChecked(), \ - Boolean::New(isolate, heap_stats.name())) \ - .Check(); - - // Numeric stats - SET_HEAP_STAT_NUMBER(total_heap_size) - SET_HEAP_STAT_NUMBER(total_heap_size_executable) - SET_HEAP_STAT_NUMBER(total_physical_size) - SET_HEAP_STAT_NUMBER(total_available_size) - SET_HEAP_STAT_NUMBER(used_heap_size) - SET_HEAP_STAT_NUMBER(heap_size_limit) - SET_HEAP_STAT_NUMBER(malloced_memory) - SET_HEAP_STAT_NUMBER(peak_malloced_memory) - SET_HEAP_STAT_NUMBER(number_of_native_contexts) - SET_HEAP_STAT_NUMBER(number_of_detached_contexts) - SET_HEAP_STAT_NUMBER(total_global_handles_size) - SET_HEAP_STAT_NUMBER(used_global_handles_size) - SET_HEAP_STAT_NUMBER(external_memory) - - // Boolean stat - SET_HEAP_STAT_BOOLEAN(does_zap_garbage) - - #undef SET_HEAP_STAT_NUMBER - #undef SET_HEAP_STAT_BOOLEAN + // Define an array of property names + Local heap_stats_names[] = { + FIXED_ONE_BYTE_STRING(isolate, "total_heap_size"), + FIXED_ONE_BYTE_STRING(isolate, "total_heap_size_executable"), + FIXED_ONE_BYTE_STRING(isolate, "total_physical_size"), + FIXED_ONE_BYTE_STRING(isolate, "total_available_size"), + FIXED_ONE_BYTE_STRING(isolate, "used_heap_size"), + FIXED_ONE_BYTE_STRING(isolate, "heap_size_limit"), + FIXED_ONE_BYTE_STRING(isolate, "malloced_memory"), + FIXED_ONE_BYTE_STRING(isolate, "peak_malloced_memory"), + FIXED_ONE_BYTE_STRING(isolate, "does_zap_garbage"), + FIXED_ONE_BYTE_STRING(isolate, "number_of_native_contexts"), + FIXED_ONE_BYTE_STRING(isolate, "number_of_detached_contexts"), + FIXED_ONE_BYTE_STRING(isolate, "total_global_handles_size"), + FIXED_ONE_BYTE_STRING(isolate, "used_global_handles_size"), + FIXED_ONE_BYTE_STRING(isolate, "external_memory") + }; + + // Define an array of property values + Local heap_stats_values[] = { + Number::New(isolate, heap_stats.total_heap_size()), + Number::New(isolate, heap_stats.total_heap_size_executable()), + Number::New(isolate, heap_stats.total_physical_size()), + Number::New(isolate, heap_stats.total_available_size()), + Number::New(isolate, heap_stats.used_heap_size()), + Number::New(isolate, heap_stats.heap_size_limit()), + Number::New(isolate, heap_stats.malloced_memory()), + Number::New(isolate, heap_stats.peak_malloced_memory()), + Boolean::New(isolate, heap_stats.does_zap_garbage()), + Number::New(isolate, heap_stats.number_of_native_contexts()), + Number::New(isolate, heap_stats.number_of_detached_contexts()), + Number::New(isolate, heap_stats.total_global_handles_size()), + Number::New(isolate, heap_stats.used_global_handles_size()), + Number::New(isolate, heap_stats.external_memory()) + }; + + // Create the object with the property names and values + Local stats = Object::New(isolate, + Null(isolate), + heap_stats_names, + heap_stats_values, + arraysize(heap_stats_names)); args.GetReturnValue().Set(stats); } From 47bd934ba9763b491d484098d53d414b1896d10d Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 19:41:01 +0200 Subject: [PATCH 09/19] refactor to use an interrupt Signed-off-by: Matteo Collina --- doc/api/worker_threads.md | 7 +- lib/internal/worker.js | 9 +- src/async_wrap.h | 1 + src/env_properties.h | 1 + src/node_worker.cc | 127 +++++++++++++++++++ test/parallel/test-worker-heap-statistics.js | 11 +- 6 files changed, 149 insertions(+), 7 deletions(-) diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index a2306174c6ba0b..813efe731fc0a9 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -1343,10 +1343,11 @@ immediately with an [`ERR_WORKER_NOT_RUNNING`][] error. added: REPLACEME --> -* Returns: {Object} +* Returns: {Promise} -This method is identical to [`v8.getHeapStatistics()`][] but it allows the -statistics to be observed from outside the actual thread. +This method returns a `Promise` that will resolve to an object identical to [`v8.getHeapStatistics()`][], +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.performance` diff --git a/lib/internal/worker.js b/lib/internal/worker.js index c4e95dcfb77537..8b43dcac320777 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -461,9 +461,14 @@ class Worker extends EventEmitter { } getHeapStatistics() { - if (this[kHandle] === null) return {}; + const taker = this[kHandle]?.getHeapStatistics(); - return this[kHandle].getHeapStatistics(); + return new Promise((resolve, reject) => { + if (!taker) return reject(new ERR_WORKER_NOT_RUNNING()); + taker.ondone = (handle) => { + resolve(handle); + }; + }); } } diff --git a/src/async_wrap.h b/src/async_wrap.h index 5b33290b4bb2d0..808c653d4c5c98 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -79,6 +79,7 @@ namespace node { V(SIGINTWATCHDOG) \ V(WORKER) \ V(WORKERHEAPSNAPSHOT) \ + V(WORKERHEAPSTATISTICS) \ V(WRITEWRAP) \ V(ZLIB) diff --git a/src/env_properties.h b/src/env_properties.h index 6ccc581034f4b2..a92e06113291ca 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -464,6 +464,7 @@ V(tty_constructor_template, v8::FunctionTemplate) \ V(write_wrap_template, v8::ObjectTemplate) \ V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \ + V(worker_heap_statistics_taker_template, v8::ObjectTemplate) \ V(x509_constructor_template, v8::FunctionTemplate) #define PER_REALM_STRONG_PERSISTENT_VALUES(V) \ diff --git a/src/node_worker.cc b/src/node_worker.cc index ed4ecfe61c54de..6c4bd07f6ac61b 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -816,6 +816,7 @@ void Worker::Unref(const FunctionCallbackInfo& args) { } } +/* void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { Worker* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); @@ -871,6 +872,118 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(stats); } +*/ + +class WorkerHeapStatisticsTaker : public AsyncWrap { + public: + WorkerHeapStatisticsTaker(Environment* env, Local obj) + : AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERHEAPSTATISTICS) {} + + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(WorkerHeapStatisticsTaker) + SET_SELF_SIZE(WorkerHeapStatisticsTaker) +}; + +void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { + Worker* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); + + Debug(w, "Worker %llu getting heap statistics", w->thread_id_.id); + + Environment* env = w->env(); + AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); + Local wrap; + if (!env->worker_heap_statistics_taker_template() + ->NewInstance(env->context()).ToLocal(&wrap)) { + return; + } + + // The created WorkerHeapStatisticsTaker is an object owned by main + // thread's Isolate, it can not be accessed by worker thread + std::unique_ptr> taker = + std::make_unique>( + MakeDetachedBaseObject(env, wrap)); + + // Interrupt the worker thread and take a snapshot, then schedule a call + // on the parent thread that turns that snapshot into a readable stream. + bool scheduled = w->RequestInterrupt([taker = std::move(taker), env]( + Environment* worker_env) mutable { + + v8::HeapStatistics heap_stats; + worker_env->isolate()->GetHeapStatistics(&heap_stats); + + // Here, the worker thread temporarily owns the WorkerHeapStatisticsTaker + // object. + + env->SetImmediateThreadsafe( + [taker = std::move(taker), + heap_stats = std::move(heap_stats)](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 heap_stats_names[] = { + FIXED_ONE_BYTE_STRING(isolate, "total_heap_size"), + FIXED_ONE_BYTE_STRING(isolate, "total_heap_size_executable"), + FIXED_ONE_BYTE_STRING(isolate, "total_physical_size"), + FIXED_ONE_BYTE_STRING(isolate, "total_available_size"), + FIXED_ONE_BYTE_STRING(isolate, "used_heap_size"), + FIXED_ONE_BYTE_STRING(isolate, "heap_size_limit"), + FIXED_ONE_BYTE_STRING(isolate, "malloced_memory"), + FIXED_ONE_BYTE_STRING(isolate, "peak_malloced_memory"), + FIXED_ONE_BYTE_STRING(isolate, "does_zap_garbage"), + FIXED_ONE_BYTE_STRING(isolate, "number_of_native_contexts"), + FIXED_ONE_BYTE_STRING(isolate, "number_of_detached_contexts"), + FIXED_ONE_BYTE_STRING(isolate, "total_global_handles_size"), + FIXED_ONE_BYTE_STRING(isolate, "used_global_handles_size"), + FIXED_ONE_BYTE_STRING(isolate, "external_memory") + }; + + // Define an array of property values + Local heap_stats_values[] = { + Number::New(isolate, heap_stats.total_heap_size()), + Number::New(isolate, heap_stats.total_heap_size_executable()), + Number::New(isolate, heap_stats.total_physical_size()), + Number::New(isolate, heap_stats.total_available_size()), + Number::New(isolate, heap_stats.used_heap_size()), + Number::New(isolate, heap_stats.heap_size_limit()), + Number::New(isolate, heap_stats.malloced_memory()), + Number::New(isolate, heap_stats.peak_malloced_memory()), + Boolean::New(isolate, heap_stats.does_zap_garbage()), + Number::New(isolate, heap_stats.number_of_native_contexts()), + Number::New(isolate, heap_stats.number_of_detached_contexts()), + Number::New(isolate, heap_stats.total_global_handles_size()), + Number::New(isolate, heap_stats.used_global_handles_size()), + Number::New(isolate, heap_stats.external_memory()) + }; + + // Create the object with the property names and values + Local stats = Object::New(isolate, + Null(isolate), + heap_stats_names, + heap_stats_values, + arraysize(heap_stats_names)); + + + Local args[] = {stats}; + taker->get()->MakeCallback( + env->ondone_string(), arraysize(args), args); + // implicitly delete `taker` + }, + CallbackFlags::kUnrefed); + + // Now, the lambda is delivered to the main thread, as a result, the + // WorkerHeapSnapshotTaker object is delivered to the main thread, too. + }); + + if (scheduled) { + args.GetReturnValue().Set(wrap); + } else { + args.GetReturnValue().Set(Local()); + } +} void Worker::GetResourceLimits(const FunctionCallbackInfo& args) { Worker* w; @@ -1071,6 +1184,20 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data, wst->InstanceTemplate()); } + { + Local wst = NewFunctionTemplate(isolate, nullptr); + + wst->InstanceTemplate()->SetInternalFieldCount( + WorkerHeapSnapshotTaker::kInternalFieldCount); + wst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); + + Local wst_string = + FIXED_ONE_BYTE_STRING(isolate, "WorkerHeapStatisticsTaker"); + wst->SetClassName(wst_string); + isolate_data->set_worker_heap_statistics_taker_template( + wst->InstanceTemplate()); + } + SetMethod(isolate, target, "getEnvMessagePort", GetEnvMessagePort); } diff --git a/test/parallel/test-worker-heap-statistics.js b/test/parallel/test-worker-heap-statistics.js index a9d8f996282581..175a55bbce9555 100644 --- a/test/parallel/test-worker-heap-statistics.js +++ b/test/parallel/test-worker-heap-statistics.js @@ -23,8 +23,8 @@ if (isMainThread) { const worker = new Worker(fixtures.path('worker-name.js'), { name, }); - worker.once('message', common.mustCall((message) => { - const stats = worker.getHeapStatistics(); + worker.once('message', common.mustCall(async (message) => { + const stats = await worker.getHeapStatistics(); const keys = [ `total_heap_size`, `total_heap_size_executable`, @@ -54,4 +54,11 @@ if (isMainThread) { worker.postMessage('done'); })); + + worker.once('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + assert.rejects(worker.getHeapStatistics(), { + code: 'ERR_WORKER_NOT_RUNNING' + }); + })); } From ea24ef464ee70c933e9a9235fa267bf5b4a200b5 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 19:43:48 +0200 Subject: [PATCH 10/19] fixup Signed-off-by: Matteo Collina --- src/async_wrap.h | 2 +- src/env_properties.h | 2 +- src/node_worker.cc | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/async_wrap.h b/src/async_wrap.h index 808c653d4c5c98..ab066e826b3027 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -79,7 +79,7 @@ namespace node { V(SIGINTWATCHDOG) \ V(WORKER) \ V(WORKERHEAPSNAPSHOT) \ - V(WORKERHEAPSTATISTICS) \ + V(WORKERHEAPSTATISTICS) \ V(WRITEWRAP) \ V(ZLIB) diff --git a/src/env_properties.h b/src/env_properties.h index a92e06113291ca..1bc9f25cd58002 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -464,7 +464,7 @@ V(tty_constructor_template, v8::FunctionTemplate) \ V(write_wrap_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) #define PER_REALM_STRONG_PERSISTENT_VALUES(V) \ diff --git a/src/node_worker.cc b/src/node_worker.cc index 6c4bd07f6ac61b..98a1b69e12fe21 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -908,7 +908,6 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { // on the parent thread that turns that snapshot into a readable stream. bool scheduled = w->RequestInterrupt([taker = std::move(taker), env]( Environment* worker_env) mutable { - v8::HeapStatistics heap_stats; worker_env->isolate()->GetHeapStatistics(&heap_stats); From 821744f7c0a2d87dbe47099c33cbdcb63351750e Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 19:44:21 +0200 Subject: [PATCH 11/19] fixup Signed-off-by: Matteo Collina --- src/node_worker.cc | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/node_worker.cc b/src/node_worker.cc index 98a1b69e12fe21..7c747f7474a8cd 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -877,7 +877,7 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { class WorkerHeapStatisticsTaker : public AsyncWrap { public: WorkerHeapStatisticsTaker(Environment* env, Local obj) - : AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERHEAPSTATISTICS) {} + : AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERHEAPSTATISTICS) {} SET_NO_MEMORY_INFO() SET_MEMORY_INFO_NAME(WorkerHeapStatisticsTaker) @@ -894,7 +894,8 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); Local wrap; if (!env->worker_heap_statistics_taker_template() - ->NewInstance(env->context()).ToLocal(&wrap)) { + ->NewInstance(env->context()) + .ToLocal(&wrap)) { return; } @@ -906,8 +907,8 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { // Interrupt the worker thread and take a snapshot, then schedule a call // on the parent thread that turns that snapshot into a readable stream. - bool scheduled = w->RequestInterrupt([taker = std::move(taker), env]( - Environment* worker_env) mutable { + bool scheduled = w->RequestInterrupt([taker = std::move(taker), + env](Environment* worker_env) mutable { v8::HeapStatistics heap_stats; worker_env->isolate()->GetHeapStatistics(&heap_stats); @@ -937,8 +938,7 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { FIXED_ONE_BYTE_STRING(isolate, "number_of_detached_contexts"), FIXED_ONE_BYTE_STRING(isolate, "total_global_handles_size"), FIXED_ONE_BYTE_STRING(isolate, "used_global_handles_size"), - FIXED_ONE_BYTE_STRING(isolate, "external_memory") - }; + FIXED_ONE_BYTE_STRING(isolate, "external_memory")}; // Define an array of property values Local heap_stats_values[] = { @@ -955,16 +955,14 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { Number::New(isolate, heap_stats.number_of_detached_contexts()), Number::New(isolate, heap_stats.total_global_handles_size()), Number::New(isolate, heap_stats.used_global_handles_size()), - Number::New(isolate, heap_stats.external_memory()) - }; + Number::New(isolate, heap_stats.external_memory())}; // Create the object with the property names and values Local stats = Object::New(isolate, - Null(isolate), - heap_stats_names, - heap_stats_values, - arraysize(heap_stats_names)); - + Null(isolate), + heap_stats_names, + heap_stats_values, + arraysize(heap_stats_names)); Local args[] = {stats}; taker->get()->MakeCallback( From 8236424ed4043b0d816642b797f04788834cb851 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 19:45:14 +0200 Subject: [PATCH 12/19] linting Signed-off-by: Matteo Collina --- test/parallel/test-worker-heap-statistics.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-worker-heap-statistics.js b/test/parallel/test-worker-heap-statistics.js index 175a55bbce9555..cd262180052d1e 100644 --- a/test/parallel/test-worker-heap-statistics.js +++ b/test/parallel/test-worker-heap-statistics.js @@ -55,9 +55,9 @@ if (isMainThread) { worker.postMessage('done'); })); - worker.once('exit', common.mustCall((code) => { + worker.once('exit', common.mustCall(async (code) => { assert.strictEqual(code, 0); - assert.rejects(worker.getHeapStatistics(), { + await assert.rejects(worker.getHeapStatistics(), { code: 'ERR_WORKER_NOT_RUNNING' }); })); From ab5f978749c264e248e16d16b5261a42a7207056 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 19:53:08 +0200 Subject: [PATCH 13/19] fixup Signed-off-by: Matteo Collina --- test/sequential/test-async-wrap-getasyncid.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/sequential/test-async-wrap-getasyncid.js b/test/sequential/test-async-wrap-getasyncid.js index cd5957de11e157..a75207b66e6633 100644 --- a/test/sequential/test-async-wrap-getasyncid.js +++ b/test/sequential/test-async-wrap-getasyncid.js @@ -61,6 +61,7 @@ const { getSystemErrorName } = require('util'); delete providers.ELDHISTOGRAM; delete providers.SIGINTWATCHDOG; delete providers.WORKERHEAPSNAPSHOT; + delete providers.WORKERHEAPSTATISTICS; delete providers.BLOBREADER; delete providers.RANDOMPRIMEREQUEST; delete providers.CHECKPRIMEREQUEST; From a3e2066c8cb3bb887427a08893f5128c0f366f04 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 19:57:50 +0200 Subject: [PATCH 14/19] fixup Signed-off-by: Matteo Collina --- src/node_worker.cc | 58 ---------------------------------------------- 1 file changed, 58 deletions(-) diff --git a/src/node_worker.cc b/src/node_worker.cc index 7c747f7474a8cd..9f416504ca2353 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -816,64 +816,6 @@ void Worker::Unref(const FunctionCallbackInfo& args) { } } -/* -void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { - Worker* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); - - v8::HeapStatistics heap_stats; - w->isolate_->GetHeapStatistics(&heap_stats); - - auto* isolate = args.GetIsolate(); - Local currentContext = isolate->GetCurrentContext(); - - // Define an array of property names - Local heap_stats_names[] = { - FIXED_ONE_BYTE_STRING(isolate, "total_heap_size"), - FIXED_ONE_BYTE_STRING(isolate, "total_heap_size_executable"), - FIXED_ONE_BYTE_STRING(isolate, "total_physical_size"), - FIXED_ONE_BYTE_STRING(isolate, "total_available_size"), - FIXED_ONE_BYTE_STRING(isolate, "used_heap_size"), - FIXED_ONE_BYTE_STRING(isolate, "heap_size_limit"), - FIXED_ONE_BYTE_STRING(isolate, "malloced_memory"), - FIXED_ONE_BYTE_STRING(isolate, "peak_malloced_memory"), - FIXED_ONE_BYTE_STRING(isolate, "does_zap_garbage"), - FIXED_ONE_BYTE_STRING(isolate, "number_of_native_contexts"), - FIXED_ONE_BYTE_STRING(isolate, "number_of_detached_contexts"), - FIXED_ONE_BYTE_STRING(isolate, "total_global_handles_size"), - FIXED_ONE_BYTE_STRING(isolate, "used_global_handles_size"), - FIXED_ONE_BYTE_STRING(isolate, "external_memory") - }; - - // Define an array of property values - Local heap_stats_values[] = { - Number::New(isolate, heap_stats.total_heap_size()), - Number::New(isolate, heap_stats.total_heap_size_executable()), - Number::New(isolate, heap_stats.total_physical_size()), - Number::New(isolate, heap_stats.total_available_size()), - Number::New(isolate, heap_stats.used_heap_size()), - Number::New(isolate, heap_stats.heap_size_limit()), - Number::New(isolate, heap_stats.malloced_memory()), - Number::New(isolate, heap_stats.peak_malloced_memory()), - Boolean::New(isolate, heap_stats.does_zap_garbage()), - Number::New(isolate, heap_stats.number_of_native_contexts()), - Number::New(isolate, heap_stats.number_of_detached_contexts()), - Number::New(isolate, heap_stats.total_global_handles_size()), - Number::New(isolate, heap_stats.used_global_handles_size()), - Number::New(isolate, heap_stats.external_memory()) - }; - - // Create the object with the property names and values - Local stats = Object::New(isolate, - Null(isolate), - heap_stats_names, - heap_stats_values, - arraysize(heap_stats_names)); - - args.GetReturnValue().Set(stats); -} -*/ - class WorkerHeapStatisticsTaker : public AsyncWrap { public: WorkerHeapStatisticsTaker(Environment* env, Local obj) From 99c6b806c919b13c7d11d0bab2855ff32992949c Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 19:59:56 +0200 Subject: [PATCH 15/19] fixup Signed-off-by: Matteo Collina --- src/node_worker.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_worker.cc b/src/node_worker.cc index 9f416504ca2353..3a6eab2cafc3c2 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -914,7 +914,7 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { CallbackFlags::kUnrefed); // Now, the lambda is delivered to the main thread, as a result, the - // WorkerHeapSnapshotTaker object is delivered to the main thread, too. + // WorkerHeapStatisticsTaker object is delivered to the main thread, too. }); if (scheduled) { From c7ab253549d63d44c5d0c66cb1e62ea97a795df4 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 15 Apr 2025 21:50:47 -0700 Subject: [PATCH 16/19] Update src/node_worker.cc Co-authored-by: Yagiz Nizipli --- src/node_worker.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/node_worker.cc b/src/node_worker.cc index 3a6eab2cafc3c2..a432dc710010c0 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -830,7 +830,6 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { Worker* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); - Debug(w, "Worker %llu getting heap statistics", w->thread_id_.id); Environment* env = w->env(); AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); From e40a2206ff4f813ff100907a2637de05879a58ca Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 16 Apr 2025 07:00:35 +0200 Subject: [PATCH 17/19] review feedbacks Signed-off-by: Matteo Collina --- src/node_worker.cc | 37 ++++++++++++++++------------- typings/internalBinding/worker.d.ts | 1 + 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/node_worker.cc b/src/node_worker.cc index a432dc710010c0..d44191c381e8f8 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -850,8 +850,10 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { // on the parent thread that turns that snapshot into a readable stream. bool scheduled = w->RequestInterrupt([taker = std::move(taker), env](Environment* worker_env) mutable { - v8::HeapStatistics heap_stats; - worker_env->isolate()->GetHeapStatistics(&heap_stats); + // We create a unique pointer to HeapStatistics so that the actual object + // it's not copied in the lambda, but only the pointer is. + auto heap_stats = std::make_unique(); + worker_env->isolate()->GetHeapStatistics(heap_stats.get()); // Here, the worker thread temporarily owns the WorkerHeapStatisticsTaker // object. @@ -883,20 +885,23 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { // Define an array of property values Local heap_stats_values[] = { - Number::New(isolate, heap_stats.total_heap_size()), - Number::New(isolate, heap_stats.total_heap_size_executable()), - Number::New(isolate, heap_stats.total_physical_size()), - Number::New(isolate, heap_stats.total_available_size()), - Number::New(isolate, heap_stats.used_heap_size()), - Number::New(isolate, heap_stats.heap_size_limit()), - Number::New(isolate, heap_stats.malloced_memory()), - Number::New(isolate, heap_stats.peak_malloced_memory()), - Boolean::New(isolate, heap_stats.does_zap_garbage()), - Number::New(isolate, heap_stats.number_of_native_contexts()), - Number::New(isolate, heap_stats.number_of_detached_contexts()), - Number::New(isolate, heap_stats.total_global_handles_size()), - Number::New(isolate, heap_stats.used_global_handles_size()), - Number::New(isolate, heap_stats.external_memory())}; + Number::New(isolate, heap_stats->total_heap_size()), + Number::New(isolate, heap_stats->total_heap_size_executable()), + Number::New(isolate, heap_stats->total_physical_size()), + Number::New(isolate, heap_stats->total_available_size()), + Number::New(isolate, heap_stats->used_heap_size()), + Number::New(isolate, heap_stats->heap_size_limit()), + Number::New(isolate, heap_stats->malloced_memory()), + Number::New(isolate, heap_stats->peak_malloced_memory()), + Boolean::New(isolate, heap_stats->does_zap_garbage()), + Number::New(isolate, heap_stats->number_of_native_contexts()), + Number::New(isolate, heap_stats->number_of_detached_contexts()), + Number::New(isolate, heap_stats->total_global_handles_size()), + Number::New(isolate, heap_stats->used_global_handles_size()), + Number::New(isolate, heap_stats->external_memory())}; + + DCHECK_EQ( + arraysize(heap_stats_names), arraysize(heap_stats_values)); // Create the object with the property names and values Local stats = Object::New(isolate, diff --git a/typings/internalBinding/worker.d.ts b/typings/internalBinding/worker.d.ts index 10cb5fc5db6e32..0a316aaf5e5bff 100644 --- a/typings/internalBinding/worker.d.ts +++ b/typings/internalBinding/worker.d.ts @@ -15,6 +15,7 @@ declare namespace InternalWorkerBinding { unref(): void; getResourceLimits(): Float64Array; takeHeapSnapshot(): object; + getHeapStatistics(): Promise; loopIdleTime(): number; loopStartTime(): number; } From 645d972c082edc509d60847448985cd7a6839906 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 16 Apr 2025 07:07:15 +0200 Subject: [PATCH 18/19] fixup Signed-off-by: Matteo Collina --- src/node_worker.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/node_worker.cc b/src/node_worker.cc index d44191c381e8f8..25be2bca2cd23f 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -830,7 +830,6 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { Worker* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); - Environment* env = w->env(); AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); Local wrap; @@ -900,8 +899,7 @@ void Worker::GetHeapStatistics(const FunctionCallbackInfo& args) { Number::New(isolate, heap_stats->used_global_handles_size()), Number::New(isolate, heap_stats->external_memory())}; - DCHECK_EQ( - arraysize(heap_stats_names), arraysize(heap_stats_values)); + DCHECK_EQ(arraysize(heap_stats_names), arraysize(heap_stats_values)); // Create the object with the property names and values Local stats = Object::New(isolate, From df57fa325a0be20398b6545d2f5c41178eee6fbe Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 16 Apr 2025 15:35:23 +0200 Subject: [PATCH 19/19] fixup Signed-off-by: Matteo Collina --- test/parallel/test-worker-heap-statistics.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/parallel/test-worker-heap-statistics.js b/test/parallel/test-worker-heap-statistics.js index cd262180052d1e..12a748c303a026 100644 --- a/test/parallel/test-worker-heap-statistics.js +++ b/test/parallel/test-worker-heap-statistics.js @@ -45,7 +45,6 @@ if (isMainThread) { for (const key of keys) { if (key === 'does_zap_garbage') { assert.strictEqual(typeof stats[key], 'boolean', `Expected ${key} to be a boolean`); - assert.strictEqual(stats[key], false); continue; } assert.strictEqual(typeof stats[key], 'number', `Expected ${key} to be a number`);