Commit 2ce7a73fec4 for nodejs

commit 2ce7a73fec4478a340754ee81bc1fc66bef3e250
Author: James M Snell <jasnell@gmail.com>
Date:   Sun Sep 27 01:10:15 2026 -0700

    perf_hooks: fix truncation of monitorEventLoopDelay() resolution

    `IntervalHistogram` stored the interval as `int32_t`, so a resolution
    above 2^31 - 1 ms wrapped: `resolution: 2 ** 32 + 1` sampled every
    millisecond.

    Signed-off-by: James M Snell <jasnell@gmail.com>
    Assisted-by: OpenCode
    PR-URL: https://github.com/nodejs/node/pull/66115
    Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
    Reviewed-By: Filip Skokan <panva.ip@gmail.com>

diff --git a/doc/api/perf_hooks.md b/doc/api/perf_hooks.md
index f9533c1260a..dcdfd0f93c5 100644
--- a/doc/api/perf_hooks.md
+++ b/doc/api/perf_hooks.md
@@ -1926,6 +1926,9 @@ are not guaranteed to reflect any correct state of the event loop.
 <!-- YAML
 added: v11.10.0
 changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/66115
+    description: Added the `lowest`, `highest`, and `figures` options.
   - version:
      - v26.5.0
      - v24.19.0
@@ -1939,6 +1942,14 @@ changes:
   * `resolution` {number} The sampling rate in milliseconds for interval-based
     sampling. Must be greater than zero. This option is ignored when
     `samplePerIteration` is `true`. **Default:** `10`.
+  * `lowest` {number|bigint} The lowest discernible delay, in nanoseconds. Must
+    be an integer value greater than `0`. **Default:** `1` when
+    `samplePerIteration` is `true`, otherwise `1000`.
+  * `highest` {number|bigint} The highest recordable delay, in nanoseconds.
+    Must be an integer value that is equal to or greater than two times
+    `lowest`. **Default:** `2n ** 63n - 1n`.
+  * `figures` {number} The number of accuracy digits. Must be an integer
+    between `1` and `5`. **Default:** `3`.
 * Returns: {ELDHistogram}

 _This property is an extension by Node.js. It is not available in Web browsers._
@@ -1954,6 +1965,16 @@ the application is idle.
 The two sampling modes produce significantly different results and should not
 be compared directly.

