v8: add heap profile API by IlyasShabi · Pull Request #62273 · nodejs/node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions doc/api/v8.md
43 changes: 42 additions & 1 deletion doc/api/worker_threads.md
Original file line number Diff line number Diff line change
Expand Up @@ -2005,14 +2005,25 @@ w.on('online', async () => {
});
```

### `worker.startHeapProfile()`
### `worker.startHeapProfile([options])`

<!-- YAML
added:
- v24.9.0
- v22.20.0
-->

* `options` {Object}
* `sampleInterval` {number} The average sampling interval in bytes.
**Default:** `524288` (512 KiB).
* `stackDepth` {integer} The maximum stack depth for samples.
**Default:** `16`.
* `forceGC` {boolean} Force garbage collection before taking the profile.
**Default:** `false`.
* `includeObjectsCollectedByMajorGC` {boolean} Include objects collected
by major GC. **Default:** `false`.
* `includeObjectsCollectedByMinorGC` {boolean} Include objects collected
by minor GC. **Default:** `false`.
* Returns: {Promise}

Starting a Heap profile then return a Promise that fulfills with an error
Expand All @@ -2034,6 +2045,22 @@ worker.on('online', async () => {
});
```

```mjs
import { Worker } from 'node:worker_threads';

const worker = new Worker(`
const { parentPort } = require('node:worker_threads');
parentPort.on('message', () => {});
`, { eval: true });

worker.on('online', async () => {
const handle = await worker.startHeapProfile();
const profile = await handle.stop();
console.log(profile);
worker.terminate();
});
```

`await using` example.

```cjs
Expand All @@ -2050,6 +2077,20 @@ w.on('online', async () => {
});
```

```mjs
import { Worker } from 'node:worker_threads';

const w = new Worker(`
const { parentPort } = require('node:worker_threads');
parentPort.on('message', () => {});
`, { eval: true });

w.on('online', async () => {
// Stop profile automatically when return and profile will be discarded
await using handle = await w.startHeapProfile();
});
```

### `worker.stderr`

<!-- YAML
Expand Down
49 changes: 49 additions & 0 deletions lib/internal/v8/heap_profile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';

const {
validateBoolean,
validateInteger,
validateInt32,
validateObject,
} = require('internal/validators');

const {
kSamplingNoFlags,
kSamplingForceGC,
kSamplingIncludeObjectsCollectedByMajorGC,
kSamplingIncludeObjectsCollectedByMinorGC,
} = internalBinding('v8');

function normalizeHeapProfileOptions(options = {}) {
validateObject(options, 'options');
const {
sampleInterval = 512 * 1024,
stackDepth = 16,
forceGC = false,
includeObjectsCollectedByMajorGC = false,
includeObjectsCollectedByMinorGC = false,
} = options;

validateInteger(sampleInterval, 'options.sampleInterval', 1);
validateInt32(stackDepth, 'options.stackDepth', 0);
validateBoolean(forceGC, 'options.forceGC');
validateBoolean(includeObjectsCollectedByMajorGC,
'options.includeObjectsCollectedByMajorGC');
validateBoolean(includeObjectsCollectedByMinorGC,
'options.includeObjectsCollectedByMinorGC');

let flags = kSamplingNoFlags;
if (forceGC) flags |= kSamplingForceGC;
if (includeObjectsCollectedByMajorGC) {
flags |= kSamplingIncludeObjectsCollectedByMajorGC;
}
if (includeObjectsCollectedByMinorGC) {
flags |= kSamplingIncludeObjectsCollectedByMinorGC;
}

return { sampleInterval, stackDepth, flags };
}

module.exports = {
normalizeHeapProfileOptions,
};
26 changes: 23 additions & 3 deletions lib/internal/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ const {
constructSharedArrayBuffer,
kEmptyObject,
} = require('internal/util');
const { validateArray, validateString, validateObject, validateNumber } = require('internal/validators');
const {
validateArray,
validateString,
validateObject,
validateNumber,
} = require('internal/validators');
let normalizeHeapProfileOptions;
const {
throwIfBuildingSnapshot,
} = require('internal/v8/startup_snapshot');
Expand Down Expand Up @@ -582,8 +588,22 @@ class Worker extends EventEmitter {
});
}

startHeapProfile() {
const startTaker = this[kHandle]?.startHeapProfile();
/**
* @param {object} [options]
* @param {number} [options.sampleInterval]
* @param {number} [options.stackDepth]
* @param {boolean} [options.forceGC]
* @param {boolean} [options.includeObjectsCollectedByMajorGC]
* @param {boolean} [options.includeObjectsCollectedByMinorGC]
* @returns {Promise}
*/
startHeapProfile(options) {
normalizeHeapProfileOptions ??=
require('internal/v8/heap_profile').normalizeHeapProfileOptions;
const { sampleInterval, stackDepth, flags } =
normalizeHeapProfileOptions(options);
const startTaker = this[kHandle]?.startHeapProfile(
sampleInterval, stackDepth, flags);
return new Promise((resolve, reject) => {
if (!startTaker) return reject(new ERR_WORKER_NOT_RUNNING());
startTaker.ondone = (err) => {
Expand Down
41 changes: 40 additions & 1 deletion lib/v8.js
Loading
Loading