From df587a03111a6ef80b1afd52bd4c0be590c5426a Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 13 Oct 2022 14:37:22 +0200 Subject: [PATCH 1/5] 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. --- doc/api/v8.md | 18 ++++++- doc/api/worker_threads.md | 7 ++- lib/internal/heap_utils.js | 26 +++++++++- lib/internal/worker.js | 10 ++-- lib/v8.js | 23 +++++++-- src/env.cc | 6 ++- src/heap_utils.cc | 47 ++++++++++++----- src/node_internals.h | 10 +++- src/node_worker.cc | 8 +-- test/common/heap.js | 34 ++++++++++++- test/fixtures/klass-with-fields.js | 18 +++++++ test/parallel/test-worker-heapdump-failure.js | 15 ++++++ .../test-get-heapsnapshot-options.js | 39 +++++++++++++++ test/sequential/test-heapdump.js | 18 +++++++ .../test-worker-heapsnapshot-options.js | 21 ++++++++ .../test-write-heapsnapshot-options.js | 50 +++++++++++++++++++ 16 files changed, 319 insertions(+), 31 deletions(-) create mode 100644 test/fixtures/klass-with-fields.js create mode 100644 test/sequential/test-get-heapsnapshot-options.js create mode 100644 test/sequential/test-worker-heapsnapshot-options.js create mode 100644 test/sequential/test-write-heapsnapshot-options.js diff --git a/doc/api/v8.md b/doc/api/v8.md index ec167ffcfb606b..ae8602a5470ec5 100644 --- a/doc/api/v8.md +++ b/doc/api/v8.md @@ -61,12 +61,21 @@ following properties: } ``` -## `v8.getHeapSnapshot()` +## `v8.getHeapSnapshot([options])` +* `options` {Object} + * `exposeInternals` {boolean} If true, expose internals in the heap snapshot. + * `exposeNumericValues` {boolean} If true, expose numeric values in + artificial fields. + * Returns: {stream.Readable} A Readable Stream containing the V8 heap snapshot Generates a snapshot of the current V8 heap and returns a Readable @@ -289,7 +298,7 @@ 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]])` * `filename` {string} The file path where the V8 heap snapshot is to be @@ -308,6 +320,7 @@ 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} See [`v8.getHeapSnapshot()`][] for more details. * Returns: {string} The filename where the snapshot was saved. Generates a snapshot of the current V8 heap and writes it to a JSON @@ -1062,6 +1075,7 @@ Returns true if the Node.js instance is run to build a snapshot. [`serializer.transferArrayBuffer()`]: #serializertransferarraybufferid-arraybuffer [`serializer.writeRawBytes()`]: #serializerwriterawbytesbuffer [`settled` callback]: #settledpromise +[`v8.getHeapSnapshot()`]: #v8getheapsnapshot [`v8.stopCoverage()`]: #v8stopcoverage [`v8.takeCoverage()`]: #v8takecoverage [`vm.Script`]: vm.md#new-vmscriptcode-options diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index 99772189522e84..8df051fb7b40a9 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -1067,14 +1067,19 @@ added: v10.5.0 The `'online'` event is emitted when the worker thread has started executing JavaScript code. -### `worker.getHeapSnapshot()` +### `worker.getHeapSnapshot([options])` +* `options` {Object} See [`v8.getHeapSnapshot()`][] for more details. * Returns: {Promise} A promise for a Readable Stream containing a V8 heap snapshot diff --git a/lib/internal/heap_utils.js b/lib/internal/heap_utils.js index 126fe3f7e46a0c..4c666088b3d7a6 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,29 @@ 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 kHandle = Symbol('kHandle'); +function getHeapSnapshotOptions(options = { + exposeInternals: false, + exposeNumericValues: false +}) { + validateObject(options, 'options'); + if (options.exposeInternals === undefined) { + options.exposeInternals = false; + } + if (options.exposeNumericValues === undefined) { + options.exposeNumericValues = false; + } + validateBoolean(options.exposeInternals, 'options.exposeInternals'); + validateBoolean(options.exposeNumericValues, 'options.exposeNumericValues'); + return new Uint8Array([ + +options.exposeInternals, + +options.exposeNumericValues + ]); +} + class HeapSnapshotStream extends Readable { constructor(handle) { super({ autoDestroy: true }); @@ -37,5 +58,6 @@ class HeapSnapshotStream extends Readable { } module.exports = { - HeapSnapshotStream + getHeapSnapshotOptions, + HeapSnapshotStream, }; diff --git a/lib/internal/worker.js b/lib/internal/worker.js index d88170ab9cd9cf..05846a9f230841 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] && 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 479e8b13efc96d..70956192d7d34f 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 d5552ce9133519..5ad991f2922980 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; @@ -1775,7 +1776,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 8556b58f151092..14bcdd14433e7d 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 WriteSnapshot(Environment* env, const char* filename) { +Maybe WriteSnapshot(Environment* env, + const char* filename, + HeapProfiler::HeapSnapshotOptions options) { uv_fs_t req; int err; @@ -365,7 +371,7 @@ Maybe 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(); @@ -410,10 +416,28 @@ BaseObjectPtr CreateHeapSnapshotStream( return MakeBaseObject(env, std::move(snapshot), obj); } +HeapProfiler::HeapSnapshotOptions GetHeapSnapshotOptions( + Local options_value) { + CHECK(options_value->IsUint8Array()); + Local arr = options_value.As(); + uint8_t* options = + static_cast(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& 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 stream = CreateHeapSnapshotStream(env, std::move(snapshot)); @@ -424,13 +448,13 @@ void CreateHeapSnapshotStream(const FunctionCallbackInfo& args) { void TriggerHeapSnapshot(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = args.GetIsolate(); - + CHECK_EQ(args.Length(), 2); Local 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& 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 9f15a807d02e09..feee3ae1d34994 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -380,7 +380,9 @@ class DiagnosticFilename { }; namespace heap { -v8::Maybe WriteSnapshot(Environment* env, const char* filename); +v8::Maybe WriteSnapshot(Environment* env, + const char* filename, + v8::HeapProfiler::HeapSnapshotOptions options); } namespace heap { @@ -421,6 +423,12 @@ std::ostream& operator<<(std::ostream& output, } bool linux_at_secure(); + +namespace heap { +v8::HeapProfiler::HeapSnapshotOptions GetHeapSnapshotOptions( + v8::Local 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 571160a14ebb1e..6b0ca484ace83a 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -778,6 +778,8 @@ class WorkerHeapSnapshotTaker : public AsyncWrap { void Worker::TakeHeapSnapshot(const FunctionCallbackInfo& 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& 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 6e5e55b000341b..3856ad6f683cfe 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 00000000000000..c5b000d9f3ff3e --- /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 06e260374cd3a4..c5d24cdcf658a2 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 00000000000000..91ba6112722ed5 --- /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 cb84bca4cd96da..1388623e61f939 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 00000000000000..ca0ab190514b6c --- /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 00000000000000..cc3b4b19f4d724 --- /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); +} From cbe5987f9d007a862a95fcb08eedab29eaa9183c Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 31 Oct 2022 15:40:00 +0100 Subject: [PATCH 2/5] fixup! lib: add options to the heap snapshot APIs --- doc/api/v8.md | 16 ++++++++++------ doc/api/worker_threads.md | 8 ++++++-- lib/internal/heap_utils.js | 16 +++++----------- lib/internal/worker.js | 2 +- test/common/heap.js | 2 +- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/doc/api/v8.md b/doc/api/v8.md index ae8602a5470ec5..cfdab87336d7d7 100644 --- a/doc/api/v8.md +++ b/doc/api/v8.md @@ -67,16 +67,17 @@ following properties: added: v11.13.0 changes: - version: REPLACEME - pr-url: 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. + artificial fields. **Default:** `false`. -* Returns: {stream.Readable} A Readable Stream containing the V8 heap snapshot +* 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. @@ -310,7 +311,7 @@ changes: pr-url: https://github.com/nodejs/node/pull/42577 description: Make the returned error codes consistent across all platforms. - version: REPLACEME - pr-url: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44989 description: Support options to configure the heap snapshot. --> @@ -320,7 +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} See [`v8.getHeapSnapshot()`][] for more details. +* `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 @@ -1075,7 +1080,6 @@ Returns true if the Node.js instance is run to build a snapshot. [`serializer.transferArrayBuffer()`]: #serializertransferarraybufferid-arraybuffer [`serializer.writeRawBytes()`]: #serializerwriterawbytesbuffer [`settled` callback]: #settledpromise -[`v8.getHeapSnapshot()`]: #v8getheapsnapshot [`v8.stopCoverage()`]: #v8stopcoverage [`v8.takeCoverage()`]: #v8takecoverage [`vm.Script`]: vm.md#new-vmscriptcode-options diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index 8df051fb7b40a9..717a857f99727c 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -1075,11 +1075,15 @@ added: - v12.17.0 changes: - version: REPLACEME - pr-url: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44989 description: Support options to configure the heap snapshot. --> -* `options` {Object} See [`v8.getHeapSnapshot()`][] for more details. +* `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 diff --git a/lib/internal/heap_utils.js b/lib/internal/heap_utils.js index 4c666088b3d7a6..2b59ce5d787f87 100644 --- a/lib/internal/heap_utils.js +++ b/lib/internal/heap_utils.js @@ -10,25 +10,19 @@ const { 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 = { - exposeInternals: false, - exposeNumericValues: false -}) { +function getHeapSnapshotOptions(options = kEmptyObject) { validateObject(options, 'options'); - if (options.exposeInternals === undefined) { - options.exposeInternals = false; - } - if (options.exposeNumericValues === undefined) { - options.exposeNumericValues = false; - } + options.exposeInternals ??= false; + options.exposeNumbericValues ??= false; validateBoolean(options.exposeInternals, 'options.exposeInternals'); validateBoolean(options.exposeNumericValues, 'options.exposeNumericValues'); return new Uint8Array([ +options.exposeInternals, - +options.exposeNumericValues + +options.exposeNumericValues, ]); } diff --git a/lib/internal/worker.js b/lib/internal/worker.js index 05846a9f230841..3cc589c996703c 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -422,7 +422,7 @@ class Worker extends EventEmitter { getHeapSnapshotOptions } = require('internal/heap_utils'); const optionsArray = getHeapSnapshotOptions(options); - const heapSnapshotTaker = this[kHandle] && this[kHandle].takeHeapSnapshot(optionsArray); + const heapSnapshotTaker = this[kHandle]?.takeHeapSnapshot(optionsArray); return new Promise((resolve, reject) => { if (!heapSnapshotTaker) return reject(new ERR_WORKER_NOT_RUNNING()); heapSnapshotTaker.ondone = (handle) => { diff --git a/test/common/heap.js b/test/common/heap.js index 3856ad6f683cfe..1c22c274af125a 100644 --- a/test/common/heap.js +++ b/test/common/heap.js @@ -239,7 +239,7 @@ function getHeapSnapshotOptionTests() { snapshot.validateSnapshot('Klass', expected, { loose: true }); }, cases, - } + }; } module.exports = { From 863976744520b10fbafff210589b4496158890fc Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 31 Oct 2022 16:13:36 +0100 Subject: [PATCH 3/5] fixup! fixup! lib: add options to the heap snapshot APIs --- doc/api/v8.md | 6 +++--- doc/api/worker_threads.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api/v8.md b/doc/api/v8.md index cfdab87336d7d7..35df5dc24652c7 100644 --- a/doc/api/v8.md +++ b/doc/api/v8.md @@ -304,15 +304,15 @@ disk unless [`v8.stopCoverage()`][] is invoked before the process exits. * `filename` {string} The file path where the V8 heap snapshot is to be diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index 717a857f99727c..694b1e74e0661a 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -1388,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 From 5fa25c4c18a49b2e9db22a108731c531a6de308f Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 1 Nov 2022 13:17:47 +0100 Subject: [PATCH 4/5] fixup! fixup! fixup! lib: add options to the heap snapshot APIs --- lib/internal/heap_utils.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/internal/heap_utils.js b/lib/internal/heap_utils.js index 2b59ce5d787f87..9ccf5aa00f50c4 100644 --- a/lib/internal/heap_utils.js +++ b/lib/internal/heap_utils.js @@ -16,14 +16,13 @@ const kHandle = Symbol('kHandle'); function getHeapSnapshotOptions(options = kEmptyObject) { validateObject(options, 'options'); - options.exposeInternals ??= false; - options.exposeNumbericValues ??= false; - validateBoolean(options.exposeInternals, 'options.exposeInternals'); - validateBoolean(options.exposeNumericValues, 'options.exposeNumericValues'); - return new Uint8Array([ - +options.exposeInternals, - +options.exposeNumericValues, - ]); + const { + exposeInternals = false, + exposeNumericValues = false, + } = options; + validateBoolean(exposeInternals, 'options.exposeInternals'); + validateBoolean(exposeNumericValues, 'options.exposeNumericValues'); + return new Uint8Array([+exposeInternals, +exposeNumericValues,]); } class HeapSnapshotStream extends Readable { From 129998c39568a8e36ac65832f4ba4d36a62aec81 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 1 Nov 2022 15:53:02 +0100 Subject: [PATCH 5/5] fixup! fixup! fixup! fixup! lib: add options to the heap snapshot APIs --- lib/internal/heap_utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/heap_utils.js b/lib/internal/heap_utils.js index 9ccf5aa00f50c4..3e789845c7b1a1 100644 --- a/lib/internal/heap_utils.js +++ b/lib/internal/heap_utils.js @@ -22,7 +22,7 @@ function getHeapSnapshotOptions(options = kEmptyObject) { } = options; validateBoolean(exposeInternals, 'options.exposeInternals'); validateBoolean(exposeNumericValues, 'options.exposeNumericValues'); - return new Uint8Array([+exposeInternals, +exposeNumericValues,]); + return new Uint8Array([+exposeInternals, +exposeNumericValues]); } class HeapSnapshotStream extends Readable {