+The `lowest`, `highest`, and `figures` options configure the histogram as they
+do for [`perf_hooks.createHistogram()`][]. `lowest` must be greater than `0`
+because an event loop delay of zero is not possible: the event loop has a
+minimal overhead, and the measurement itself depends on the event loop turning.
+Delays greater than `highest` are not recorded, and are counted by
+[`histogram.exceeds`][] instead. With interval-based sampling, every sample
+includes the `resolution`, so `highest` should be well above
+`resolution * 1e6`. The histogram's memory use depends on these options, not
+on the number of samples.
+
 ```mjs
 import { monitorEventLoopDelay } from 'node:perf_hooks';

@@ -2253,8 +2274,8 @@ added: v11.10.0

 * Type: {number}

-The number of times the event loop delay exceeded the maximum 1 hour event
-loop delay threshold.
+The number of values that were not recorded because they exceeded the
+histogram's highest recordable value.

 ### `histogram.exceedsBigInt`

@@ -2266,8 +2287,8 @@ added:

 * Type: {bigint}

-The number of times the event loop delay exceeded the maximum 1 hour event
-loop delay threshold.
+The number of values that were not recorded because they exceeded the
+histogram's highest recordable value.

 ### `histogram.export()`

@@ -3432,7 +3453,9 @@ dns.promises.resolve('localhost');
 [`'exit'`]: process.md#event-exit
 [`child_process.spawnSync()`]: child_process.md#child_processspawnsynccommand-args-options
 [`histogram.diff()`]: #histogramdiffother
+[`histogram.exceeds`]: #histogramexceeds
 [`histogram.export()`]: #histogramexport
+[`perf_hooks.createHistogram()`]: #perf_hookscreatehistogramoptions
 [`perf_hooks.createSlidingWindowHistogram()`]: #perf_hookscreateslidingwindowhistogramoptions
 [`perf_hooks.eventLoopUtilization()`]: #perf_hookseventlooputilizationutilization1-utilization2
 [`perf_hooks.importHistogram()`]: #perf_hooksimporthistogramdata
diff --git a/lib/internal/histogram.js b/lib/internal/histogram.js
index 4014f3aeb74..9366eb13543 100644
--- a/lib/internal/histogram.js
+++ b/lib/internal/histogram.js
@@ -1041,8 +1041,10 @@ module.exports = {
   isHistogram,
   kDestroy,
   kHandle,
+  kMaxInt64,
   kSkipThrow,
   createHistogram,
   createSlidingWindowHistogram,
   importHistogram,
+  validateHistogramOptions,
 };
diff --git a/lib/internal/perf/event_loop_delay.js b/lib/internal/perf/event_loop_delay.js
index 4d14182c83f..541d6e38fcb 100644
--- a/lib/internal/perf/event_loop_delay.js
+++ b/lib/internal/perf/event_loop_delay.js
@@ -1,5 +1,6 @@
 'use strict';
 const {
+  BigInt,
   Symbol,
   SymbolDispose,
 } = primordials;
@@ -24,7 +25,9 @@ const {
 const {
   Histogram,
   kHandle,
+  kMaxInt64,
   kSkipThrow,
+  validateHistogramOptions,
 } = require('internal/histogram');

 const {
@@ -37,6 +40,12 @@ const {

 const kEnabled = Symbol('kEnabled');

+// Default histogram options. The lowest discernible delay is in nanoseconds,
+// and its default depends on the sampling mode.
+const kDefaultIntervalLowest = 1000;
+const kDefaultIterationLowest = 1;
+const kDefaultFigures = 3;
+
 class ELDHistogram extends Histogram {
   constructor(skipThrowSymbol = undefined) {
     if (skipThrowSymbol !== kSkipThrow) {
@@ -76,8 +85,11 @@ class ELDHistogram extends Histogram {

 /**
  * @param {{
- *   samplePerIteration : boolean,
- *   resolution : number
+ *   samplePerIteration? : boolean,
+ *   resolution? : number,
+ *   lowest? : number|bigint,
+ *   highest? : number|bigint,
+ *   figures? : number,
  * }} [options]
  * @returns {ELDHistogram}
  */
@@ -88,10 +100,22 @@ function monitorEventLoopDelay(options = kEmptyObject) {
   validateBoolean(samplePerIteration, 'options.samplePerIteration');
   validateInteger(resolution, 'options.resolution', 1);

+  const {
+    lowest = samplePerIteration ?
+      kDefaultIterationLowest : kDefaultIntervalLowest,
+    highest = kMaxInt64,
+    figures = kDefaultFigures,
+  } = options;
+  validateHistogramOptions(lowest, highest, figures);
+
+  // Throws if the native histogram cannot be created with these options.
+  const handle = createELDHistogram(
+    resolution, samplePerIteration, BigInt(lowest), BigInt(highest), figures);
+
   const histogram = new ELDHistogram(kSkipThrow);
   markTransferMode(histogram, true, false);
   histogram[kEnabled] = false;
-  histogram[kHandle] = createELDHistogram(resolution, samplePerIteration);
+  histogram[kHandle] = handle;
   return histogram;
 }

diff --git a/src/histogram.cc b/src/histogram.cc
index eb7fd575c98..6efe2d6985c 100644
--- a/src/histogram.cc
+++ b/src/histogram.cc
@@ -2616,11 +2616,11 @@ void IntervalHistogram::RegisterExternalReferences(
 IntervalHistogram::IntervalHistogram(Environment* env,
                                      Local<Object> wrap,
                                      AsyncWrap::ProviderType type,
-                                     int32_t interval,
+                                     uint64_t interval,
                                      OnInterval on_interval,
-                                     const Histogram::Options& options)
+                                     std::shared_ptr<Histogram> histogram)
     : HandleWrap(env, wrap, reinterpret_cast<uv_handle_t*>(&timer_), type),
-      HistogramImpl(options),
+      HistogramImpl(std::move(histogram)),
       interval_(interval),
       on_interval_(on_interval) {
   MakeWeak();
@@ -2633,9 +2633,9 @@ IntervalHistogram::IntervalHistogram(Environment* env,

 BaseObjectPtr<IntervalHistogram> IntervalHistogram::Create(
     Environment* env,
-    int32_t interval,
+    uint64_t interval,
     OnInterval on_interval,
-    const Histogram::Options& options,
+    std::shared_ptr<Histogram> histogram,
     AsyncWrap::ProviderType type) {
   Local<Object> obj;
   if (!GetConstructorTemplate(env)
@@ -2646,7 +2646,7 @@ BaseObjectPtr<IntervalHistogram> IntervalHistogram::Create(
   }

   return MakeBaseObject<IntervalHistogram>(
-      env, obj, type, interval, on_interval, options);
+      env, obj, type, interval, on_interval, std::move(histogram));
 }

 void IntervalHistogram::TimerCB(uv_timer_t* handle) {
@@ -2712,10 +2712,10 @@ void IterationHistogram::RegisterExternalReferences(
 IterationHistogram::IterationHistogram(Environment* env,
                                        Local<Object> wrap,
                                        AsyncWrap::ProviderType type,
-                                       const Histogram::Options& options)
+                                       std::shared_ptr<Histogram> histogram)
     : HandleWrap(
           env, wrap, reinterpret_cast<uv_handle_t*>(&check_handle_), type),
-      HistogramImpl(options) {
+      HistogramImpl(std::move(histogram)) {
   MakeWeak();
   wrap->SetAlignedPointerInInternalField(
       HistogramImpl::InternalFields::kImplField,
@@ -2729,7 +2729,7 @@ IterationHistogram::IterationHistogram(Environment* env,

 BaseObjectPtr<IterationHistogram> IterationHistogram::Create(
     Environment* env,
-    const Histogram::Options& options,
+    std::shared_ptr<Histogram> histogram,
     AsyncWrap::ProviderType type) {
   Local<Object> obj;
   if (!GetConstructorTemplate(env)
@@ -2739,7 +2739,8 @@ BaseObjectPtr<IterationHistogram> IterationHistogram::Create(
     return nullptr;
   }

-  return MakeBaseObject<IterationHistogram>(env, obj, type, options);
+  return MakeBaseObject<IterationHistogram>(
+      env, obj, type, std::move(histogram));
 }

 void IterationHistogram::PrepareCB(uv_prepare_t* handle) {
diff --git a/src/histogram.h b/src/histogram.h
index 93fe2b267d4..2bbbbe7d7ad 100644
--- a/src/histogram.h
+++ b/src/histogram.h
@@ -499,17 +499,17 @@ class IntervalHistogram final : public HandleWrap,

   static BaseObjectPtr<IntervalHistogram> Create(
       Environment* env,
-      int32_t interval,
+      uint64_t interval,
       OnInterval on_interval,
-      const Histogram::Options& options,
+      std::shared_ptr<Histogram> histogram,
       AsyncWrap::ProviderType type = AsyncWrap::PROVIDER_ELDHISTOGRAM);

   IntervalHistogram(Environment* env,
                     v8::Local<v8::Object> wrap,
                     AsyncWrap::ProviderType type,
-                    int32_t interval,
+                    uint64_t interval,
                     OnInterval on_interval,
-                    const Histogram::Options& options = Histogram::Options{});
+                    std::shared_ptr<Histogram> histogram);

   static void FastStart(v8::Local<v8::Value> receiver, bool reset);
   static void FastStop(v8::Local<v8::Value> receiver);
@@ -534,7 +534,7 @@ class IntervalHistogram final : public HandleWrap,
   template <typename T>
   friend void StopHandleHistogram(v8::Local<v8::Value>);

-  int32_t interval_ = 0;
+  uint64_t interval_ = 0;
   OnInterval on_interval_ = nullptr;
   uv_timer_t timer_;

@@ -559,13 +559,13 @@ class IterationHistogram final

   static BaseObjectPtr<IterationHistogram> Create(
       Environment* env,
-      const Histogram::Options& options,
+      std::shared_ptr<Histogram> histogram,
       AsyncWrap::ProviderType type = AsyncWrap::PROVIDER_ELDHISTOGRAM);

   IterationHistogram(Environment* env,
                      v8::Local<v8::Object> wrap,
                      AsyncWrap::ProviderType type,
-                     const Histogram::Options& options = Histogram::Options{});
+                     std::shared_ptr<Histogram> histogram);

   static void FastStart(v8::Local<v8::Value> receiver, bool reset);
   static void FastStop(v8::Local<v8::Value> receiver);
diff --git a/src/node_perf.cc b/src/node_perf.cc
index 381a8bdf91e..74f3c1d8f9e 100644
--- a/src/node_perf.cc
+++ b/src/node_perf.cc
@@ -4,6 +4,7 @@
 #include "histogram-inl.h"
 #include "memory_tracker-inl.h"
 #include "node_buffer.h"
+#include "node_errors.h"
 #include "node_external_reference.h"
 #include "node_internals.h"
 #include "node_process-inl.h"
@@ -14,6 +15,7 @@
 namespace node {
 namespace performance {

+using v8::BigInt;
 using v8::Context;
 using v8::DontDelete;
 using v8::Function;
@@ -28,6 +30,7 @@ using v8::Object;
 using v8::ObjectTemplate;
 using v8::PropertyAttribute;
 using v8::ReadOnly;
+using v8::Uint32;
 using v8::Value;

 // Microseconds in a millisecond, as a float.
@@ -318,14 +321,34 @@ void CreateELDHistogram(const FunctionCallbackInfo<Value>& args) {
   Environment* env = Environment::GetCurrent(args);
   int64_t interval = args[0].As<Integer>()->Value();
   CHECK_GT(interval, 0);
+  CHECK(args[2]->IsBigInt());
+  CHECK(args[3]->IsBigInt());
+  CHECK(args[4]->IsUint32());
+  bool lossless = true;
+  const int64_t lowest = args[2].As<BigInt>()->Int64Value(&lossless);
+  CHECK(lossless);
+  const int64_t highest = args[3].As<BigInt>()->Int64Value(&lossless);
+  CHECK(lossless);
+  const int figures = static_cast<int>(args[4].As<Uint32>()->Value());
+
+  // The options are validated in JS, but hdr_init() still rejects some
+  // combinations, such as a very large lowest value.
+  std::shared_ptr<Histogram> histogram =
+      Histogram::Create(Histogram::Options{lowest, highest, figures});
+  if (!histogram) {
+    return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid histogram options");
+  }
+
   if (args[1]->IsTrue()) {
-    BaseObjectPtr<IterationHistogram> histogram =
-        IterationHistogram::Create(env, Histogram::Options{1});
-    args.GetReturnValue().Set(histogram->object());
+    BaseObjectPtr<IterationHistogram> eld =
+        IterationHistogram::Create(env, std::move(histogram));
+    if (eld) args.GetReturnValue().Set(eld->object());
     return;
   }
-  BaseObjectPtr<IntervalHistogram> histogram =
-      IntervalHistogram::Create(env, interval, [](Histogram& histogram) {
+  BaseObjectPtr<IntervalHistogram> eld = IntervalHistogram::Create(
+      env,
+      interval,
+      [](Histogram& histogram) {
         uint64_t delta = histogram.RecordDelta();
         TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
                         "delay", delta);
@@ -337,8 +360,9 @@ void CreateELDHistogram(const FunctionCallbackInfo<Value>& args) {
                       "mean", histogram.Mean());
         TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
                       "stddev", histogram.Stddev());
-      }, Histogram::Options { 1000 });
-  args.GetReturnValue().Set(histogram->object());
+      },
+      std::move(histogram));
+  if (eld) args.GetReturnValue().Set(eld->object());
 }

 void MarkBootstrapComplete(const FunctionCallbackInfo<Value>& args) {
diff --git a/test/parallel/test-perf-hooks-monitor-event-loop-delay-fast-calls.js b/test/parallel/test-perf-hooks-monitor-event-loop-delay-fast-calls.js
index 1954f52e441..cdd038d57d2 100644
--- a/test/parallel/test-perf-hooks-monitor-event-loop-delay-fast-calls.js
+++ b/test/parallel/test-perf-hooks-monitor-event-loop-delay-fast-calls.js
@@ -7,7 +7,7 @@ const assert = require('assert');
 const { internalBinding } = require('internal/test/binding');
 const { createELDHistogram } = internalBinding('performance');

-const histogram = createELDHistogram(1, true);
+const histogram = createELDHistogram(1, true, 1n, 2n ** 63n - 1n, 3);

 function testFastMethods() {
   histogram.start(true);
diff --git a/test/parallel/test-perf-hooks-monitor-event-loop-delay-options.js b/test/parallel/test-perf-hooks-monitor-event-loop-delay-options.js
new file mode 100644
index 00000000000..265061659b8
--- /dev/null
+++ b/test/parallel/test-perf-hooks-monitor-event-loop-delay-options.js
@@ -0,0 +1,213 @@
+'use strict';
+
+// Tests the lowest, highest, and figures options of monitorEventLoopDelay().
+
+const common = require('../common');
+const assert = require('assert');
+const { monitorEventLoopDelay } = require('perf_hooks');
+
+const kMaxInt64 = 2n ** 63n - 1n;
+
+function readHead(data, offset) {
+  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
+  const info = data[offset] & 0x1f;
+  const major = data[offset] >> 5;
+  switch (info) {
+    case 24: return { major, argument: BigInt(view.getUint8(offset + 1)), next: offset + 2 };
+    case 25: return { major, argument: BigInt(view.getUint16(offset + 1)), next: offset + 3 };
+    case 26: return { major, argument: BigInt(view.getUint32(offset + 1)), next: offset + 5 };
+    case 27: return { major, argument: view.getBigUint64(offset + 1), next: offset + 9 };
+    default:
+      assert.ok(info < 24);
+      return { major, argument: BigInt(info), next: offset + 1 };
+  }
+}
+
+// Returns the histogram configuration from the documented export() format:
+// a CBOR map in which key 1 is the lowest discernible value, key 2 the highest
+// trackable value, key 3 the number of significant figures, and key 9 the
+// length of the counts array.
+function getLayout(histogram) {
+  const data = histogram.export();
+  const map = readHead(data, 0);
+  assert.strictEqual(map.major, 5);
+  const fields = new Map();
+  let offset = map.next;
+  for (let n = 0n; n < map.argument; n++) {
+    const key = readHead(data, offset);
+    const value = readHead(data, key.next);
+    offset = value.next;
+    if (value.major === 4) {
+      // The counts array contains unsigned integers.
+      for (let i = 0n; i < value.argument; i++)
+        offset = readHead(data, offset).next;
+    }
+    fields.set(key.argument, value.argument);
+  }
+  return {
+    lowest: fields.get(1n),
+    highest: fields.get(2n),
+    figures: fields.get(3n),
+    countsLength: fields.get(9n),
+  };
+}
+
+{
+  // The defaults are unchanged.
+  assert.deepStrictEqual(getLayout(monitorEventLoopDelay()), {
+    lowest: 1000n,
+    highest: kMaxInt64,
+    figures: 3n,
+    countsLength: 46080n,
+  });
+  assert.deepStrictEqual(
+    getLayout(monitorEventLoopDelay({ samplePerIteration: true })), {
+      lowest: 1n,
+      highest: kMaxInt64,
+      figures: 3n,
+      countsLength: 55296n,
+    });
+}
+
+for (const samplePerIteration of [false, true]) {
+  assert.deepStrictEqual(getLayout(monitorEventLoopDelay({
+    samplePerIteration,
+    lowest: 1000,
+    highest: 3_600_000_000_000,
+    figures: 2,
+  })), {
+    lowest: 1000n,
+    highest: 3_600_000_000_000n,
+    figures: 2n,
+    countsLength: 3456n,
+  });
+
+  assert.deepStrictEqual(getLayout(monitorEventLoopDelay({
+    samplePerIteration,
+    lowest: 1_000_000n,
+    highest: 60_000_000_000n,
+    figures: 1,
+  })), {
+    lowest: 1_000_000n,
+    highest: 60_000_000_000n,
+    figures: 1n,
+    countsLength: 224n,
+  });
+
+  assert.deepStrictEqual(getLayout(monitorEventLoopDelay({
+    samplePerIteration,
+    lowest: 1_000_000,
+  })), {
+    lowest: 1_000_000n,
+    highest: kMaxInt64,
+    figures: 3n,
+    countsLength: 35840n,
+  });
+
+  for (const name of ['lowest', 'highest', 'figures']) {
+    for (const value of ['a', null, false, {}, []]) {
+      assert.throws(() => monitorEventLoopDelay({
+        samplePerIteration,
+        [name]: value,
+      }), { code: 'ERR_INVALID_ARG_TYPE' });
+    }
+  }
+  assert.throws(() => monitorEventLoopDelay({ samplePerIteration, figures: 3n }),
+                { code: 'ERR_INVALID_ARG_TYPE' });
+
+  for (const options of [
+    { lowest: 0 },
+    { lowest: 1.5 },
+    { lowest: 2 ** 53 },
+    { lowest: 0n },
+    { lowest: 2n ** 63n },
+    { highest: 0 },
+    { highest: 1.5 },
+    { highest: 2 ** 53 },
+    { highest: 2n ** 63n },
+    { lowest: 10, highest: 19 },
+    { lowest: 10n, highest: 19n },
+    { figures: 0 },
+    { figures: 6 },
+    { figures: 1.5 },
+  ]) {
+    assert.throws(() => monitorEventLoopDelay({
+      samplePerIteration,
+      ...options,
+    }), { code: 'ERR_OUT_OF_RANGE' });
+  }
+
+  // These options pass validation, but the histogram cannot be created.
+  assert.throws(() => monitorEventLoopDelay({
+    samplePerIteration,
+    lowest: 2 ** 45,
+    highest: 2 ** 46,
+    figures: 5,
+  }), { code: 'ERR_INVALID_ARG_VALUE' });
+}
+
+{
+  // The default lowest depends on the sampling mode, and highest is validated
+  // against it.
+  assert.throws(() => monitorEventLoopDelay({ highest: 1999 }),
+                { code: 'ERR_OUT_OF_RANGE' });
+  monitorEventLoopDelay({ highest: 2000 });
+  monitorEventLoopDelay({ samplePerIteration: true, highest: 1999 });
+  assert.throws(() => monitorEventLoopDelay({
+    samplePerIteration: true,
+    highest: 1,
+  }), { code: 'ERR_OUT_OF_RANGE' });
+}
+
+{
+  // A right-sized histogram records one sample per event loop iteration.
+  const iterations = 10;
+  const histogram = monitorEventLoopDelay({
+    samplePerIteration: true,
+    lowest: 1000,
+    highest: 3_600_000_000_000,
+    figures: 2,
+  });
+  histogram.enable();
+
+  const done = common.mustCall(() => {
+    histogram.disable();
+    assert.ok(histogram.count >= iterations - 1,
+              `Expected at least ${iterations - 1} samples, got ${histogram.count}`);
+    assert.strictEqual(histogram.exceeds, 0);
+  });
+
+  let remaining = iterations;
+  function tick() {
+    if (--remaining > 0) {
+      setImmediate(tick);
+    } else {
+      done();
+    }
+  }
+  setImmediate(tick);
+}
+
+{
+  // Check that delays greater than highest are counted by exceeds.
+  // Delayed timer callbacks can run close together, so some samples may
+  // still be recorded despite resolution being much larger than highest.
+  const histogram = monitorEventLoopDelay({
+    resolution: 20,
+    highest: 2_000_000,
+  });
+  histogram.enable();
+
+  const done = common.mustCall(() => {
+    histogram.disable();
+    assert.ok(histogram.exceeds >= 2);
+  });
+
+  (function wait() {
+    if (histogram.exceeds >= 2) {
+      done();
+    } else {
+      setTimeout(wait, 5);
+    }
+  })();
+}
diff --git a/test/parallel/test-perf-hooks-monitor-event-loop-delay-resolution.js b/test/parallel/test-perf-hooks-monitor-event-loop-delay-resolution.js
new file mode 100644
index 00000000000..f0402524e7c
--- /dev/null
+++ b/test/parallel/test-perf-hooks-monitor-event-loop-delay-resolution.js
@@ -0,0 +1,31 @@
+'use strict';
+
+// Tests that monitorEventLoopDelay() does not truncate a resolution greater
+// than 2 ** 31 - 1 milliseconds to 32 bits.
+
+const common = require('../common');
+const assert = require('assert');
+const { monitorEventLoopDelay } = require('perf_hooks');
+
+// Truncated to 32 bits, this resolution would be 1 ms.
+const histogram = monitorEventLoopDelay({ resolution: 2 ** 32 + 1 });
+const control = monitorEventLoopDelay({ resolution: 1 });
+histogram.enable();
+control.enable();
+
+const done = common.mustCall(() => {
+  histogram.disable();
+  control.disable();
+  // The first sample is recorded after two timer callbacks, which for this
+  // resolution is roughly 99 days after enable().
+  assert.strictEqual(histogram.count, 0);
+  assert.strictEqual(histogram.exceeds, 0);
+});
+
+(function wait() {
+  if (control.count >= 10) {
+    done();
+  } else {
+    setTimeout(wait, 2);
+  }
+})();
diff --git a/typings/internalBinding/performance.d.ts b/typings/internalBinding/performance.d.ts
index 98a176ea1ac..abf07578a4a 100644
--- a/typings/internalBinding/performance.d.ts
+++ b/typings/internalBinding/performance.d.ts
@@ -145,6 +145,9 @@ export interface PerformanceBinding {
   createELDHistogram(
     interval: number,
     samplePerIteration: boolean,
+    lowest: bigint,
+    highest: bigint,
+    figures: number,
   ): InternalPerformanceBinding.ELDHistogram;
   markBootstrapComplete(): void;
   uvMetricsInfo(): void;