From a0b9aa00bcc04709d8760daa8592ab5aa186fb00 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 12 Feb 2026 23:07:10 +0100 Subject: [PATCH 1/2] test: add retry logic for eventloopdelay histogram sampling On some build configurations (e.g., sharedlibs), the histogram may not record valid samples (min > 0) after the initial spinning period. This adds a retry mechanism that will spin the event loop additional times if valid samples haven't been recorded yet. This helps ensure the test passes on slower or differently-configured systems where event loop delay sampling may take longer to produce measurable results. Refs: https://github.com/nodejs/reliability/issues/1461 --- .../test-performance-eventloopdelay.js | 107 ++++++++++-------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/test/sequential/test-performance-eventloopdelay.js b/test/sequential/test-performance-eventloopdelay.js index 72e6f7abfef3c2..62ac8a6f34c962 100644 --- a/test/sequential/test-performance-eventloopdelay.js +++ b/test/sequential/test-performance-eventloopdelay.js @@ -3,7 +3,6 @@ const common = require('../common'); const assert = require('assert'); -const os = require('os'); const { monitorEventLoopDelay, } = require('perf_hooks'); @@ -52,59 +51,77 @@ const { sleep } = require('internal/util'); } { - const s390x = os.arch() === 's390x'; const histogram = monitorEventLoopDelay({ resolution: 1 }); histogram.enable(); - let m = 5; - if (s390x) { - m = m * 2; + + // Check if histogram has recorded valid samples (min > 0 indicates real delays measured) + function hasValidSamples() { + return histogram.count > 0 && histogram.min > 0 && histogram.max > 0; } + + // Spin the event loop with blocking work to generate measurable delays. + // Some configurations (s390x, sharedlibs) need more iterations. + let spinsRemaining = 5; + const maxRetries = 3; + let retries = 0; + function spinAWhile() { sleep(1000); - if (--m > 0) { + if (--spinsRemaining > 0) { setTimeout(spinAWhile, common.platformTimeout(500)); } else { - // Give the histogram a chance to record final samples before disabling. - // This helps on slower systems where sampling may be delayed. - setImmediate(common.mustCall(() => { - histogram.disable(); - // The values are non-deterministic, so we just check that a value is - // present, as opposed to a specific value. - assert(histogram.count > 0, `Expected samples to be recorded, got count=${histogram.count}`); - assert(histogram.min > 0); - assert(histogram.max > 0); - assert(histogram.stddev > 0); - assert(histogram.mean > 0); - assert(histogram.percentiles.size > 0); - for (let n = 1; n < 100; n = n + 0.1) { - assert(histogram.percentile(n) >= 0); + // Give the histogram a chance to record final samples before checking. + setImmediate(() => { + // If we don't have valid samples yet, retry with more spinning. + // This handles slower configurations like sharedlibs builds. + if (!hasValidSamples() && retries < maxRetries) { + retries++; + spinsRemaining = 5; + setTimeout(spinAWhile, common.platformTimeout(500)); + return; } - histogram.reset(); - assert.strictEqual(histogram.min, 9223372036854776000); - assert.strictEqual(histogram.max, 0); - assert(Number.isNaN(histogram.stddev)); - assert(Number.isNaN(histogram.mean)); - assert.strictEqual(histogram.percentiles.size, 1); - ['a', false, {}, []].forEach((i) => { - assert.throws( - () => histogram.percentile(i), - { - name: 'TypeError', - code: 'ERR_INVALID_ARG_TYPE', - } - ); - }); - [-1, 0, 101, NaN].forEach((i) => { - assert.throws( - () => histogram.percentile(i), - { - name: 'RangeError', - code: 'ERR_OUT_OF_RANGE', - } - ); - }); - })); + // Wrap final assertions in mustCall to ensure they run + common.mustCall(() => { + histogram.disable(); + // The values are non-deterministic, so we just check that a value is + // present, as opposed to a specific value. + assert(histogram.count > 0, `Expected samples to be recorded, got count=${histogram.count}`); + assert(histogram.min > 0, `Expected min > 0, got ${histogram.min}`); + assert(histogram.max > 0); + assert(histogram.stddev > 0); + assert(histogram.mean > 0); + assert(histogram.percentiles.size > 0); + for (let n = 1; n < 100; n = n + 0.1) { + assert(histogram.percentile(n) >= 0); + } + histogram.reset(); + assert.strictEqual(histogram.min, 9223372036854776000); + assert.strictEqual(histogram.max, 0); + assert(Number.isNaN(histogram.stddev)); + assert(Number.isNaN(histogram.mean)); + assert.strictEqual(histogram.percentiles.size, 1); + + ['a', false, {}, []].forEach((i) => { + assert.throws( + () => histogram.percentile(i), + { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + } + ); + }); + [-1, 0, 101, NaN].forEach((i) => { + assert.throws( + () => histogram.percentile(i), + { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + } + ); + }); + })(); + }); } } spinAWhile(); From 64c291afb908e3e6d1686a23b1c196b7f054323a Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sat, 14 Feb 2026 17:47:16 +0100 Subject: [PATCH 2/2] test: refactor eventloopdelay test for clarity Extract assertions into separate function and simplify control flow. Use setImmediate for final assertions to ensure histogram has recorded all samples. --- .../test-performance-eventloopdelay.js | 93 ++++++++++--------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/test/sequential/test-performance-eventloopdelay.js b/test/sequential/test-performance-eventloopdelay.js index 62ac8a6f34c962..06d4d5df244c63 100644 --- a/test/sequential/test-performance-eventloopdelay.js +++ b/test/sequential/test-performance-eventloopdelay.js @@ -59,6 +59,49 @@ const { sleep } = require('internal/util'); return histogram.count > 0 && histogram.min > 0 && histogram.max > 0; } + // Final assertions - call this when ready to verify histogram values + function runAssertions() { + setImmediate(common.mustCall(() => { + histogram.disable(); + // The values are non-deterministic, so we just check that a value is + // present, as opposed to a specific value. + assert(histogram.count > 0, `Expected samples to be recorded, got count=${histogram.count}`); + assert(histogram.min > 0, `Expected min > 0, got ${histogram.min}`); + assert(histogram.max > 0); + assert(histogram.stddev > 0); + assert(histogram.mean > 0); + assert(histogram.percentiles.size > 0); + for (let n = 1; n < 100; n = n + 0.1) { + assert(histogram.percentile(n) >= 0); + } + histogram.reset(); + assert.strictEqual(histogram.min, 9223372036854776000); + assert.strictEqual(histogram.max, 0); + assert(Number.isNaN(histogram.stddev)); + assert(Number.isNaN(histogram.mean)); + assert.strictEqual(histogram.percentiles.size, 1); + + ['a', false, {}, []].forEach((i) => { + assert.throws( + () => histogram.percentile(i), + { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + } + ); + }); + [-1, 0, 101, NaN].forEach((i) => { + assert.throws( + () => histogram.percentile(i), + { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + } + ); + }); + })); + } + // Spin the event loop with blocking work to generate measurable delays. // Some configurations (s390x, sharedlibs) need more iterations. let spinsRemaining = 5; @@ -72,55 +115,15 @@ const { sleep } = require('internal/util'); } else { // Give the histogram a chance to record final samples before checking. setImmediate(() => { - // If we don't have valid samples yet, retry with more spinning. - // This handles slower configurations like sharedlibs builds. - if (!hasValidSamples() && retries < maxRetries) { + // If we have valid samples or exhausted retries, run assertions. + // Otherwise retry with more spinning for slower configurations. + if (hasValidSamples() || retries >= maxRetries) { + runAssertions(); + } else { retries++; spinsRemaining = 5; setTimeout(spinAWhile, common.platformTimeout(500)); - return; } - - // Wrap final assertions in mustCall to ensure they run - common.mustCall(() => { - histogram.disable(); - // The values are non-deterministic, so we just check that a value is - // present, as opposed to a specific value. - assert(histogram.count > 0, `Expected samples to be recorded, got count=${histogram.count}`); - assert(histogram.min > 0, `Expected min > 0, got ${histogram.min}`); - assert(histogram.max > 0); - assert(histogram.stddev > 0); - assert(histogram.mean > 0); - assert(histogram.percentiles.size > 0); - for (let n = 1; n < 100; n = n + 0.1) { - assert(histogram.percentile(n) >= 0); - } - histogram.reset(); - assert.strictEqual(histogram.min, 9223372036854776000); - assert.strictEqual(histogram.max, 0); - assert(Number.isNaN(histogram.stddev)); - assert(Number.isNaN(histogram.mean)); - assert.strictEqual(histogram.percentiles.size, 1); - - ['a', false, {}, []].forEach((i) => { - assert.throws( - () => histogram.percentile(i), - { - name: 'TypeError', - code: 'ERR_INVALID_ARG_TYPE', - } - ); - }); - [-1, 0, 101, NaN].forEach((i) => { - assert.throws( - () => histogram.percentile(i), - { - name: 'RangeError', - code: 'ERR_OUT_OF_RANGE', - } - ); - }); - })(); }); } }