Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoyee Cheung <joyeec9h3@gmail.com>2022-10-13 15:37:22 +0300
committerJoyee Cheung <joyeec9h3@gmail.com>2022-11-08 18:19:54 +0300
commitb872d30d1956981f9579a564e601c94207f800ff (patch)
treeb997ba399b7ff78b31dc20d318a0d1009a9a2d45
parent59938e3180d97440cd38eeb6ed816f99640a1511 (diff)
lib: add options to the heap snapshot APIs
Support configuration of the HeapSnapshotMode and NumericsMode fields inf HeapSnapshotOptions in the JS APIs for heap snapshots. PR-URL: https://github.com/nodejs/node/pull/44989 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
-rw-r--r--doc/api/v8.md24
-rw-r--r--doc/api/worker_threads.md13
-rw-r--r--lib/internal/heap_utils.js19
-rw-r--r--lib/internal/worker.js10
-rw-r--r--lib/v8.js23
-rw-r--r--src/env.cc6
-rw-r--r--src/heap_utils.cc47
-rw-r--r--src/node_internals.h10
-rw-r--r--src/node_worker.cc8
-rw-r--r--test/common/heap.js34
-rw-r--r--test/fixtures/klass-with-fields.js18
-rw-r--r--test/parallel/test-worker-heapdump-failure.js15
-rw-r--r--test/sequential/test-get-heapsnapshot-options.js39
-rw-r--r--test/sequential/test-heapdump.js18
-rw-r--r--test/sequential/test-worker-heapsnapshot-options.js21
-rw-r--r--test/sequential/test-write-heapsnapshot-options.js50
16 files changed, 322 insertions, 33 deletions
diff --git a/doc/api/v8.md b/doc/api/v8.md
index ed63f44f01b..1d26ff953b4 100644
--- a/doc/api/v8.md
+++ b/doc/api/v8.md
@@ -61,13 +61,23 @@ following properties:
}
```
-## `v8.getHeapSnapshot()`
+## `v8.getHeapSnapshot([options])`
<!-- YAML
added: v11.13.0
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/44989
+ description: Support options to configure the heap snapshot.
-->
-* Returns: {stream.Readable} A Readable Stream containing the V8 heap snapshot
+* `options` {Object}
+ * `exposeInternals` {boolean} If true, expose internals in the heap snapshot.
+ **Default:** `false`.
+ * `exposeNumericValues` {boolean} If true, expose numeric values in
+ artificial fields. **Default:** `false`.
+
+* Returns: {stream.Readable} A Readable containing the V8 heap snapshot.
Generates a snapshot of the current V8 heap and returns a Readable
Stream that may be used to read the JSON serialized representation.
@@ -289,11 +299,14 @@ by [`NODE_V8_COVERAGE`][].
When the process is about to exit, one last coverage will still be written to
disk unless [`v8.stopCoverage()`][] is invoked before the process exits.
-## `v8.writeHeapSnapshot([filename])`
+## `v8.writeHeapSnapshot([filename[,options]])`
<!-- YAML
added: v11.13.0
changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/44989
+ description: Support options to configure the heap snapshot.
- version: v18.0.0
pr-url: https://github.com/nodejs/node/pull/41373
description: An exception will now be thrown if the file could not be written.
@@ -308,6 +321,11 @@ changes:
generated, where `{pid}` will be the PID of the Node.js process,
`{thread_id}` will be `0` when `writeHeapSnapshot()` is called from
the main Node.js thread or the id of a worker thread.
+* `options` {Object}
+ * `exposeInternals` {boolean} If true, expose internals in the heap snapshot.
+ **Default:** `false`.
+ * `exposeNumericValues` {boolean} If true, expose numeric values in
+ artificial fields. **Default:** `false`.
* Returns: {string} The filename where the snapshot was saved.
Generates a snapshot of the current V8 heap and writes it to a JSON
diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md
index 99772189522..694b1e74e06 100644
--- a/doc/api/worker_threads.md
+++ b/doc/api/worker_threads.md
@@ -1067,14 +1067,23 @@ added: v10.5.0
The `'online'` event is emitted when the worker thread has started executing
JavaScript code.
-### `worker.getHeapSnapshot()`
+### `worker.getHeapSnapshot([options])`
<!-- YAML
added:
- v13.9.0
- v12.17.0
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/44989
+ description: Support options to configure the heap snapshot.
-->
+* `options` {Object}
+ * `exposeInternals` {boolean} If true, expose internals in the heap snapshot.
+ **Default:** `false`.
+ * `exposeNumericValues` {boolean} If true, expose numeric values in
+ artificial fields. **Default:** `false`.
* Returns: {Promise} A promise for a Readable Stream containing
a V8 heap snapshot
@@ -1379,7 +1388,7 @@ thread spawned will spawn another until the application crashes.
[`require('node:worker_threads').threadId`]: #workerthreadid
[`require('node:worker_threads').workerData`]: #workerworkerdata
[`trace_events`]: tracing.md
-[`v8.getHeapSnapshot()`]: v8.md#v8getheapsnapshot
+[`v8.getHeapSnapshot()`]: v8.md#v8getheapsnapshotoptions
[`vm`]: vm.md
[`worker.SHARE_ENV`]: #workershare_env
[`worker.on('message')`]: #event-message_1
diff --git a/lib/internal/heap_utils.js b/lib/internal/heap_utils.js
index 126fe3f7e46..3e789845c7b 100644
--- a/lib/internal/heap_utils.js
+++ b/lib/internal/heap_utils.js
@@ -1,6 +1,7 @@
'use strict';
const {
- Symbol
+ Symbol,
+ Uint8Array,
} = primordials;
const {
kUpdateTimer,
@@ -8,9 +9,22 @@ const {
} = require('internal/stream_base_commons');
const { owner_symbol } = require('internal/async_hooks').symbols;
const { Readable } = require('stream');
+const { validateObject, validateBoolean } = require('internal/validators');
+const { kEmptyObject } = require('internal/util');
const kHandle = Symbol('kHandle');
+function getHeapSnapshotOptions(options = kEmptyObject) {
+ validateObject(options, 'options');
+ const {
+ exposeInternals = false,
+ exposeNumericValues = false,
+ } = options;
+ validateBoolean(exposeInternals, 'options.exposeInternals');
+ validateBoolean(exposeNumericValues, 'options.exposeNumericValues');
+ return new Uint8Array([+exposeInternals, +exposeNumericValues]);
+}
+
class HeapSnapshotStream extends Readable {
constructor(handle) {
super({ autoDestroy: true });
@@ -37,5 +51,6 @@ class HeapSnapshotStream extends Readable {
}
module.exports = {
- HeapSnapshotStream
+ getHeapSnapshotOptions,
+ HeapSnapshotStream,
};
diff --git a/lib/internal/worker.js b/lib/internal/worker.js
index d88170ab9cd..3cc589c9967 100644
--- a/lib/internal/worker.js
+++ b/lib/internal/worker.js
@@ -416,12 +416,16 @@ class Worker extends EventEmitter {
return makeResourceLimits(this[kHandle].getResourceLimits());
}
- getHeapSnapshot() {
- const heapSnapshotTaker = this[kHandle] && this[kHandle].takeHeapSnapshot();
+ getHeapSnapshot(options) {
+ const {
+ HeapSnapshotStream,
+ getHeapSnapshotOptions
+ } = require('internal/heap_utils');
+ const optionsArray = getHeapSnapshotOptions(options);
+ const heapSnapshotTaker = this[kHandle]?.takeHeapSnapshot(optionsArray);
return new Promise((resolve, reject) => {
if (!heapSnapshotTaker) return reject(new ERR_WORKER_NOT_RUNNING());
heapSnapshotTaker.ondone = (handle) => {
- const { HeapSnapshotStream } = require('internal/heap_utils');
resolve(new HeapSnapshotStream(handle));
};
});
diff --git a/lib/v8.js b/lib/v8.js
index 479e8b13efc..70956192d7d 100644
--- a/lib/v8.js
+++ b/lib/v8.js
@@ -57,7 +57,10 @@ const {
createHeapSnapshotStream,
triggerHeapSnapshot
} = internalBinding('heap_utils');
-const { HeapSnapshotStream } = require('internal/heap_utils');
+const {
+ HeapSnapshotStream,
+ getHeapSnapshotOptions
+} = require('internal/heap_utils');
const promiseHooks = require('internal/promise_hooks');
const { getOptionValue } = require('internal/options');
@@ -65,23 +68,33 @@ const { getOptionValue } = require('internal/options');
* Generates a snapshot of the current V8 heap
* and writes it to a JSON file.
* @param {string} [filename]
+ * @param {{
+ * exposeInternals?: boolean,
+ * exposeNumericValues?: boolean
+ * }} [options]
* @returns {string}
*/
-function writeHeapSnapshot(filename) {
+function writeHeapSnapshot(filename, options) {
if (filename !== undefined) {
filename = getValidatedPath(filename);
filename = toNamespacedPath(filename);
}
- return triggerHeapSnapshot(filename);
+ const optionArray = getHeapSnapshotOptions(options);
+ return triggerHeapSnapshot(filename, optionArray);
}
/**
* Generates a snapshot of the current V8 heap
* and returns a Readable Stream.
+ * @param {{
+ * exposeInternals?: boolean,
+ * exposeNumericValues?: boolean
+ * }} [options]
* @returns {import('./stream.js').Readable}
*/
-function getHeapSnapshot() {
- const handle = createHeapSnapshotStream();
+function getHeapSnapshot(options) {
+ const optionArray = getHeapSnapshotOptions(options);
+ const handle = createHeapSnapshotStream(optionArray);
assert(handle);
return new HeapSnapshotStream(handle);
}
diff --git a/src/env.cc b/src/env.cc
index 01cce4650b6..be5d4b0723c 100644
--- a/src/env.cc
+++ b/src/env.cc
@@ -39,6 +39,7 @@ using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionTemplate;
using v8::HandleScope;
+using v8::HeapProfiler;
using v8::HeapSpaceStatistics;
using v8::Integer;
using v8::Isolate;
@@ -1790,7 +1791,10 @@ size_t Environment::NearHeapLimitCallback(void* data,
Debug(env, DebugCategory::DIAGNOSTICS, "Start generating %s...\n", *name);
- heap::WriteSnapshot(env, filename.c_str());
+ HeapProfiler::HeapSnapshotOptions options;
+ options.numerics_mode = HeapProfiler::NumericsMode::kExposeNumericValues;
+ options.snapshot_mode = HeapProfiler::HeapSnapshotMode::kExposeInternals;
+ heap::WriteSnapshot(env, filename.c_str(), options);
env->heap_limit_snapshot_taken_ += 1;
Debug(env,
diff --git a/src/heap_utils.cc b/src/heap_utils.cc
index 8556b58f151..14bcdd14433 100644
--- a/src/heap_utils.cc
+++ b/src/heap_utils.cc
@@ -25,6 +25,7 @@ using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Global;
using v8::HandleScope;
+using v8::HeapProfiler;
using v8::HeapSnapshot;
using v8::Isolate;
using v8::JustVoid;
@@ -36,6 +37,7 @@ using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::String;
+using v8::Uint8Array;
using v8::Value;
namespace node {
@@ -340,15 +342,19 @@ class HeapSnapshotStream : public AsyncWrap,
HeapSnapshotPointer snapshot_;
};
-inline void TakeSnapshot(Environment* env, v8::OutputStream* out) {
- HeapSnapshotPointer snapshot {
- env->isolate()->GetHeapProfiler()->TakeHeapSnapshot() };
+inline void TakeSnapshot(Environment* env,
+ v8::OutputStream* out,
+ HeapProfiler::HeapSnapshotOptions options) {
+ HeapSnapshotPointer snapshot{
+ env->isolate()->GetHeapProfiler()->TakeHeapSnapshot(options)};
snapshot->Serialize(out, HeapSnapshot::kJSON);
}
} // namespace
-Maybe<void> WriteSnapshot(Environment* env, const char* filename) {
+Maybe<void> WriteSnapshot(Environment* env,
+ const char* filename,
+ HeapProfiler::HeapSnapshotOptions options) {
uv_fs_t req;
int err;
@@ -365,7 +371,7 @@ Maybe<void> WriteSnapshot(Environment* env, const char* filename) {
}
FileOutputStream stream(fd, &req);
- TakeSnapshot(env, &stream);
+ TakeSnapshot(env, &stream, options);
if ((err = stream.status()) < 0) {
env->ThrowUVException(err, "write", nullptr, filename);
return Nothing<void>();
@@ -410,10 +416,28 @@ BaseObjectPtr<AsyncWrap> CreateHeapSnapshotStream(
return MakeBaseObject<HeapSnapshotStream>(env, std::move(snapshot), obj);
}
+HeapProfiler::HeapSnapshotOptions GetHeapSnapshotOptions(
+ Local<Value> options_value) {
+ CHECK(options_value->IsUint8Array());
+ Local<Uint8Array> arr = options_value.As<Uint8Array>();
+ uint8_t* options =
+ static_cast<uint8_t*>(arr->Buffer()->Data()) + arr->ByteOffset();
+ HeapProfiler::HeapSnapshotOptions result;
+ result.snapshot_mode = options[0]
+ ? HeapProfiler::HeapSnapshotMode::kExposeInternals
+ : HeapProfiler::HeapSnapshotMode::kRegular;
+ result.numerics_mode = options[1]
+ ? HeapProfiler::NumericsMode::kExposeNumericValues
+ : HeapProfiler::NumericsMode::kHideNumericValues;
+ return result;
+}
+
void CreateHeapSnapshotStream(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
- HeapSnapshotPointer snapshot {
- env->isolate()->GetHeapProfiler()->TakeHeapSnapshot() };
+ CHECK_EQ(args.Length(), 1);
+ auto options = GetHeapSnapshotOptions(args[0]);
+ HeapSnapshotPointer snapshot{
+ env->isolate()->GetHeapProfiler()->TakeHeapSnapshot(options)};
CHECK(snapshot);
BaseObjectPtr<AsyncWrap> stream =
CreateHeapSnapshotStream(env, std::move(snapshot));
@@ -424,13 +448,13 @@ void CreateHeapSnapshotStream(const FunctionCallbackInfo<Value>& args) {
void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = args.GetIsolate();
-
+ CHECK_EQ(args.Length(), 2);
Local<Value> filename_v = args[0];
+ auto options = GetHeapSnapshotOptions(args[1]);
if (filename_v->IsUndefined()) {
DiagnosticFilename name(env, "Heap", "heapsnapshot");
- if (WriteSnapshot(env, *name).IsNothing())
- return;
+ if (WriteSnapshot(env, *name, options).IsNothing()) return;
if (String::NewFromUtf8(isolate, *name).ToLocal(&filename_v)) {
args.GetReturnValue().Set(filename_v);
}
@@ -439,8 +463,7 @@ void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
BufferValue path(isolate, filename_v);
CHECK_NOT_NULL(*path);
- if (WriteSnapshot(env, *path).IsNothing())
- return;
+ if (WriteSnapshot(env, *path, options).IsNothing()) return;
return args.GetReturnValue().Set(filename_v);
}
diff --git a/src/node_internals.h b/src/node_internals.h
index 2dd2b6038ea..e47b180b192 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -382,7 +382,9 @@ class DiagnosticFilename {
};
namespace heap {
-v8::Maybe<void> WriteSnapshot(Environment* env, const char* filename);
+v8::Maybe<void> WriteSnapshot(Environment* env,
+ const char* filename,
+ v8::HeapProfiler::HeapSnapshotOptions options);
}
namespace heap {
@@ -423,6 +425,12 @@ std::ostream& operator<<(std::ostream& output,
}
bool linux_at_secure();
+
+namespace heap {
+v8::HeapProfiler::HeapSnapshotOptions GetHeapSnapshotOptions(
+ v8::Local<v8::Value> options);
+} // namespace heap
+
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
diff --git a/src/node_worker.cc b/src/node_worker.cc
index 571160a14eb..6b0ca484ace 100644
--- a/src/node_worker.cc
+++ b/src/node_worker.cc
@@ -778,6 +778,8 @@ class WorkerHeapSnapshotTaker : public AsyncWrap {
void Worker::TakeHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
Worker* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
+ CHECK_EQ(args.Length(), 1);
+ auto options = heap::GetHeapSnapshotOptions(args[0]);
Debug(w, "Worker %llu taking heap snapshot", w->thread_id_.id);
@@ -797,10 +799,10 @@ void Worker::TakeHeapSnapshot(const FunctionCallbackInfo<Value>& 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, options](
+ Environment* worker_env) mutable {
heap::HeapSnapshotPointer snapshot{
- worker_env->isolate()->GetHeapProfiler()->TakeHeapSnapshot()};
+ worker_env->isolate()->GetHeapProfiler()->TakeHeapSnapshot(options)};
CHECK(snapshot);
// Here, the worker thread temporarily owns the WorkerHeapSnapshotTaker
diff --git a/test/common/heap.js b/test/common/heap.js
index 6e5e55b0003..1c22c274af1 100644
--- a/test/common/heap.js
+++ b/test/common/heap.js
@@ -211,7 +211,39 @@ function validateSnapshotNodes(...args) {
return recordState().validateSnapshotNodes(...args);
}
+function getHeapSnapshotOptionTests() {
+ const fixtures = require('../common/fixtures');
+ const cases = [
+ {
+ options: { exposeInternals: true },
+ expected: [{
+ children: [
+ // We don't have anything special to test here yet
+ // because we don't use cppgc or embedder heap tracer.
+ { edge_name: 'nonNumeric', node_name: 'test' },
+ ]
+ }]
+ },
+ {
+ options: { exposeNumericValues: true },
+ expected: [{
+ children: [
+ { edge_name: 'numeric', node_name: 'smi number' },
+ ]
+ }]
+ },
+ ];
+ return {
+ fixtures: fixtures.path('klass-with-fields.js'),
+ check(snapshot, expected) {
+ snapshot.validateSnapshot('Klass', expected, { loose: true });
+ },
+ cases,
+ };
+}
+
module.exports = {
recordState,
- validateSnapshotNodes
+ validateSnapshotNodes,
+ getHeapSnapshotOptionTests
};
diff --git a/test/fixtures/klass-with-fields.js b/test/fixtures/klass-with-fields.js
new file mode 100644
index 00000000000..c5b000d9f3f
--- /dev/null
+++ b/test/fixtures/klass-with-fields.js
@@ -0,0 +1,18 @@
+'use strict';
+
+const {
+ parentPort,
+ isMainThread
+} = require('node:worker_threads');
+
+class Klass {
+ numeric = 1234;
+ nonNumeric = 'test';
+}
+
+globalThis.obj = new Klass();
+
+if (!isMainThread) {
+ parentPort.postMessage('ready');
+ setInterval(() => {}, 100);
+}
diff --git a/test/parallel/test-worker-heapdump-failure.js b/test/parallel/test-worker-heapdump-failure.js
index 06e260374cd..c5d24cdcf65 100644
--- a/test/parallel/test-worker-heapdump-failure.js
+++ b/test/parallel/test-worker-heapdump-failure.js
@@ -13,3 +13,18 @@ const { once } = require('events');
code: 'ERR_WORKER_NOT_RUNNING'
});
})().then(common.mustCall());
+
+(async function() {
+ const worker = new Worker('setInterval(() => {}, 1000);', { eval: true });
+ await once(worker, 'online');
+
+ [1, true, [], null, Infinity, NaN].forEach((i) => {
+ assert.throws(() => worker.getHeapSnapshot(i), {
+ code: 'ERR_INVALID_ARG_TYPE',
+ name: 'TypeError',
+ message: 'The "options" argument must be of type object.' +
+ common.invalidArgTypeHelper(i)
+ });
+ });
+ await worker.terminate();
+})().then(common.mustCall());
diff --git a/test/sequential/test-get-heapsnapshot-options.js b/test/sequential/test-get-heapsnapshot-options.js
new file mode 100644
index 00000000000..91ba6112722
--- /dev/null
+++ b/test/sequential/test-get-heapsnapshot-options.js
@@ -0,0 +1,39 @@
+'use strict';
+
+// Flags: --expose-internals
+
+require('../common');
+
+const { getHeapSnapshotOptionTests, recordState } = require('../common/heap');
+
+const tests = getHeapSnapshotOptionTests();
+if (process.argv[2] === 'child') {
+ const { getHeapSnapshot } = require('v8');
+ require(tests.fixtures);
+ const { options, expected } = tests.cases[parseInt(process.argv[3])];
+ const snapshot = recordState(getHeapSnapshot(options));
+ console.log('Snapshot nodes', snapshot.snapshot.length);
+ console.log('Searching for', expected[0].children);
+ tests.check(snapshot, expected);
+ delete globalThis.obj; // To pass the leaked global tests.
+ return;
+}
+
+const { spawnSync } = require('child_process');
+const assert = require('assert');
+const tmpdir = require('../common/tmpdir');
+tmpdir.refresh();
+
+for (let i = 0; i < tests.cases.length; ++i) {
+ const child = spawnSync(
+ process.execPath,
+ ['--expose-internals', __filename, 'child', i + ''],
+ {
+ cwd: tmpdir.path
+ });
+ const stderr = child.stderr.toString();
+ const stdout = child.stdout.toString();
+ console.log('[STDERR]', stderr);
+ console.log('[STDOUT]', stdout);
+ assert.strictEqual(child.status, 0);
+}
diff --git a/test/sequential/test-heapdump.js b/test/sequential/test-heapdump.js
index cb84bca4cd9..1388623e61f 100644
--- a/test/sequential/test-heapdump.js
+++ b/test/sequential/test-heapdump.js
@@ -47,6 +47,24 @@ process.chdir(tmpdir.path);
});
});
+[1, true, [], null, Infinity, NaN].forEach((i) => {
+ assert.throws(() => writeHeapSnapshot('test.heapsnapshot', i), {
+ code: 'ERR_INVALID_ARG_TYPE',
+ name: 'TypeError',
+ message: 'The "options" argument must be of type object.' +
+ common.invalidArgTypeHelper(i)
+ });
+});
+
+[1, true, [], null, Infinity, NaN].forEach((i) => {
+ assert.throws(() => getHeapSnapshot(i), {
+ code: 'ERR_INVALID_ARG_TYPE',
+ name: 'TypeError',
+ message: 'The "options" argument must be of type object.' +
+ common.invalidArgTypeHelper(i)
+ });
+});
+
{
let data = '';
const snapshot = getHeapSnapshot();
diff --git a/test/sequential/test-worker-heapsnapshot-options.js b/test/sequential/test-worker-heapsnapshot-options.js
new file mode 100644
index 00000000000..ca0ab190514
--- /dev/null
+++ b/test/sequential/test-worker-heapsnapshot-options.js
@@ -0,0 +1,21 @@
+// Flags: --expose-internals
+'use strict';
+const common = require('../common');
+const { recordState, getHeapSnapshotOptionTests } = require('../common/heap');
+const { Worker } = require('worker_threads');
+const { once } = require('events');
+
+(async function() {
+ const tests = getHeapSnapshotOptionTests();
+ const w = new Worker(tests.fixtures);
+
+ await once(w, 'message');
+
+ for (const { options, expected } of tests.cases) {
+ const stream = await w.getHeapSnapshot(options);
+ const snapshot = recordState(stream);
+ tests.check(snapshot, expected);
+ }
+
+ await w.terminate();
+})().then(common.mustCall());
diff --git a/test/sequential/test-write-heapsnapshot-options.js b/test/sequential/test-write-heapsnapshot-options.js
new file mode 100644
index 00000000000..cc3b4b19f4d
--- /dev/null
+++ b/test/sequential/test-write-heapsnapshot-options.js
@@ -0,0 +1,50 @@
+'use strict';
+
+// Flags: --expose-internals
+
+require('../common');
+
+const fs = require('fs');
+const { getHeapSnapshotOptionTests, recordState } = require('../common/heap');
+
+class ReadStream {
+ constructor(filename) {
+ this._content = fs.readFileSync(filename, 'utf-8');
+ }
+ pause() {}
+ read() { return this._content; }
+}
+
+const tests = getHeapSnapshotOptionTests();
+if (process.argv[2] === 'child') {
+ const { writeHeapSnapshot } = require('v8');
+ require(tests.fixtures);
+ const { options, expected } = tests.cases[parseInt(process.argv[3])];
+ const filename = writeHeapSnapshot(undefined, options);
+ const snapshot = recordState(new ReadStream(filename));
+ console.log('Snapshot nodes', snapshot.snapshot.length);
+ console.log('Searching for', expected[0].children);
+ tests.check(snapshot, expected);
+ delete globalThis.obj; // To pass the leaked global tests.
+ return;
+}
+
+const { spawnSync } = require('child_process');
+const assert = require('assert');
+const tmpdir = require('../common/tmpdir');
+tmpdir.refresh();
+
+// Start child processes to prevent the heap from growing too big.
+for (let i = 0; i < tests.cases.length; ++i) {
+ const child = spawnSync(
+ process.execPath,
+ ['--expose-internals', __filename, 'child', i + ''],
+ {
+ cwd: tmpdir.path
+ });
+ const stderr = child.stderr.toString();
+ const stdout = child.stdout.toString();
+ console.log('[STDERR]', stderr);
+ console.log('[STDOUT]', stdout);
+ assert.strictEqual(child.status, 0);
+}