diff --git a/doc/api/v8.md b/doc/api/v8.md index db92ad062b7e63..04ffe657351c0a 100644 --- a/doc/api/v8.md +++ b/doc/api/v8.md @@ -7,7 +7,11 @@ The `node:v8` module exposes APIs that are specific to the version of [V8][] built into the Node.js binary. It can be accessed using: -```js +```mjs +import v8 from 'node:v8'; +``` + +```cjs const v8 = require('node:v8'); ``` @@ -92,9 +96,18 @@ terminating the process. Generating a snapshot is a synchronous operation which blocks the event loop for a duration depending on the heap size. +```mjs +// Print heap snapshot to the console +import { getHeapSnapshot } from 'node:v8'; +import process from 'node:process'; +const stream = getHeapSnapshot(); +stream.pipe(process.stdout); +``` + ```js // Print heap snapshot to the console const v8 = require('node:v8'); +const process = require('node:process'); const stream = v8.getHeapSnapshot(); stream.pipe(process.stdout); ``` @@ -467,11 +480,70 @@ The V8 options available for a version of Node.js may be determined by running Usage: -```js -// Print GC events to stdout for one minute. -const v8 = require('node:v8'); -v8.setFlagsFromString('--trace_gc'); -setTimeout(() => { v8.setFlagsFromString('--notrace_gc'); }, 60e3); +```mjs +import { setFlagsFromString } from 'node:v8'; +import { setInterval } from 'node:timers'; + +// setFlagsFromString to trace garbage collection events +setFlagsFromString('--trace-gc'); + +// Trigger GC events by using some memory +let arrays = []; +const interval = setInterval(() => { + for (let i = 0; i < 500; i++) { + arrays.push(new Array(10000).fill(Math.random())); + } + + if (arrays.length > 5000) { + arrays = arrays.slice(-1000); + } + + console.log(`\n* Created ${arrays.length} arrays\n`); +}, 100); + +// setFlagsFromString to stop tracing GC events after 1.5 seconds +setTimeout(() => { + setFlagsFromString('--notrace-gc'); + console.log('\nStopped tracing!\n'); +}, 1500); + +// Stop triggering GC events altogether after 2.5 seconds +setTimeout(() => { + clearInterval(interval); +}, 2500); +``` + +```cjs +const { setFlagsFromString } = require('node:v8'); +const { setInterval } = require('node:timers'); + +// setFlagsFromString to trace garbage collection events +setFlagsFromString('--trace-gc'); + +// Trigger GC events by using some memory +let arrays = []; +const interval = setInterval(() => { + for (let i = 0; i < 500; i++) { + arrays.push(new Array(10000).fill(Math.random())); + } + + if (arrays.length > 5000) { + arrays = arrays.slice(-1000); + } + + console.log(`\n* Created ${arrays.length} arrays\n`); +}, 100); + +// setFlagsFromString to stop tracing GC events after 1.5 seconds +setTimeout(() => { + console.log('\nStopped tracing!\n'); + setFlagsFromString('--notrace-gc'); +}, 1500); + +// Stop triggering GC events altogether after 2.5 seconds +setTimeout(() => { + clearInterval(interval); +}, 2500); ``` ## `v8.stopCoverage()` @@ -551,13 +623,37 @@ terminating the process. Generating a snapshot is a synchronous operation which blocks the event loop for a duration depending on the heap size. -```js +```mjs +import { writeHeapSnapshot } from 'node:v8'; +import { Worker, isMainThread, parentPort } from 'node:worker_threads'; +import { fileURLToPath } from 'node:url'; + +if (isMainThread) { + const __filename = fileURLToPath(import.meta.url); + const worker = new Worker(__filename); + + worker.once('message', (filename) => { + console.log(`worker heapdump: ${filename}`); + // Now get a heapdump for the main thread. + console.log(`main thread heapdump: ${writeHeapSnapshot()}`); + }); + + // Tell the worker to create a heapdump. + worker.postMessage('heapdump'); +} else { + parentPort.once('message', (message) => { + if (message === 'heapdump') { + // Generate a heapdump for the worker + // and return the filename to the parent. + parentPort.postMessage(writeHeapSnapshot()); + } + }); +} +``` + +```cjs const { writeHeapSnapshot } = require('node:v8'); -const { - Worker, - isMainThread, - parentPort, -} = require('node:worker_threads'); +const { Worker, isMainThread, parentPort } = require('node:worker_threads'); if (isMainThread) { const worker = new Worker(__filename); @@ -903,6 +999,71 @@ const stopHookSet = promiseHooks.createHook({ after, }); +// Trigger the hooks by using promises +const promiseLog = (word) => Promise.resolve(word).then(console.log); +promiseLog('Hello'); +promiseLog('World'); + +// To stop a hook, call the function returned at its creation. +stopWatchingInits(); +stopWatchingSettleds(); +stopWatchingBefores(); +stopWatchingAfters(); +stopHookSet(); +``` + +```cjs +const { promiseHooks } = require('node:v8'); + +// There are four lifecycle events produced by promises: + +// The `init` event represents the creation of a promise. This could be a +// direct creation such as with `new Promise(...)` or a continuation such +// as `then()` or `catch()`. It also happens whenever an async function is +// called or does an `await`. If a continuation promise is created, the +// `parent` will be the promise it is a continuation from. +function init(promise, parent) { + console.log('a promise was created', { promise, parent }); +} + +// The `settled` event happens when a promise receives a resolution or +// rejection value. This may happen synchronously such as when using +// `Promise.resolve()` on non-promise input. +function settled(promise) { + console.log('a promise resolved or rejected', { promise }); +} + +// The `before` event runs immediately before a `then()` or `catch()` handler +// runs or an `await` resumes execution. +function before(promise) { + console.log('a promise is about to call a then handler', { promise }); +} + +// The `after` event runs immediately after a `then()` handler runs or when +// an `await` begins after resuming from another. +function after(promise) { + console.log('a promise is done calling a then handler', { promise }); +} + +// Lifecycle hooks may be started and stopped individually +const stopWatchingInits = promiseHooks.onInit(init); +const stopWatchingSettleds = promiseHooks.onSettled(settled); +const stopWatchingBefores = promiseHooks.onBefore(before); +const stopWatchingAfters = promiseHooks.onAfter(after); + +// Or they may be started and stopped in groups +const stopHookSet = promiseHooks.createHook({ + init, + settled, + before, + after, +}); + +// Trigger the hooks by using promises +const promisePrint = (word) => Promise.resolve(word).then(console.log); +promisePrint('Hello'); +promisePrint('World'); + // To stop a hook, call the function returned at its creation. stopWatchingInits(); stopWatchingSettleds(); @@ -1396,7 +1557,16 @@ is as follows. Here's an example. -```js +```mjs +import { GCProfiler } from 'node:v8'; +const profiler = new GCProfiler(); +profiler.start(); +setTimeout(() => { + console.log(profiler.stop()); +}, 1000); +``` + +```cjs const { GCProfiler } = require('node:v8'); const profiler = new GCProfiler(); profiler.start(); @@ -1507,8 +1677,37 @@ otherwise, it returns false. If this method returns false, that does not mean that the string contains some characters not in `Latin-1/ISO-8859-1`. Sometimes a `Latin-1` string may also be represented as `UTF16`. -```js +```mjs +import { isStringOneByteRepresentation } from 'node:v8'; +import { Buffer } from 'node:buffer'; + +const Encoding = { + latin1: 1, + utf16le: 2, +}; +const buffer = Buffer.alloc(100); +function writeString(input) { + if (isStringOneByteRepresentation(input)) { + console.log(`input: '${input}'`); + buffer.writeUint8(Encoding.latin1); + buffer.writeUint32LE(input.length, 1); + buffer.write(input, 5, 'latin1'); + console.log(`decoded: '${buffer.toString('latin1', 5, 5 + input.length)}'\n`); + } else { + console.log(`input: '${input}'`); + buffer.writeUint8(Encoding.utf16le); + buffer.writeUint32LE(input.length * 2, 1); + buffer.write(input, 5, 'utf16le'); + console.log(`decoded: '${buffer.toString('utf16le', 5, 5 + input.length * 2)}'`); + } +} +writeString('hello'); +writeString('你好'); +``` + +```cjs const { isStringOneByteRepresentation } = require('node:v8'); +const { Buffer } = require('node:buffer'); const Encoding = { latin1: 1, @@ -1517,13 +1716,17 @@ const Encoding = { const buffer = Buffer.alloc(100); function writeString(input) { if (isStringOneByteRepresentation(input)) { + console.log(`input: '${input}'`); buffer.writeUint8(Encoding.latin1); buffer.writeUint32LE(input.length, 1); buffer.write(input, 5, 'latin1'); + console.log(`decoded: '${buffer.toString('latin1', 5, 5 + input.length)}'\n`); } else { + console.log(`input: '${input}'`); buffer.writeUint8(Encoding.utf16le); buffer.writeUint32LE(input.length * 2, 1); buffer.write(input, 5, 'utf16le'); + console.log(`decoded: '${buffer.toString('utf16le', 5, 5 + input.length * 2)}'`); } } writeString('hello');