Commit adc26bc82f5 for nodejs

commit adc26bc82f5835dca7b89326cd3cfa85f29eb358
Author: James M Snell <jasnell@gmail.com>
Date:   Thu Sep 17 20:31:58 2026 +0000

    perf_hooks: fix truncation of uvMetricsInfo counters

    libuv reports the event loop metrics exposed through
    `performance.nodeTiming.uvMetricsInfo` as `uint64_t` counters, but
    they were copied into an `Int32Array` (and, before that, converted
    using `v8::Integer::New()`), so they wrapped around after 2^31 on
    long-running processes.

    Store the metrics in a `Float64Array` instead. Values are now exact
    up to `Number.MAX_SAFE_INTEGER`.

    Assisted-by: OpenCode
    Signed-off-by: James M Snell <jasnell@gmail.com>
    PR-URL: https://github.com/nodejs/node/pull/66094
    Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
    Reviewed-By: Chengzhong Wu <legendecas@gmail.com>

diff --git a/src/node_perf.cc b/src/node_perf.cc
index b4c74e9a09a..d3348cd22fc 100644
--- a/src/node_perf.cc
+++ b/src/node_perf.cc
@@ -280,10 +280,12 @@ void UvMetricsInfo(const FunctionCallbackInfo<Value>& args) {
   uv_metrics_t metrics;
   // uv_metrics_info always return 0
   CHECK_EQ(uv_metrics_info(env->event_loop(), &metrics), 0);
-  AliasedInt32Array& buffer = env->performance_state()->uv_metrics;
-  buffer[0] = static_cast<int32_t>(metrics.loop_count);
-  buffer[1] = static_cast<int32_t>(metrics.events);
-  buffer[2] = static_cast<int32_t>(metrics.events_waiting);
+  // libuv reports 64-bit counters. Store them as doubles so that they are
+  // exact up to Number.MAX_SAFE_INTEGER instead of wrapping at 2^31.
+  AliasedFloat64Array& buffer = env->performance_state()->uv_metrics;
+  buffer[0] = static_cast<double>(metrics.loop_count);
+  buffer[1] = static_cast<double>(metrics.events);
+  buffer[2] = static_cast<double>(metrics.events_waiting);
 }

 void CreateELDHistogram(const FunctionCallbackInfo<Value>& args) {
diff --git a/src/node_perf_common.h b/src/node_perf_common.h
index aa84ba55b08..3aa228a501d 100644
--- a/src/node_perf_common.h
+++ b/src/node_perf_common.h
@@ -79,7 +79,7 @@ class PerformanceState {
   AliasedUint8Array root;
   AliasedFloat64Array milestones;
   AliasedUint32Array observers;
-  AliasedInt32Array uv_metrics;
+  AliasedFloat64Array uv_metrics;

   uint64_t performance_last_gc_start_mark = 0;
   uint16_t current_gc_type = 0;
@@ -93,8 +93,8 @@ class PerformanceState {
   struct performance_state_internal {
     // doubles first so that they are always sizeof(double)-aligned
     double milestones[NODE_PERFORMANCE_MILESTONE_INVALID];
+    double uv_metrics[3];
     uint32_t observers[NODE_PERFORMANCE_ENTRY_TYPE_INVALID];
-    int32_t uv_metrics[3];
   };
 };

diff --git a/test/parallel/test-performance-nodetiming-uvmetricsinfo-buffer.js b/test/parallel/test-performance-nodetiming-uvmetricsinfo-buffer.js
new file mode 100644
index 00000000000..4882f946211
--- /dev/null
+++ b/test/parallel/test-performance-nodetiming-uvmetricsinfo-buffer.js
@@ -0,0 +1,18 @@
+// Flags: --expose-internals
+'use strict';
+
+require('../common');
+const assert = require('node:assert');
+const { internalBinding } = require('internal/test/binding');
+
+// The event loop metrics reported by libuv are 64-bit counters. The buffer
+// used to transfer them to JavaScript must not truncate them to 32 bits.
+const { uvMetricsBuffer, uvMetricsInfo } = internalBinding('performance');
+assert.ok(uvMetricsBuffer instanceof Float64Array);
+assert.strictEqual(uvMetricsBuffer.length, 3);
+
+uvMetricsInfo();
+for (const value of uvMetricsBuffer) {
+  assert.ok(Number.isSafeInteger(value), `${value} is not a safe integer`);
+  assert.ok(value >= 0, `${value} is negative`);
+}
diff --git a/test/parallel/test-performance-nodetiming-uvmetricsinfo.js b/test/parallel/test-performance-nodetiming-uvmetricsinfo.js
index b67682b0ff3..c7b1f7481b7 100644
--- a/test/parallel/test-performance-nodetiming-uvmetricsinfo.js
+++ b/test/parallel/test-performance-nodetiming-uvmetricsinfo.js
@@ -13,10 +13,13 @@ const fixtures = require('../common/fixtures');

 const file = fixtures.path('test-nodetiming-uvmetricsinfo.js');

-{
+// Run both with and without the built-in startup snapshot, as the
+// performance state buffers are initialized differently in each case.
+for (const execArgv of [[], ['--no-node-snapshot']]) {
   const { status, stderr } = spawnSync(
     process.execPath,
     [
+      ...execArgv,
       file,
     ],
   );
diff --git a/typings/internalBinding/performance.d.ts b/typings/internalBinding/performance.d.ts
index cf3ef0a664f..26c5335321d 100644
--- a/typings/internalBinding/performance.d.ts
+++ b/typings/internalBinding/performance.d.ts
@@ -146,6 +146,6 @@ export interface PerformanceBinding {
   ): InternalPerformanceBinding.ELDHistogram;
   markBootstrapComplete(): void;
   uvMetricsInfo(): void;
-  uvMetricsBuffer: Int32Array;
+  uvMetricsBuffer: Float64Array;
   now(): number;
 }