Skip to content

Commit 85d6960

Browse files
committed
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 <hello@matteocollina.com>
1 parent ca74d64 commit 85d6960

File tree

5 files changed

+156
-1
lines changed

5 files changed

+156
-1
lines changed

doc/api/worker_threads.md

+27-1
Original file line numberDiff line numberDiff line change
@@ -1337,6 +1337,31 @@ If the Worker thread is no longer running, which may occur before the
13371337
[`'exit'` event][] is emitted, the returned `Promise` is rejected
13381338
immediately with an [`ERR_WORKER_NOT_RUNNING`][] error.
13391339

1340+
### `worker.getHeapStatistics()`
1341+
1342+
<!-- YAML
1343+
added: REPLACEME
1344+
-->
1345+
1346+
* returns: {object}
1347+
* `total_heap_size` {number}
1348+
* `total_heap_size_executable` {number}
1349+
* `total_physical_size` {number}
1350+
* `total_available_size` {number}
1351+
* `used_heap_size` {number}
1352+
* `heap_size_limit` {number}
1353+
* `malloced_memory` {number}
1354+
* `peak_malloced_memory` {number}
1355+
* `does_zap_garbage` {number}
1356+
* `number_of_native_contexts` {number}
1357+
* `number_of_detached_contexts` {number}
1358+
* `total_global_handles_size` {number}
1359+
* `used_global_handles_size` {number}
1360+
* `external_memory` {number}
1361+
1362+
This method is identical to [`v8.getHeapStatistics()`][] but it allows the
1363+
statistics to be observed from outside the actual thread.
1364+
13401365
### `worker.performance`
13411366

13421367
<!-- YAML
@@ -1362,7 +1387,7 @@ added:
13621387
`eventLoopUtilization()`.
13631388
* `utilization2` {Object} The result of a previous call to
13641389
`eventLoopUtilization()` prior to `utilization1`.
1365-
* Returns: {Object}
1390+
* returns: {object}
13661391
* `idle` {number}
13671392
* `active` {number}
13681393
* `utilization` {number}
@@ -1631,6 +1656,7 @@ thread spawned will spawn another until the application crashes.
16311656
[`require('node:worker_threads').workerData`]: #workerworkerdata
16321657
[`trace_events`]: tracing.md
16331658
[`v8.getHeapSnapshot()`]: v8.md#v8getheapsnapshotoptions
1659+
[`v8.getHeapStatistics()`]: v8.md#v8getheapstatistics
16341660
[`vm`]: vm.md
16351661
[`worker.SHARE_ENV`]: #workershare_env
16361662
[`worker.on('message')`]: #event-message_1

lib/internal/worker.js

+6
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,12 @@ class Worker extends EventEmitter {
459459
};
460460
});
461461
}
462+
463+
getHeapStatistics() {
464+
if (this[kHandle] === null) return {};
465+
466+
return this[kHandle].getHeapStatistics();
467+
}
462468
}
463469

