1616
1717const {
1818 Array,
19+ ArrayPrototypePush,
1920 BigInt64Array,
2021 BigUint64Array,
2122 DataView,
@@ -26,7 +27,9 @@ const {
2627 Int32Array,
2728 Int8Array,
2829 JSONParse,
30+ ObjectKeys,
2931 ObjectPrototypeToString,
32+ String,
3033 SymbolDispose,
3134 Uint16Array,
3235 Uint32Array,
@@ -38,7 +41,10 @@ const {
3841} = primordials ;
3942
4043const { Buffer } = require ( 'buffer' ) ;
44+ const { AsyncLocalStorage } = require ( 'async_hooks' ) ;
4145const {
46+ validateFunction,
47+ validateObject,
4248 validateString,
4349 validateUint32,
4450 validateOneOf,
@@ -156,6 +162,11 @@ const {
156162 heapSpaceStatisticsBuffer,
157163 getCppHeapStatistics : _getCppHeapStatistics ,
158164 detailLevel,
165+
166+ startSamplingHeapProfiler : _startSamplingHeapProfiler ,
167+ stopSamplingHeapProfiler : _stopSamplingHeapProfiler ,
168+ getAllocationProfile : _getAllocationProfile ,
169+ setHeapProfileLabelsStore : _setHeapProfileLabelsStore ,
159170} = binding ;
160171
161172const kNumberOfHeapSpaces = kHeapSpaces . length ;
@@ -494,6 +505,81 @@ class GCProfiler {
494505 }
495506}
496507
508+ // --- Heap profile labels API ---
509+ // Internal AsyncLocalStorage for propagating labels through async context.
510+ // Requires --experimental-async-context-frame (Node 22) or Node 24+.
511+ const _heapProfileLabelsALS = new AsyncLocalStorage ( ) ;
512+ // Register the ALS instance with C++ so the V8 callback can look up
513+ // label values from stored CPED (AsyncContextFrame Map) at read time.
514+ _setHeapProfileLabelsStore ( _heapProfileLabelsALS ) ;
515+
516+ /**
517+ * Convert a labels object to a flat array [key1, val1, key2, val2, ...].
518+ * Pre-flattened at label-set time (not per allocation/sample) because the
519+ * C++ callback runs during BuildSamples() iteration where V8 Object property
520+ * access could allocate and trigger GC, invalidating the sample iterator.
521+ * @param {Record<string, string> } labels
522+ * @returns {string[] }
523+ */
524+ function labelsToFlat ( labels ) {
525+ const keys = ObjectKeys ( labels ) ;
526+ const flat = [ ] ;
527+ for ( let i = 0 ; i < keys . length ; i ++ ) {
528+ ArrayPrototypePush ( flat , String ( keys [ i ] ) , String ( labels [ keys [ i ] ] ) ) ;
529+ }
530+ return flat ;
531+ }
532+
533+ /**
534+ * Starts the V8 sampling heap profiler.
535+ * @param {number } [sampleInterval] - Average bytes between samples (default 512 KB).
536+ * @param {number } [stackDepth] - Maximum stack depth for samples (default 16).
537+ * @param {object } [options] - Options object.
538+ * @param {boolean } [options.includeCollectedObjects] - If true, retain
539+ * samples for objects collected by GC (allocation-rate mode).
540+ */
541+ function startSamplingHeapProfiler ( sampleInterval , stackDepth , options ) {
542+ if ( sampleInterval !== undefined ) validateUint32 ( sampleInterval , 'sampleInterval' ) ;
543+ if ( stackDepth !== undefined ) validateUint32 ( stackDepth , 'stackDepth' ) ;
544+ if ( options !== undefined ) validateObject ( options , 'options' ) ;
545+ return _startSamplingHeapProfiler ( sampleInterval , stackDepth , options ) ;
546+ }
547+
548+ /**
549+ * Runs `fn` with the given heap profile labels active. Labels propagate
550+ * across `await` boundaries via AsyncLocalStorage. If `fn` returns a
551+ * Promise, labels remain active until the Promise settles.
552+ *
553+ * @param {Record<string, string> } labels
554+ * @param {Function } fn
555+ * @returns {* } The return value of `fn`.
556+ */
557+ function withHeapProfileLabels ( labels , fn ) {
558+ validateObject ( labels , 'labels' ) ;
559+ validateFunction ( fn , 'fn' ) ;
560+ // Store the flat [key1, val1, key2, val2, ...] array in ALS.
561+ // Conversion happens once at label-set time (not per allocation).
562+ // Must stay pre-flattened because the C++ callback runs during
563+ // BuildSamples iteration — Object property access would allocate
564+ // V8 objects, potentially triggering GC and invalidating the iterator.
565+ const flat = labelsToFlat ( labels ) ;
566+ return _heapProfileLabelsALS . run ( flat , fn ) ;
567+ }
568+
569+ /**
570+ * Sets heap profile labels for the current async scope using
571+ * `enterWith` semantics. Labels persist until overwritten or the
572+ * async scope ends. Useful for frameworks (e.g. Hapi) where the
573+ * handler runs after the extension returns.
574+ *
575+ * @param {Record<string, string> } labels
576+ */
577+ function setHeapProfileLabels ( labels ) {
578+ validateObject ( labels , 'labels' ) ;
579+ const flat = labelsToFlat ( labels ) ;
580+ _heapProfileLabelsALS . enterWith ( flat ) ;
581+ }
582+
497583module . exports = {
498584 cachedDataVersionTag,
499585 getHeapSnapshot,
@@ -518,4 +604,9 @@ module.exports = {
518604 GCProfiler,
519605 isStringOneByteRepresentation,
520606 startCpuProfile,
607+ startSamplingHeapProfiler,
608+ stopSamplingHeapProfiler : _stopSamplingHeapProfiler ,
609+ getAllocationProfile : _getAllocationProfile ,
610+ withHeapProfileLabels,
611+ setHeapProfileLabels,
521612} ;
0 commit comments