@@ -1446,6 +1446,90 @@ added:
14461446
14471447Returns true if the Node.js instance is run to build a snapshot.
14481448
1449+ ## Heap profile labels
1450+
1451+ <!-- YAML
1452+ added: REPLACEME
1453+ -->
1454+
1455+ > Stability: 1 - Experimental
1456+
1457+ Attach string labels to V8 sampling heap profiler allocation samples.
1458+ Combined with [` AsyncLocalStorage` ][], labels propagate through ` await `
1459+ boundaries for per-context memory attribution (e.g., per-HTTP-route).
1460+
1461+ ### ` v8 .withHeapProfileLabels (labels, fn)`
1462+
1463+ <!-- YAML
1464+ added: REPLACEME
1465+ -->
1466+
1467+ * ` labels` {Object} Key-value string pairs (e.g., ` { route: ' /users/:id' }` ).
1468+ * ` fn` {Function} May be ` async ` .
1469+ * Returns: {\* } Return value of ` fn` .
1470+
1471+ Runs ` fn` with the given labels active. If ` fn` returns a promise, labels
1472+ remain active until the promise settles.
1473+
1474+ ` ` ` mjs
1475+ const handle = v8 .startHeapProfile ({ sampleInterval: 64 , labels: true });
1476+
1477+ await v8 .withHeapProfileLabels ({ route: ' /users' }, async () => {
1478+ const data = await fetchUsers ();
1479+ return processData (data);
1480+ });
1481+
1482+ const profile = handle .getAllocationProfile ();
1483+ handle .stop ();
1484+ ` ` `
1485+
1486+ ### ` v8 .setHeapProfileLabels (labels)`
1487+
1488+ <!-- YAML
1489+ added: REPLACEME
1490+ -->
1491+
1492+ * ` labels` {Object} Key-value string pairs.
1493+
1494+ Sets labels for the current async scope using ` enterWith` semantics.
1495+ Useful for frameworks where the handler runs after the extension returns.
1496+
1497+ Prefer [` v8 .withHeapProfileLabels ()` ][] when possible for automatic cleanup.
1498+
1499+ ### Limitations — what is measured
1500+
1501+ Heap samples cover V8 heap allocations (JS objects, strings, closures).
1502+ ` externalBytes` covers ` Buffer` /` ArrayBuffer ` backing stores.
1503+
1504+ Not measured: native addon memory, JIT code space, OS-level allocations.
1505+
1506+ **Native addon ABI.** The ` v8:: AllocationProfile:: Sample` struct in
1507+ ` v8- profiler .h ` includes the ` label_id` field only when
1508+ ` V8_HEAP_PROFILER_SAMPLE_LABELS ` is defined at compile time. Node.js sets
1509+ this macro for its own builds and for addons compiled through node-gyp
1510+ (via ` common .gypi ` ). Addons built with other build systems (cmake, meson,
1511+ Makefile) must define ` - DV8_HEAP_PROFILER_SAMPLE_LABELS ` themselves to match
1512+ libnode. The ` label_id` field is appended after the pre-existing fields, so
1513+ their offsets never change. On the common 64-bit ABIs (and on 32-bit targets
1514+ whose ABI 8-aligns ` uint64_t` , such as Windows and ARM), it lands in existing
1515+ tail padding, ` sizeof (AllocationProfile:: Sample)` is unchanged, and iterating
1516+ ` GetSamples ()` strides correctly whether or not the macro is defined; an addon
1517+ only needs the macro to name ` label_id` . On i386 System V (32-bit x86 on
1518+ Linux and the BSDs), ` uint64_t` is 4-aligned and the field grows the struct by
1519+ 4 bytes, so an addon there must define the macro to stride ` GetSamples ()`
1520+ correctly. A ` static_assert` in V8 enforces the no-growth invariant on the
1521+ ABIs that provide the padding. When the macro is not defined, the
1522+ label APIs (` withHeapProfileLabels ()` , ` setHeapProfileLabels ()` ) are no-ops
1523+ and ` getAllocationProfile ()` omits ` samples[].labels ` and ` externalBytes`
1524+ entirely.
1525+
1526+ **Label availability.** Labels depend on async-context-frame, which propagates
1527+ the ` AsyncLocalStorage` map through continuation callbacks via
1528+ ` ContinuationPreservedEmbedderData` . The option is on by default; passing
1529+ ` -- no- async - context- frame` disables it, leaving every sample's ` labels` object
1530+ empty. A one-time ` process .emitWarning ` fires at first label use when the
1531+ option is off.
1532+
14491533## Class: ` v8 .GCProfiler `
14501534
14511535<!-- YAML
@@ -1618,6 +1702,57 @@ added: v26.1.0
16181702
16191703> Stability: 1 - Experimental
16201704
1705+ ### ` syncHeapProfileHandle .getAllocationProfile ()`
1706+
1707+ <!-- YAML
1708+ added: REPLACEME
1709+ -->
1710+
1711+ * Returns: {Object | undefined}
1712+
1713+ Returns the current allocation profile without stopping the profiler, or
1714+ ` undefined ` if the handle has already been stopped or if its session was
1715+ superseded by a newer one started on the same binding (for example when
1716+ an inspector ` HeapProfiler .stopSampling ` call ended the handle's V8
1717+ session out of band and a new session was subsequently started). In the
1718+ superseded case the handle carries a stale session identity and will never
1719+ return a profile; call ` stop ()` on the new handle instead. The method is
1720+ always available regardless of the ` labels` option. For sessions started with
1721+ ` labels: true ` , each sample's ` labels` object is populated with the
1722+ active label context at allocation time and ` externalBytes` is included
1723+ when labelled backing stores are live. For sessions started without
1724+ ` labels: true ` , each sample carries an empty ` labels` object and
1725+ ` externalBytes` is omitted.
1726+
1727+ ` ` ` json
1728+ {
1729+ " samples" : [
1730+ { " nodeId" : 1 , " size" : 128 , " count" : 4 , " sampleId" : 42 ,
1731+ " labels" : { " route" : " /users/:id" } }
1732+ ],
1733+ " externalBytes" : [
1734+ { " labels" : { " route" : " /users/:id" }, " bytes" : 1048576 }
1735+ ]
1736+ }
1737+ ` ` `
1738+
1739+ * ` samples[].labels ` — key-value string pairs from the active label context
1740+ at allocation time. Empty object if no labels were active. The object is
1741+ **frozen** and **shared** across all samples captured under the same
1742+ active label context — mutating it throws in strict mode.
1743+ * ` externalBytes[]` — live ` Buffer` /` ArrayBuffer ` backing-store bytes per
1744+ label context. Omitted when the profiling allocator is inactive, when no
1745+ labelled backing stores are live, or when all live stores were allocated
1746+ under an empty label set.
1747+
1748+ **Label memory model.** The V8 heap profiler retains one copy of each
1749+ distinct label set for the profiler's lifetime. In the default mode
1750+ (` includeObjectsCollectedByMajorGC: false ` ) samples are dropped when their
1751+ objects are GC'd. With ` includeObjectsCollectedByMajorGC: true ` or
1752+ ` includeObjectsCollectedByMinorGC: true ` , dead samples are kept and each
1753+ unique label set pins one internal array for the profiler's lifetime —
1754+ label cardinality should be bounded to avoid unbounded growth.
1755+
16211756### ` syncHeapProfileHandle .stop ()`
16221757
16231758<!-- YAML
@@ -1626,7 +1761,17 @@ added: v26.1.0
16261761
16271762* Returns: {string}
16281763
1629- Stopping collecting the profile and return the profile data.
1764+ Stops the profiler and returns the profile as a DevTools-compatible JSON
1765+ string. Each sample in the returned JSON contains ` size` , ` nodeId` , and
1766+ ` ordinal` fields. The returned string does not include ` labels` or
1767+ ` externalBytes` ; labelled allocation data is available mid-session through
1768+ ` syncHeapProfileHandle .getAllocationProfile ()` .
1769+
1770+ Returns ` undefined ` without stopping anything if ` stop ()` is called on a
1771+ handle whose session was superseded (see ` getAllocationProfile ()` above).
1772+ Throws ` ERR_HEAP_PROFILE_NOT_STARTED ` if ` stop ()` is called on a handle
1773+ whose V8 session was ended out of band and no subsequent session has yet
1774+ started.
16301775
16311776### ` syncHeapProfileHandle[Symbol .dispose ]()`
16321777
@@ -1804,6 +1949,14 @@ added: v26.1.0
18041949 by major GC. **Default:** ` false ` .
18051950 * ` includeObjectsCollectedByMinorGC` {boolean} Include objects collected
18061951 by minor GC. **Default:** ` false ` .
1952+ * ` labels` {boolean} Enable per-allocation label attribution via
1953+ [` v8 .withHeapProfileLabels ()` ][] and [` v8 .setHeapProfileLabels ()` ][].
1954+ When ` true ` , each sample's ` labels` object in ` getAllocationProfile ()`
1955+ is populated with the active label context at allocation time, and
1956+ ` externalBytes` entries are included when labelled backing stores are
1957+ live. Labelled data is only available through ` getAllocationProfile ()` ;
1958+ ` stop ()` always returns the unlabelled DevTools JSON string.
1959+ **Default:** ` false ` .
18071960* Returns: {SyncHeapProfileHandle}
18081961
18091962Starting a heap profile then return a ` SyncHeapProfileHandle` object.
@@ -1887,7 +2040,9 @@ console.log(profile);
18872040[` serializer .transferArrayBuffer ()` ]: #serializertransferarraybufferid-arraybuffer
18882041[` serializer .writeRawBytes ()` ]: #serializerwriterawbytesbuffer
18892042[` settled` callback]: #settledpromise
2043+ [` v8 .setHeapProfileLabels ()` ]: #v8setheapprofilelabelslabels
18902044[` v8 .stopCoverage ()` ]: #v8stopcoverage
18912045[` v8 .takeCoverage ()` ]: #v8takecoverage
2046+ [` v8 .withHeapProfileLabels ()` ]: #v8withheapprofilelabelslabels-fn
18922047[` vm .Script ` ]: vm.md#new-vmscriptcode-options
18932048[worker threads]: worker_threads.md
0 commit comments