Skip to content

Commit 5861bc1

Browse files
committed
support heap sampling params
1 parent 6d4a348 commit 5861bc1

File tree

11 files changed

+378
-23
lines changed

11 files changed

+378
-23
lines changed

doc/api/v8.md

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,23 +1788,92 @@ const profile = handle.stop();
17881788
console.log(profile);
17891789
```
17901790
1791-
## `v8.startHeapProfile()`
1791+
## `v8.heapProfilerConstants`
17921792
17931793
<!-- YAML
17941794
added: REPLACEME
17951795
-->
17961796
1797+
* {Object}
1798+
1799+
A frozen object containing constants used with [`v8.startHeapProfile()`][].
1800+
1801+
The following constants are available:
1802+
1803+
| Constant | Description |
1804+
| ------------------------------------------------ | --------------------------------------------------- |
1805+
| `SAMPLING_NO_FLAGS` | No flags. |
1806+
| `SAMPLING_FORCE_GC` | Force garbage collection before taking the profile. |
1807+
| `SAMPLING_INCLUDE_OBJECTS_COLLECTED_BY_MAJOR_GC` | Include objects collected by major GC. |
1808+
| `SAMPLING_INCLUDE_OBJECTS_COLLECTED_BY_MINOR_GC` | Include objects collected by minor GC. |
1809+
1810+
These constants can be combined using bitwise OR to pass as the `flags`
1811+
parameter.
1812+
1813+
## `v8.startHeapProfile([sampleInterval[, stackDepth[, flags]]])`
1814+
1815+
<!-- YAML
1816+
added: REPLACEME
1817+
-->
1818+
1819+
* `sampleInterval` {number} The average sampling interval in bytes.
1820+
**Default:** `524288` (512 KiB).
1821+
* `stackDepth` {integer} The maximum stack depth for samples.
1822+
**Default:** `16`.
1823+
* `flags` {integer} Flags to control sampling behavior. Use constants from
1824+
[`v8.heapProfilerConstants`][]. Multiple flags can be combined with bitwise
1825+
OR. **Default:** `v8.heapProfilerConstants.SAMPLING_NO_FLAGS`.
17971826
* Returns: {SyncHeapProfileHandle}
17981827
17991828
Starting a heap profile then return a `SyncHeapProfileHandle` object.
18001829
This API supports `using` syntax.
18011830
18021831
```cjs
1832+
const v8 = require('node:v8');
1833+
18031834
const handle = v8.startHeapProfile();
18041835
const profile = handle.stop();
18051836
console.log(profile);
18061837
```
18071838
1839+
```mjs
1840+
import v8 from 'node:v8';
1841+
1842+
const handle = v8.startHeapProfile();
1843+
const profile = handle.stop();
1844+
console.log(profile);
1845+
```
1846+
1847+
With custom parameters:
1848+
1849+
```cjs
1850+
const v8 = require('node:v8');
1851+
const { heapProfilerConstants } = v8;
1852+
1853+
const handle = v8.startHeapProfile(
1854+
1024,
1855+
8,
1856+
heapProfilerConstants.SAMPLING_FORCE_GC |
1857+
heapProfilerConstants.SAMPLING_INCLUDE_OBJECTS_COLLECTED_BY_MAJOR_GC,
1858+
);
1859+
const profile = handle.stop();
1860+
console.log(profile);
1861+
```
1862+
1863+
```mjs
1864+
import v8 from 'node:v8';
1865+
const { heapProfilerConstants } = v8;
1866+
1867+
const handle = v8.startHeapProfile(
1868+
1024,
1869+
8,
1870+
heapProfilerConstants.SAMPLING_FORCE_GC |
1871+
heapProfilerConstants.SAMPLING_INCLUDE_OBJECTS_COLLECTED_BY_MAJOR_GC,
1872+
);
1873+
const profile = handle.stop();
1874+
console.log(profile);
1875+
```
1876+
18081877
[CppHeap]: https://v8docs.nodesource.com/node-22.4/d9/dc4/classv8_1_1_cpp_heap.html
18091878
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
18101879
[Hook Callbacks]: #hook-callbacks
@@ -1839,6 +1908,8 @@ console.log(profile);
18391908
[`serializer.transferArrayBuffer()`]: #serializertransferarraybufferid-arraybuffer
18401909
[`serializer.writeRawBytes()`]: #serializerwriterawbytesbuffer
18411910
[`settled` callback]: #settledpromise
1911+
[`v8.heapProfilerConstants`]: #v8heapprofilerconstants
1912+
[`v8.startHeapProfile()`]: #v8startheapprofilesampleinterval-stackdepth-flags
18421913
[`v8.stopCoverage()`]: #v8stopcoverage
18431914
[`v8.takeCoverage()`]: #v8takecoverage
18441915
[`vm.Script`]: vm.md#new-vmscriptcode-options

doc/api/worker_threads.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2005,14 +2005,21 @@ w.on('online', async () => {
20052005
});
20062006
```
20072007
2008-
### `worker.startHeapProfile()`
2008+
### `worker.startHeapProfile([sampleInterval[, stackDepth[, flags]]])`
20092009
20102010
<!-- YAML
20112011
added:
20122012
- v24.9.0
20132013
- v22.20.0
20142014
-->
20152015
2016+
* `sampleInterval` {number} The average sampling interval in bytes.
2017+
**Default:** `524288` (512 KiB).
2018+
* `stackDepth` {integer} The maximum stack depth for samples.
2019+
**Default:** `16`.
2020+
* `flags` {integer} Flags to control sampling behavior. Use constants from
2021+
[`v8.heapProfilerConstants`][]. Multiple flags can be combined with bitwise
2022+
OR. **Default:** `v8.heapProfilerConstants.SAMPLING_NO_FLAGS`.
20162023
* Returns: {Promise}
20172024
20182025
Starting a Heap profile then return a Promise that fulfills with an error
@@ -2034,6 +2041,22 @@ worker.on('online', async () => {
20342041
});
20352042
```
20362043
2044+
```mjs
2045+
import { Worker } from 'node:worker_threads';
2046+
2047+
const worker = new Worker(`
2048+
const { parentPort } = require('node:worker_threads');
2049+
parentPort.on('message', () => {});
2050+
`, { eval: true });
2051+
2052+
worker.on('online', async () => {
2053+
const handle = await worker.startHeapProfile();
2054+
const profile = await handle.stop();
2055+
console.log(profile);
2056+
worker.terminate();
2057+
});
2058+
```
2059+
20372060
`await using` example.
20382061
20392062
```cjs
@@ -2050,6 +2073,20 @@ w.on('online', async () => {
20502073
});
20512074
```
20522075
2076+
```mjs
2077+
import { Worker } from 'node:worker_threads';
2078+
2079+
const w = new Worker(`
2080+
const { parentPort } = require('node:worker_threads');
2081+
parentPort.on('message', () => {});
2082+
`, { eval: true });
2083+
2084+
w.on('online', async () => {
2085+
// Stop profile automatically when return and profile will be discarded
2086+
await using handle = await w.startHeapProfile();
2087+
});
2088+
```
2089+
20532090
### `worker.stderr`
20542091
20552092
<!-- YAML
@@ -2266,6 +2303,7 @@ thread spawned will spawn another until the application crashes.
22662303
[`trace_events`]: tracing.md
22672304
[`v8.getHeapSnapshot()`]: v8.md#v8getheapsnapshotoptions
22682305
[`v8.getHeapStatistics()`]: v8.md#v8getheapstatistics
2306+
[`v8.heapProfilerConstants`]: v8.md#v8heapprofilerconstants
22692307
[`vm`]: vm.md
22702308
[`worker.SHARE_ENV`]: #worker_threadsshare_env
22712309
[`worker.on('message')`]: #event-message_1

lib/internal/v8/heap_profile.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
3+
const {
4+
ObjectFreeze,
5+
} = primordials;
6+
7+
const {
8+
validateInteger,
9+
validateInt32,
10+
} = require('internal/validators');
11+
12+
const {
13+
kSamplingNoFlags,
14+
kSamplingForceGC,
15+
kSamplingIncludeObjectsCollectedByMajorGC,
16+
kSamplingIncludeObjectsCollectedByMinorGC,
17+
} = internalBinding('v8');
18+
19+
const heapProfilerConstants = {
20+
__proto__: null,
21+
SAMPLING_NO_FLAGS: kSamplingNoFlags,
22+
SAMPLING_FORCE_GC: kSamplingForceGC,
23+
SAMPLING_INCLUDE_OBJECTS_COLLECTED_BY_MAJOR_GC:
24+
kSamplingIncludeObjectsCollectedByMajorGC,
25+
SAMPLING_INCLUDE_OBJECTS_COLLECTED_BY_MINOR_GC:
26+
kSamplingIncludeObjectsCollectedByMinorGC,
27+
};
28+
ObjectFreeze(heapProfilerConstants);
29+
30+
const kMaxSamplingFlags =
31+
heapProfilerConstants.SAMPLING_FORCE_GC |
32+
heapProfilerConstants.SAMPLING_INCLUDE_OBJECTS_COLLECTED_BY_MAJOR_GC |
33+
heapProfilerConstants.SAMPLING_INCLUDE_OBJECTS_COLLECTED_BY_MINOR_GC;
34+
35+
function normalizeHeapProfileOptions(
36+
sampleInterval = 512 * 1024,
37+
stackDepth = 16,
38+
flags = heapProfilerConstants.SAMPLING_NO_FLAGS,
39+
) {
40+
validateInteger(sampleInterval, 'sampleInterval', 1);
41+
validateInt32(stackDepth, 'stackDepth', 0);
42+
validateInt32(flags, 'flags', 0, kMaxSamplingFlags);
43+
return { sampleInterval, stackDepth, flags };
44+
}
45+
46+
module.exports = {
47+
heapProfilerConstants,
48+
normalizeHeapProfileOptions,
49+
};

lib/internal/worker.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,15 @@ const {
6565
constructSharedArrayBuffer,
6666
kEmptyObject,
6767
} = require('internal/util');
68-
const { validateArray, validateString, validateObject, validateNumber } = require('internal/validators');
68+
const {
69+
validateArray,
70+
validateString,
71+
validateObject,
72+
validateNumber,
73+
} = require('internal/validators');
74+
const {
75+
normalizeHeapProfileOptions,
76+
} = require('internal/v8/heap_profile');
6977
const {
7078
throwIfBuildingSnapshot,
7179
} = require('internal/v8/startup_snapshot');
@@ -582,8 +590,11 @@ class Worker extends EventEmitter {
582590
});
583591
}
584592

585-
startHeapProfile() {
586-
const startTaker = this[kHandle]?.startHeapProfile();
593+
startHeapProfile(sampleInterval, stackDepth, flags) {
594+
({ sampleInterval, stackDepth, flags } =
595+
normalizeHeapProfileOptions(sampleInterval, stackDepth, flags));
596+
const startTaker = this[kHandle]?.startHeapProfile(
597+
sampleInterval, stackDepth, flags);
587598
return new Promise((resolve, reject) => {
588599
if (!startTaker) return reject(new ERR_WORKER_NOT_RUNNING());
589600
startTaker.ondone = (err) => {

lib/v8.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const {
2626
Int32Array,
2727
Int8Array,
2828
JSONParse,
29+
ObjectDefineProperty,
2930
ObjectPrototypeToString,
3031
SymbolDispose,
3132
Uint16Array,
@@ -40,8 +41,8 @@ const {
4041
const { Buffer } = require('buffer');
4142
const {
4243
validateString,
43-
validateUint32,
4444
validateOneOf,
45+
validateUint32,
4546
} = require('internal/validators');
4647
const {
4748
Serializer,
@@ -50,6 +51,10 @@ const {
5051
const {
5152
namespace: startupSnapshot,
5253
} = require('internal/v8/startup_snapshot');
54+
const {
55+
heapProfilerConstants,
56+
normalizeHeapProfileOptions,
57+
} = require('internal/v8/heap_profile');
5358

5459
let profiler = {};
5560
if (internalBinding('config').hasInspector) {
@@ -220,10 +225,15 @@ function startCpuProfile() {
220225

221226
/**
222227
* Starting Heap Profile.
228+
* @param {number} [sampleInterval]
229+
* @param {number} [stackDepth]
230+
* @param {number} [flags]
223231
* @returns {SyncHeapProfileHandle}
224232
*/
225-
function startHeapProfile() {
226-
_startHeapProfile();
233+
function startHeapProfile(sampleInterval, stackDepth, flags) {
234+
({ sampleInterval, stackDepth, flags } =
235+
normalizeHeapProfileOptions(sampleInterval, stackDepth, flags));
236+
_startHeapProfile(sampleInterval, stackDepth, flags);
227237
return new SyncHeapProfileHandle();
228238
}
229239

@@ -547,3 +557,10 @@ module.exports = {
547557
startCpuProfile,
548558
startHeapProfile,
549559
};
560+
561+
ObjectDefineProperty(module.exports, 'heapProfilerConstants', {
562+
__proto__: null,
563+
configurable: false,
564+
enumerable: true,
565+
value: heapProfilerConstants,
566+
});

src/node_v8.cc

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,10 @@ void StopCpuProfile(const FunctionCallbackInfo<Value>& args) {
282282

283283
void StartHeapProfile(const FunctionCallbackInfo<Value>& args) {
284284
Isolate* isolate = args.GetIsolate();
285-
if (isolate->GetHeapProfiler()->StartSamplingHeapProfiler()) {
285+
auto options = ParseHeapProfileOptions(args);
286+
287+
if (isolate->GetHeapProfiler()->StartSamplingHeapProfiler(
288+
options.sample_interval, options.stack_depth, options.flags)) {
286289
return;
287290
}
288291
THROW_ERR_HEAP_PROFILE_HAVE_BEEN_STARTED(isolate,
@@ -766,6 +769,42 @@ void Initialize(Local<Object> target,
766769
SetMethod(context, target, "stopCpuProfile", StopCpuProfile);
767770
SetMethod(context, target, "startHeapProfile", StartHeapProfile);
768771
SetMethod(context, target, "stopHeapProfile", StopHeapProfile);
772+
target
773+
->Set(context,
774+
FIXED_ONE_BYTE_STRING(env->isolate(), "kSamplingNoFlags"),
775+
Uint32::NewFromUnsigned(
776+
env->isolate(),
777+
static_cast<uint32_t>(
778+
v8::HeapProfiler::SamplingFlags::kSamplingNoFlags)))
779+
.Check();
780+
target
781+
->Set(context,
782+
FIXED_ONE_BYTE_STRING(env->isolate(), "kSamplingForceGC"),
783+
Uint32::NewFromUnsigned(
784+
env->isolate(),
785+
static_cast<uint32_t>(
786+
v8::HeapProfiler::SamplingFlags::kSamplingForceGC)))
787+
.Check();
788+
target
789+
->Set(context,
790+
FIXED_ONE_BYTE_STRING(env->isolate(),
791+
"kSamplingIncludeObjectsCollectedByMajorGC"),
792+
Uint32::NewFromUnsigned(
793+
env->isolate(),
794+
static_cast<uint32_t>(
795+
v8::HeapProfiler::SamplingFlags::
796+
kSamplingIncludeObjectsCollectedByMajorGC)))
797+
.Check();
798+
target
799+
->Set(context,
800+
FIXED_ONE_BYTE_STRING(env->isolate(),
801+
"kSamplingIncludeObjectsCollectedByMinorGC"),
802+
Uint32::NewFromUnsigned(
803+
env->isolate(),
804+
static_cast<uint32_t>(
805+
v8::HeapProfiler::SamplingFlags::
806+
kSamplingIncludeObjectsCollectedByMinorGC)))
807+
.Check();
769808

770809
// Export symbols used by v8.isStringOneByteRepresentation()
771810
SetFastMethodNoSideEffect(context,

src/node_worker.cc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,7 @@ void Worker::StartHeapProfile(const FunctionCallbackInfo<Value>& args) {
10441044
Worker* w;
10451045
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
10461046
Environment* env = w->env();
1047+
auto options = ParseHeapProfileOptions(args);
10471048

10481049
AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w);
10491050
Local<Object> wrap;
@@ -1056,10 +1057,11 @@ void Worker::StartHeapProfile(const FunctionCallbackInfo<Value>& args) {
10561057
BaseObjectPtr<WorkerHeapProfileTaker> taker =
10571058
MakeDetachedBaseObject<WorkerHeapProfileTaker>(env, wrap);
10581059

1059-
bool scheduled = w->RequestInterrupt([taker = std::move(taker),
1060-
env](Environment* worker_env) mutable {
1060+
bool scheduled = w->RequestInterrupt([taker = std::move(taker), env, options](
1061+
Environment* worker_env) mutable {
10611062
v8::HeapProfiler* profiler = worker_env->isolate()->GetHeapProfiler();
1062-
bool success = profiler->StartSamplingHeapProfiler();
1063+
bool success = profiler->StartSamplingHeapProfiler(
1064+
options.sample_interval, options.stack_depth, options.flags);
10631065
env->SetImmediateThreadsafe(
10641066
[taker = std::move(taker),
10651067
success = success](Environment* env) mutable {

0 commit comments

Comments
 (0)