Skip to content

Commit 0478e40

Browse files
joyeecheungRafaelGSS
authored andcommitted
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: #44989 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]>
1 parent ba45373 commit 0478e40

16 files changed

+322
-33
lines changed

doc/api/v8.md

+21-3
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,23 @@ following properties:
6161
}
6262
```
6363

64-
## `v8.getHeapSnapshot()`
64+
## `v8.getHeapSnapshot([options])`
6565

6666
<!-- YAML
6767
added: v11.13.0
68+
changes:
69+
- version: REPLACEME
70+
pr-url: https://github.com/nodejs/node/pull/44989
71+
description: Support options to configure the heap snapshot.
6872
-->
6973

70-
* Returns: {stream.Readable} A Readable Stream containing the V8 heap snapshot
74+
* `options` {Object}
75+
* `exposeInternals` {boolean} If true, expose internals in the heap snapshot.
76+
**Default:** `false`.
77+
* `exposeNumericValues` {boolean} If true, expose numeric values in
78+
artificial fields. **Default:** `false`.
79+
80+
* Returns: {stream.Readable} A Readable containing the V8 heap snapshot.
7181

7282
Generates a snapshot of the current V8 heap and returns a Readable
7383
Stream that may be used to read the JSON serialized representation.
@@ -289,11 +299,14 @@ by [`NODE_V8_COVERAGE`][].
289299
When the process is about to exit, one last coverage will still be written to
290300
disk unless [`v8.stopCoverage()`][] is invoked before the process exits.
291301

292-
## `v8.writeHeapSnapshot([filename])`
302+
## `v8.writeHeapSnapshot([filename[,options]])`
293303

294304
<!-- YAML
295305
added: v11.13.0
296306
changes:
307+
- version: REPLACEME
308+
pr-url: https://github.com/nodejs/node/pull/44989
309+
description: Support options to configure the heap snapshot.
297310
- version: v18.0.0
298311
pr-url: https://github.com/nodejs/node/pull/41373
299312
description: An exception will now be thrown if the file could not be written.
@@ -308,6 +321,11 @@ changes:
308321
generated, where `{pid}` will be the PID of the Node.js process,
309322
`{thread_id}` will be `0` when `writeHeapSnapshot()` is called from
310323
the main Node.js thread or the id of a worker thread.
324+
* `options` {Object}
325+
* `exposeInternals` {boolean} If true, expose internals in the heap snapshot.
326+
**Default:** `false`.
327+
* `exposeNumericValues` {boolean} If true, expose numeric values in
328+
artificial fields. **Default:** `false`.
311329
* Returns: {string} The filename where the snapshot was saved.
312330

313331
Generates a snapshot of the current V8 heap and writes it to a JSON

doc/api/worker_threads.md

+11-2
Original file line numberDiff line numberDiff line change
@@ -1067,14 +1067,23 @@ added: v10.5.0
10671067
The `'online'` event is emitted when the worker thread has started executing
10681068
JavaScript code.
10691069

1070-
### `worker.getHeapSnapshot()`
1070+
### `worker.getHeapSnapshot([options])`
10711071

10721072
<!-- YAML
10731073
added:
10741074
- v13.9.0
10751075
- v12.17.0
1076+
changes:
1077+
- version: REPLACEME
1078+
pr-url: https://github.com/nodejs/node/pull/44989
1079+
description: Support options to configure the heap snapshot.
10761080
-->
10771081

1082+
* `options` {Object}
1083+
* `exposeInternals` {boolean} If true, expose internals in the heap snapshot.
1084+
**Default:** `false`.
1085+
* `exposeNumericValues` {boolean} If true, expose numeric values in
1086+
artificial fields. **Default:** `false`.
10781087
* Returns: {Promise} A promise for a Readable Stream containing
10791088
a V8 heap snapshot
10801089

@@ -1379,7 +1388,7 @@ thread spawned will spawn another until the application crashes.
13791388
[`require('node:worker_threads').threadId`]: #workerthreadid
13801389
[`require('node:worker_threads').workerData`]: #workerworkerdata
13811390
[`trace_events`]: tracing.md
1382-
[`v8.getHeapSnapshot()`]: v8.md#v8getheapsnapshot
1391+
[`v8.getHeapSnapshot()`]: v8.md#v8getheapsnapshotoptions
13831392
[`vm`]: vm.md
13841393
[`worker.SHARE_ENV`]: #workershare_env
13851394
[`worker.on('message')`]: #event-message_1

lib/internal/heap_utils.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,30 @@
11
'use strict';
22
const {
3-
Symbol
3+
Symbol,
4+
Uint8Array,
45
} = primordials;
56
const {
67
kUpdateTimer,
78
onStreamRead,
89
} = require('internal/stream_base_commons');
910
const { owner_symbol } = require('internal/async_hooks').symbols;
1011
const { Readable } = require('stream');
12+
const { validateObject, validateBoolean } = require('internal/validators');
13+
const { kEmptyObject } = require('internal/util');
1114

1215
const kHandle = Symbol('kHandle');
1316

17+
function getHeapSnapshotOptions(options = kEmptyObject) {
18+
validateObject(options, 'options');
19+
const {
20+
exposeInternals = false,
21+
exposeNumericValues = false,
22+
} = options;
23+
validateBoolean(exposeInternals, 'options.exposeInternals');
24+
validateBoolean(exposeNumericValues, 'options.exposeNumericValues');
25+
return new Uint8Array([+exposeInternals, +exposeNumericValues]);
26+
}
27+
1428
class HeapSnapshotStream extends Readable {
1529
constructor(handle) {
1630
super({ autoDestroy: true });
@@ -37,5 +51,6 @@ class HeapSnapshotStream extends Readable {
3751
}
3852

3953
module.exports = {
40-
HeapSnapshotStream
54+
getHeapSnapshotOptions,
55+
HeapSnapshotStream,
4156
};

lib/internal/worker.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -416,12 +416,16 @@ class Worker extends EventEmitter {
416416
return makeResourceLimits(this[kHandle].getResourceLimits());
417417
}
418418

419-
getHeapSnapshot() {
420-
const heapSnapshotTaker = this[kHandle] && this[kHandle].takeHeapSnapshot();
419+
getHeapSnapshot(options) {
420+
const {
421+
HeapSnapshotStream,
422+
getHeapSnapshotOptions
423+
} = require('internal/heap_utils');
424+
const optionsArray = getHeapSnapshotOptions(options);
425+
const heapSnapshotTaker = this[kHandle]?.takeHeapSnapshot(optionsArray);
421426
return new Promise((resolve, reject) => {
422427
if (!heapSnapshotTaker) return reject(new ERR_WORKER_NOT_RUNNING());
423428
heapSnapshotTaker.ondone = (handle) => {
424-
const { HeapSnapshotStream } = require('internal/heap_utils');
425429
resolve(new HeapSnapshotStream(handle));
426430
};
427431
});

lib/v8.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -57,31 +57,44 @@ const {
5757
createHeapSnapshotStream,
5858
triggerHeapSnapshot
5959
} = internalBinding('heap_utils');
60-
const { HeapSnapshotStream } = require('internal/heap_utils');
60+
const {
61+
HeapSnapshotStream,
62+
getHeapSnapshotOptions
63+
} = require('internal/heap_utils');
6164
const promiseHooks = require('internal/promise_hooks');
6265
const { getOptionValue } = require('internal/options');
6366

6467
/**
6568
* Generates a snapshot of the current V8 heap
6669
* and writes it to a JSON file.
6770
* @param {string} [filename]
71+
* @param {{
72+
* exposeInternals?: boolean,
73+
* exposeNumericValues?: boolean
74+
* }} [options]
6875
* @returns {string}
6976
*/
70-
function writeHeapSnapshot(filename) {
77+
function writeHeapSnapshot(filename, options) {
7178
if (filename !== undefined) {
7279
filename = getValidatedPath(filename);
7380
filename = toNamespacedPath(filename);
7481
}
75-
return triggerHeapSnapshot(filename);
82+
const optionArray = getHeapSnapshotOptions(options);
83+
return triggerHeapSnapshot(filename, optionArray);
7684
}
7785

7886
/**
7987
* Generates a snapshot of the current V8 heap
8088
* and returns a Readable Stream.
89+
* @param {{
90+
* exposeInternals?: boolean,
91+
* exposeNumericValues?: boolean
92+
* }} [options]
8193
* @returns {import('./stream.js').Readable}
8294
*/
83-
function getHeapSnapshot() {
84-
const handle = createHeapSnapshotStream();
95+
function getHeapSnapshot(options) {
96+
const optionArray = getHeapSnapshotOptions(options);
97+
const handle = createHeapSnapshotStream(optionArray);
8598
assert(handle);
8699
return new HeapSnapshotStream(handle);
87100
}

src/env.cc

+5-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ using v8::EscapableHandleScope;
3939
using v8::Function;
4040
using v8::FunctionTemplate;
4141
using v8::HandleScope;
42+
using v8::HeapProfiler;
4243
using v8::HeapSpaceStatistics;
4344
using v8::Integer;
4445
using v8::Isolate;
@@ -1790,7 +1791,10 @@ size_t Environment::NearHeapLimitCallback(void* data,
17901791

17911792
Debug(env, DebugCategory::DIAGNOSTICS, "Start generating %s...\n", *name);
17921793

1793-
heap::WriteSnapshot(env, filename.c_str());
1794+
HeapProfiler::HeapSnapshotOptions options;
1795+
options.numerics_mode = HeapProfiler::NumericsMode::kExposeNumericValues;
1796+
options.snapshot_mode = HeapProfiler::HeapSnapshotMode::kExposeInternals;
1797+
heap::WriteSnapshot(env, filename.c_str(), options);
17941798
env->heap_limit_snapshot_taken_ += 1;
17951799

17961800
Debug(env,

src/heap_utils.cc

+35-12
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ using v8::FunctionCallbackInfo;
2525
using v8::FunctionTemplate;
2626
using v8::Global;
2727
using v8::HandleScope;
28+
using v8::HeapProfiler;
2829
using v8::HeapSnapshot;
2930
using v8::Isolate;
3031
using v8::JustVoid;
@@ -36,6 +37,7 @@ using v8::Number;
3637
using v8::Object;
3738
using v8::ObjectTemplate;
3839
using v8::String;
40+
using v8::Uint8Array;
3941
using v8::Value;
4042

4143
namespace node {
@@ -340,15 +342,19 @@ class HeapSnapshotStream : public AsyncWrap,
340342
HeapSnapshotPointer snapshot_;
341343
};
342344

343-
inline void TakeSnapshot(Environment* env, v8::OutputStream* out) {
344-
HeapSnapshotPointer snapshot {
345-
env->isolate()->GetHeapProfiler()->TakeHeapSnapshot() };
345+
inline void TakeSnapshot(Environment* env,
346+
v8::OutputStream* out,
347+
HeapProfiler::HeapSnapshotOptions options) {
348+
HeapSnapshotPointer snapshot{
349+
env->isolate()->GetHeapProfiler()->TakeHeapSnapshot(options)};
346350
snapshot->Serialize(out, HeapSnapshot::kJSON);
347351
}
348352

349353
} // namespace
350354

351-
Maybe<void> WriteSnapshot(Environment* env, const char* filename) {
355+
Maybe<void> WriteSnapshot(Environment* env,
356+
const char* filename,
357+
HeapProfiler::HeapSnapshotOptions options) {
352358
uv_fs_t req;
353359
int err;
354360

@@ -365,7 +371,7 @@ Maybe<void> WriteSnapshot(Environment* env, const char* filename) {
365371
}
366372

367373
FileOutputStream stream(fd, &req);
368-
TakeSnapshot(env, &stream);
374+
TakeSnapshot(env, &stream, options);
369375
if ((err = stream.status()) < 0) {
370376
env->ThrowUVException(err, "write", nullptr, filename);
371377
return Nothing<void>();
@@ -410,10 +416,28 @@ BaseObjectPtr<AsyncWrap> CreateHeapSnapshotStream(
410416
return MakeBaseObject<HeapSnapshotStream>(env, std::move(snapshot), obj);
411417
}
412418

419+
HeapProfiler::HeapSnapshotOptions GetHeapSnapshotOptions(
420+
Local<Value> options_value) {
421+
CHECK(options_value->IsUint8Array());
422+
Local<Uint8Array> arr = options_value.As<Uint8Array>();
423+
uint8_t* options =
424+
static_cast<uint8_t*>(arr->Buffer()->Data()) + arr->ByteOffset();
425+
HeapProfiler::HeapSnapshotOptions result;
426+
result.snapshot_mode = options[0]
427+
? HeapProfiler::HeapSnapshotMode::kExposeInternals
428+
: HeapProfiler::HeapSnapshotMode::kRegular;
429+
result.numerics_mode = options[1]
430+
? HeapProfiler::NumericsMode::kExposeNumericValues
431+
: HeapProfiler::NumericsMode::kHideNumericValues;
432+
return result;
433+
}
434+
413435
void CreateHeapSnapshotStream(const FunctionCallbackInfo<Value>& args) {
414436
Environment* env = Environment::GetCurrent(args);
415-
HeapSnapshotPointer snapshot {
416-
env->isolate()->GetHeapProfiler()->TakeHeapSnapshot() };
437+
CHECK_EQ(args.Length(), 1);
438+
auto options = GetHeapSnapshotOptions(args[0]);
439+
HeapSnapshotPointer snapshot{
440+
env->isolate()->GetHeapProfiler()->TakeHeapSnapshot(options)};
417441
CHECK(snapshot);
418442
BaseObjectPtr<AsyncWrap> stream =
419443
CreateHeapSnapshotStream(env, std::move(snapshot));
@@ -424,13 +448,13 @@ void CreateHeapSnapshotStream(const FunctionCallbackInfo<Value>& args) {
424448
void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
425449
Environment* env = Environment::GetCurrent(args);
426450
Isolate* isolate = args.GetIsolate();
427-
451+
CHECK_EQ(args.Length(), 2);
428452
Local<Value> filename_v = args[0];
453+
auto options = GetHeapSnapshotOptions(args[1]);
429454

430455
if (filename_v->IsUndefined()) {
431456
DiagnosticFilename name(env, "Heap", "heapsnapshot");
432-
if (WriteSnapshot(env, *name).IsNothing())
433-
return;
457+
if (WriteSnapshot(env, *name, options).IsNothing()) return;
434458
if (String::NewFromUtf8(isolate, *name).ToLocal(&filename_v)) {
435459
args.GetReturnValue().Set(filename_v);
436460
}
@@ -439,8 +463,7 @@ void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
439463

440464
BufferValue path(isolate, filename_v);
441465
CHECK_NOT_NULL(*path);
442-
if (WriteSnapshot(env, *path).IsNothing())
443-
return;
466+
if (WriteSnapshot(env, *path, options).IsNothing()) return;
444467
return args.GetReturnValue().Set(filename_v);
445468
}
446469

src/node_internals.h

+9-1
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,9 @@ class DiagnosticFilename {
382382
};
383383

384384
namespace heap {
385-
v8::Maybe<void> WriteSnapshot(Environment* env, const char* filename);
385+
v8::Maybe<void> WriteSnapshot(Environment* env,
386+
const char* filename,
387+
v8::HeapProfiler::HeapSnapshotOptions options);
386388
}
387389

388390
namespace heap {
@@ -423,6 +425,12 @@ std::ostream& operator<<(std::ostream& output,
423425
}
424426

425427
bool linux_at_secure();
428+
429+
namespace heap {
430+
v8::HeapProfiler::HeapSnapshotOptions GetHeapSnapshotOptions(
431+
v8::Local<v8::Value> options);
432+
} // namespace heap
433+
426434
} // namespace node
427435

428436
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

src/node_worker.cc

+5-3
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,8 @@ class WorkerHeapSnapshotTaker : public AsyncWrap {
778778
void Worker::TakeHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
779779
Worker* w;
780780
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
781+
CHECK_EQ(args.Length(), 1);
782+
auto options = heap::GetHeapSnapshotOptions(args[0]);
781783

782784
Debug(w, "Worker %llu taking heap snapshot", w->thread_id_.id);
783785

@@ -797,10 +799,10 @@ void Worker::TakeHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
797799

798800
// Interrupt the worker thread and take a snapshot, then schedule a call
799801
// on the parent thread that turns that snapshot into a readable stream.
800-
bool scheduled = w->RequestInterrupt([taker = std::move(taker),
801-
env](Environment* worker_env) mutable {
802+
bool scheduled = w->RequestInterrupt([taker = std::move(taker), env, options](
803+
Environment* worker_env) mutable {
802804
heap::HeapSnapshotPointer snapshot{
803-
worker_env->isolate()->GetHeapProfiler()->TakeHeapSnapshot()};
805+
worker_env->isolate()->GetHeapProfiler()->TakeHeapSnapshot(options)};
804806
CHECK(snapshot);
805807

806808
// Here, the worker thread temporarily owns the WorkerHeapSnapshotTaker

0 commit comments

Comments
 (0)