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 9880e424dff1b23eef5253ca3f79159c77d188ec 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 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/parallel/test-performance-gc-minor-ms.js b/test/parallel/test-performance-gc-minor-ms.js index dca6f2372eee2b..6333159f42ba2b 100644 --- a/test/parallel/test-performance-gc-minor-ms.js +++ b/test/parallel/test-performance-gc-minor-ms.js @@ -7,6 +7,7 @@ const common = require('../common'); const assert = require('assert'); +const { gcUntil } = require('../common/gc'); const { PerformanceObserver, constants @@ -17,6 +18,7 @@ const { NODE_PERFORMANCE_GC_FLAGS_FORCED } = constants; +let observed = false; const obs = new PerformanceObserver(common.mustCallAtLeast((list) => { const entry = list.getEntries()[0]; assert(entry); @@ -24,10 +26,9 @@ const obs = new PerformanceObserver(common.mustCallAtLeast((list) => { 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); + observed = true; 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)); +gcUntil('minor gc event', () => observed, 10, { type: 'minor' });