From 816b30f277f3f27caffe14b1912a95848e9a40b6 Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Fri, 12 Jun 2026 17:02:25 +0200 Subject: [PATCH 1/2] perf_hooks: add NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP constant V8's Minor Mark-Sweep collector, enabled with the --minor-ms flag (available since Node.js 22), reports garbage collection performance entries with kind kGCTypeMinorMarkSweep (value 2). perf_hooks exposed constants for every other GC kind (major, minor, incremental, weakcb) but not this one, so consumers inspecting performanceEntry.detail.kind had no constant to compare against and saw an unmapped value. Expose it as perf_hooks.constants.NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP, mirroring the existing v8::GCType mappings, and document it alongside the other GC kinds. Signed-off-by: Attila Szegedi --- doc/api/perf_hooks.md | 2 ++ src/node_perf.cc | 1 + src/node_perf.h | 1 + test/parallel/test-performance-gc-minor-ms.js | 33 +++++++++++++++++++ test/parallel/test-performance-gc.js | 2 ++ 5 files changed, 39 insertions(+) create mode 100644 test/parallel/test-performance-gc-minor-ms.js diff --git a/doc/api/perf_hooks.md b/doc/api/perf_hooks.md index 61634056679062..fbd1d2e08a8c30 100644 --- a/doc/api/perf_hooks.md +++ b/doc/api/perf_hooks.md @@ -643,6 +643,7 @@ The value may be one of: * `perf_hooks.constants.NODE_PERFORMANCE_GC_MAJOR` * `perf_hooks.constants.NODE_PERFORMANCE_GC_MINOR` +* `perf_hooks.constants.NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP` * `perf_hooks.constants.NODE_PERFORMANCE_GC_INCREMENTAL` * `perf_hooks.constants.NODE_PERFORMANCE_GC_WEAKCB` @@ -654,6 +655,7 @@ When `performanceEntry.type` is equal to `'gc'`, the * `kind` {number} One of: * `perf_hooks.constants.NODE_PERFORMANCE_GC_MAJOR` * `perf_hooks.constants.NODE_PERFORMANCE_GC_MINOR` + * `perf_hooks.constants.NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP` * `perf_hooks.constants.NODE_PERFORMANCE_GC_INCREMENTAL` * `perf_hooks.constants.NODE_PERFORMANCE_GC_WEAKCB` * `flags` {number} One of: diff --git a/src/node_perf.cc b/src/node_perf.cc index e984fd4c3bf003..ca1b2eaf1c18b3 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -365,6 +365,7 @@ void CreatePerContextProperties(Local target, NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_MAJOR); NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_MINOR); + NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP); NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_INCREMENTAL); NODE_DEFINE_CONSTANT(constants, NODE_PERFORMANCE_GC_WEAKCB); diff --git a/src/node_perf.h b/src/node_perf.h index 79b3aaf8bb7f5f..e518ae1c33d589 100644 --- a/src/node_perf.h +++ b/src/node_perf.h @@ -63,6 +63,7 @@ inline PerformanceEntryType ToPerformanceEntryTypeEnum( enum PerformanceGCKind { NODE_PERFORMANCE_GC_MAJOR = v8::GCType::kGCTypeMarkSweepCompact, NODE_PERFORMANCE_GC_MINOR = v8::GCType::kGCTypeScavenge, + NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP = v8::GCType::kGCTypeMinorMarkSweep, NODE_PERFORMANCE_GC_INCREMENTAL = v8::GCType::kGCTypeIncrementalMarking, NODE_PERFORMANCE_GC_WEAKCB = v8::GCType::kGCTypeProcessWeakCallbacks }; diff --git a/test/parallel/test-performance-gc-minor-ms.js b/test/parallel/test-performance-gc-minor-ms.js new file mode 100644 index 00000000000000..dca6f2372eee2b --- /dev/null +++ b/test/parallel/test-performance-gc-minor-ms.js @@ -0,0 +1,33 @@ +// Flags: --expose-gc --no-warnings --minor-ms +'use strict'; + +// When V8's Minor Mark-Sweep collector is enabled (--minor-ms), minor garbage +// collections are reported with kind NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP +// rather than NODE_PERFORMANCE_GC_MINOR. + +const common = require('../common'); +const assert = require('assert'); +const { + PerformanceObserver, + constants +} = require('perf_hooks'); + +const { + NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP, + NODE_PERFORMANCE_GC_FLAGS_FORCED +} = constants; + +const obs = new PerformanceObserver(common.mustCallAtLeast((list) => { + const entry = list.getEntries()[0]; + assert(entry); + assert.strictEqual(entry.name, 'gc'); + assert.strictEqual(entry.entryType, 'gc'); + assert.strictEqual(entry.detail.kind, NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP); + assert.strictEqual(entry.detail.flags, NODE_PERFORMANCE_GC_FLAGS_FORCED); + obs.disconnect(); +})); +obs.observe({ entryTypes: ['gc'] }); + +globalThis.gc({ type: 'minor' }); +// Keep the event loop alive to witness the GC async callback happen. +setImmediate(() => setImmediate(() => 0)); diff --git a/test/parallel/test-performance-gc.js b/test/parallel/test-performance-gc.js index 4fad8b5b9ff349..0c2ea201fc630a 100644 --- a/test/parallel/test-performance-gc.js +++ b/test/parallel/test-performance-gc.js @@ -11,6 +11,7 @@ const { const { NODE_PERFORMANCE_GC_MAJOR, NODE_PERFORMANCE_GC_MINOR, + NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP, NODE_PERFORMANCE_GC_INCREMENTAL, NODE_PERFORMANCE_GC_WEAKCB, NODE_PERFORMANCE_GC_FLAGS_FORCED @@ -19,6 +20,7 @@ const { const kinds = [ NODE_PERFORMANCE_GC_MAJOR, NODE_PERFORMANCE_GC_MINOR, + NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP, NODE_PERFORMANCE_GC_INCREMENTAL, NODE_PERFORMANCE_GC_WEAKCB, ]; From 4a6995c5fb954ef66c23cdd5a14fb50ed3badaf7 Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Mon, 15 Jun 2026 10:55:59 +0200 Subject: [PATCH 2/2] test: use gcUntil to reduce flakiness in minor-ms gc perf test Signed-off-by: Attila Szegedi --- test/parallel/test-performance-gc-minor-ms.js | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/test/parallel/test-performance-gc-minor-ms.js b/test/parallel/test-performance-gc-minor-ms.js index dca6f2372eee2b..322d22c6b00201 100644 --- a/test/parallel/test-performance-gc-minor-ms.js +++ b/test/parallel/test-performance-gc-minor-ms.js @@ -7,27 +7,30 @@ const common = require('../common'); const assert = require('assert'); +const { gcUntil } = require('../common/gc'); const { PerformanceObserver, constants } = require('perf_hooks'); const { - NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP, - NODE_PERFORMANCE_GC_FLAGS_FORCED + NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP } = constants; +let observed = false; const obs = new PerformanceObserver(common.mustCallAtLeast((list) => { - const entry = list.getEntries()[0]; - assert(entry); - assert.strictEqual(entry.name, 'gc'); - assert.strictEqual(entry.entryType, 'gc'); - assert.strictEqual(entry.detail.kind, NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP); - assert.strictEqual(entry.detail.flags, NODE_PERFORMANCE_GC_FLAGS_FORCED); - obs.disconnect(); + for (const entry of list.getEntries()) { + // Other GC kinds (e.g. a scheduled or incremental GC) may be observed in + // between; only the minor mark-sweep entry is relevant here. + if (entry.detail.kind === NODE_PERFORMANCE_GC_MINOR_MARK_SWEEP) { + assert.strictEqual(entry.name, 'gc'); + assert.strictEqual(entry.entryType, 'gc'); + observed = true; + obs.disconnect(); + return; + } + } })); obs.observe({ entryTypes: ['gc'] }); -globalThis.gc({ type: 'minor' }); -// Keep the event loop alive to witness the GC async callback happen. -setImmediate(() => setImmediate(() => 0)); +gcUntil('minor gc event', () => observed, 10, { type: 'minor' });