Commit e3499df9b41 for nodejs

commit e3499df9b4115f52d96cf510e3b4249825e00041
Author: James M Snell <jasnell@gmail.com>
Date:   Sun Oct 11 17:07:02 2026 +0000

    lib: default bench operations to 1

    b.end(N) ... N defaults to 1
    b.report(...) operations defaults to 1

    Signed-off-by: James M Snell <jasnell@gmail.com>
    Assisted-by: Opencode
    PR-URL: https://github.com/nodejs/node/pull/66664
    Reviewed-By: Matteo Collina <matteo.collina@gmail.com>

diff --git a/doc/api/bench.md b/doc/api/bench.md
index 5f23df07f01..b457cff50c4 100644
--- a/doc/api/bench.md
+++ b/doc/api/bench.md
@@ -71,7 +71,7 @@ to their caller.

 Each warmup and measured sample invokes the benchmark function once with a
 fresh {BenchContext}. The function must either call `context.start()` and
-`context.end(operations)` exactly once, or call `context.record(sample)` exactly
+`context.end([operations])` exactly once, or call `context.record(sample)` exactly
 once to provide an externally measured sample. Setup before `start()` and
 cleanup after `end()` are outside the measured region. Promise-returning
 functions are awaited.
@@ -665,14 +665,14 @@ added: v26.9.0
 Starts the measured region using `process.hrtime.bigint()`. Calling `start()`
 more than once is an error.

-### `context.end(operations[, options])`
+### `context.end([operations[, options]])`

 <!-- YAML
 added: v26.9.0
 -->

 * `operations` {number} The number of completed operations. Must be a positive
-  safe integer.
+  safe integer. **Default:** `1`.
 * `options` {Object}
   * `detail` {any} Additional structured-cloneable sample data. With CLI process
     isolation, it must also be supported by advanced child process
@@ -694,7 +694,7 @@ added: v26.9.0

 * `sample` {Object}
   * `operations` {number} The number of completed operations. Must be a positive
-    safe integer.
+    safe integer. **Default:** `1`.
   * `duration_ns` {bigint} An externally measured positive duration in
     nanoseconds no greater than `Number.MAX_SAFE_INTEGER`.
   * `detail` {any} Additional structured-cloneable sample data. With CLI process
diff --git a/lib/internal/bench_runner/benchmark.js b/lib/internal/bench_runner/benchmark.js
index cea0abc71e5..ea47696489a 100644
--- a/lib/internal/bench_runner/benchmark.js
+++ b/lib/internal/bench_runner/benchmark.js
@@ -349,7 +349,7 @@ class BenchContext {
     this.#startTime = hrtime();
   }

-  end(operations, options = kEmptyObject) {
+  end(operations = 1, options = kEmptyObject) {
     const endTime = hrtime();
     if (this.#closed) {
       this.#invalid = true;
@@ -415,7 +415,7 @@ class BenchContext {

     try {
       validateObject(sample, 'sample');
-      const { detail, duration_ns, operations } = sample;
+      const { detail, duration_ns, operations = 1 } = sample;
       validateInteger(
         operations, 'sample.operations', 1, NumberMAX_SAFE_INTEGER);
       if (typeof duration_ns !== 'bigint') {
diff --git a/test/parallel/test-bench-end-operations.js b/test/parallel/test-bench-end-operations.js
new file mode 100644
index 00000000000..83a51512e1b
--- /dev/null
+++ b/test/parallel/test-bench-end-operations.js
@@ -0,0 +1,91 @@
+// Flags: --experimental-bench --no-warnings
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const { createRunner } = require('node:bench');
+
+function waitForTick() {
+  const started = process.hrtime.bigint();
+  while (process.hrtime.bigint() === started);
+}
+
+const runner = createRunner({ yieldBetweenSamples: false });
+
+runner.bench('default operations', { samples: 1, warmup: 0 },
+             common.mustCall((b) => {
+               b.start();
+               waitForTick();
+               const sample = b.end();
+               assert.strictEqual(sample.operations, 1);
+             }));
+
+runner.bench('undefined operations with options', { samples: 1, warmup: 0 },
+             common.mustCall((b) => {
+               b.start();
+               waitForTick();
+               const sample = b.end(undefined, { detail: 'value' });
+               assert.strictEqual(sample.operations, 1);
+               assert.strictEqual(sample.detail, 'value');
+             }));
+
+const invalid = [0, -1, 0.5, NaN, Infinity, null, '1'];
+for (const operations of invalid) {
+  runner.bench(`invalid ${String(operations)}`, { samples: 1, warmup: 0 },
+               (b) => {
+                 b.start();
+                 waitForTick();
+                 b.end(operations);
+               });
+}
+
+runner.bench('record default operations', { samples: 1, warmup: 0 },
+             common.mustCall((b) => {
+               const sample = b.record({ duration_ns: 10n });
+               assert.strictEqual(sample.operations, 1);
+               assert.strictEqual(sample.rate, 100_000_000);
+             }));
+
+runner.bench('record undefined operations', { samples: 1, warmup: 0 },
+             common.mustCall((b) => {
+               const sample = b.record({ duration_ns: 10n, operations: undefined });
+               assert.strictEqual(sample.operations, 1);
+             }));
+
+for (const operations of invalid) {
+  runner.bench(`record invalid ${String(operations)}`,
+               { samples: 1, warmup: 0 },
+               (b) => b.record({ duration_ns: 10n, operations }));
+}
+
+(async () => {
+  const records = await runner.run().toArray();
+  const byName = new Map(records
+    .filter(({ type }) => type === 'bench:complete')
+    .map(({ data }) => [data.name, data]));
+
+  assert.deepStrictEqual(
+    byName.get('default operations').samples.map(({ operations }) => operations),
+    [1]);
+  assert.deepStrictEqual(
+    byName.get('undefined operations with options').samples
+      .map(({ operations }) => operations),
+    [1]);
+
+  for (const name of ['record default operations',
+                      'record undefined operations']) {
+    assert.deepStrictEqual(
+      byName.get(name).samples.map(({ operations }) => operations), [1]);
+  }
+
+  for (const operations of invalid) {
+    const code = typeof operations === 'number' ?
+      'ERR_OUT_OF_RANGE' : 'ERR_INVALID_ARG_TYPE';
+    for (const prefix of ['invalid', 'record invalid']) {
+      const { error, samples } =
+        byName.get(`${prefix} ${String(operations)}`);
+      assert.strictEqual(error.code, code);
+      assert.strictEqual(samples.length, 0);
+    }
+  }
+})().then(common.mustCall());