464470
/**

src/node_worker.cc

+64
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,68 @@ void Worker::Unref(const FunctionCallbackInfo<Value>& args) {
812812
}
813813
}
814814

815+
void Worker::GetHeapStatistics(const FunctionCallbackInfo<Value>& args) {
816+
Worker* w;
817+
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
818+
819+
v8::HeapStatistics heap_stats;
820+
w->isolate_->GetHeapStatistics(&heap_stats);
821+
822+
auto* isolate = args.GetIsolate();
823+
Local<Context> currentContext = isolate->GetCurrentContext();
824+
825+
Local<Object> stats = Object::New(isolate);
826+
827+
stats->Set(currentContext,
828+
String::NewFromUtf8(isolate, "total_heap_size").ToLocalChecked(),
829+
Number::New(isolate, heap_stats.total_heap_size()));
830+
stats->Set(currentContext,
831+
String::NewFromUtf8(isolate, "total_heap_size_executable")
832+
.ToLocalChecked(),
833+
Number::New(isolate, heap_stats.total_heap_size_executable()));
834+
stats->Set(currentContext,
835+
String::NewFromUtf8(isolate, "total_physical_size").ToLocalChecked(),
836+
Number::New(isolate, heap_stats.total_physical_size()));
837+
stats->Set(currentContext,
838+
String::NewFromUtf8(isolate, "total_available_size").ToLocalChecked(),
839+
Number::New(isolate, heap_stats.total_available_size()));
840+
stats->Set(currentContext,
841+
String::NewFromUtf8(isolate, "used_heap_size").ToLocalChecked(),
842+
Number::New(isolate, heap_stats.used_heap_size()));
843+
stats->Set(currentContext,
844+
String::NewFromUtf8(isolate, "heap_size_limit").ToLocalChecked(),
845+
Number::New(isolate, heap_stats.heap_size_limit()));
846+
stats->Set(currentContext,
847+
String::NewFromUtf8(isolate, "malloced_memory").ToLocalChecked(),
848+
Number::New(isolate, heap_stats.malloced_memory()));
849+
stats->Set(currentContext,
850+
String::NewFromUtf8(isolate, "peak_malloced_memory").ToLocalChecked(),
851+
Number::New(isolate, heap_stats.peak_malloced_memory()));
852+
stats->Set(currentContext,
853+
String::NewFromUtf8(isolate, "does_zap_garbage").ToLocalChecked(),
854+
Boolean::New(isolate, heap_stats.does_zap_garbage()));
855+
stats->Set(currentContext,
856+
String::NewFromUtf8(isolate, "number_of_native_contexts")
857+
.ToLocalChecked(),
858+
Number::New(isolate, heap_stats.number_of_native_contexts()));
859+
stats->Set(currentContext,
860+
String::NewFromUtf8(isolate, "number_of_detached_contexts")
861+
.ToLocalChecked(),
862+
Number::New(isolate, heap_stats.number_of_detached_contexts()));
863+
stats->Set(currentContext,
864+
String::NewFromUtf8(isolate, "total_global_handles_size")
865+
.ToLocalChecked(),
866+
Number::New(isolate, heap_stats.total_global_handles_size()));
867+
stats->Set(currentContext,
868+
String::NewFromUtf8(isolate, "used_global_handles_size").ToLocalChecked(),
869+
Number::New(isolate, heap_stats.used_global_handles_size()));
870+
stats->Set(currentContext,
871+
String::NewFromUtf8(isolate, "external_memory").ToLocalChecked(),
872+
Number::New(isolate, heap_stats.external_memory()));
873+
874+
args.GetReturnValue().Set(stats);
875+
}
876+
815877
void Worker::GetResourceLimits(const FunctionCallbackInfo<Value>& args) {
816878
Worker* w;
817879
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
@@ -992,6 +1054,8 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data,
9921054
SetProtoMethod(isolate, w, "takeHeapSnapshot", Worker::TakeHeapSnapshot);
9931055
SetProtoMethod(isolate, w, "loopIdleTime", Worker::LoopIdleTime);
9941056
SetProtoMethod(isolate, w, "loopStartTime", Worker::LoopStartTime);
1057+
SetProtoMethod(isolate, w, "getHeapStatistics", Worker::GetHeapStatistics);
1058+
9951059

9961060
SetConstructorFunction(isolate, target, "Worker", w);
9971061
}

src/node_worker.h

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ class Worker : public AsyncWrap {
7878
static void TakeHeapSnapshot(const v8::FunctionCallbackInfo<v8::Value>& args);
7979
static void LoopIdleTime(const v8::FunctionCallbackInfo<v8::Value>& args);
8080
static void LoopStartTime(const v8::FunctionCallbackInfo<v8::Value>& args);
81+
static void GetHeapStatistics(
82+
const v8::FunctionCallbackInfo<v8::Value>& args);
8183

8284
private:
8385
bool CreateEnvMessagePort(Environment* env);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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((message) => {
27+
const stats = 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+
assert.strictEqual(stats[key], false);
49+
continue;
50+
}
51+
assert.strictEqual(typeof stats[key], 'number', `Expected ${key} to be a number`);
52+
assert.ok(stats[key] >= 0, `Expected ${key} to be >= 0`);
53+
}
54+
55+
worker.postMessage('done');
56+
}));
57+
}

0 commit comments

Comments
 (0)