mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
perf_hooks: add performanceNodeTiming.uvMetricsInfoBigInt
`performance.nodeTiming.uvMetricsInfo` returns the libuv event loop metrics as numbers, which are only exact up to `Number.MAX_SAFE_INTEGER`. Add `uvMetricsInfoBigInt`, which returns the same metrics as bigints backed by `uint64_t` storage, carrying the full 64-bit range reported by libuv. A single native call fills both a `Float64Array` and a `BigUint64Array`, so `uvMetricsInfo` does not pay for bigint allocation and conversion. The new property is omitted from `toJSON()`, as `JSON.stringify()` cannot serialize bigints. 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>
This commit is contained in:
1 parent
01a7ef6b14
commit
97af3d7da5
12 files changed
+197
-19
No files matched your search
@@ -11,6 +11,7 @@ const {
|
||||
const bench = common.createBenchmark(main, {
|
||||
n: [1e6],
|
||||
events: [1, 1000, 10000],
|
||||
api: ['number', 'bigint'],
|
||||
});
|
||||
|
||||
async function runEvents(events) {
|
||||
@@ -19,11 +20,19 @@ async function runEvents(events) {
|
||||
}
|
||||
}
|
||||
|
||||
async function main({ n, events }) {
|
||||
async function main({ n, events, api }) {
|
||||
await runEvents(events);
|
||||
bench.start();
|
||||
for (let i = 0; i < n; i++) {
|
||||
assert.ok(performance.nodeTiming.uvMetricsInfo);
|
||||
if (api === 'bigint') {
|
||||
bench.start();
|
||||
for (let i = 0; i < n; i++) {
|
||||
assert.ok(performance.nodeTiming.uvMetricsInfoBigInt);
|
||||
}
|
||||
bench.end(n);
|
||||
} else {
|
||||
bench.start();
|
||||
for (let i = 0; i < n; i++) {
|
||||
assert.ok(performance.nodeTiming.uvMetricsInfo);
|
||||
}
|
||||
bench.end(n);
|
||||
}
|
||||
bench.end(n);
|
||||
}
|
||||
@@ -862,6 +862,10 @@ added:
|
||||
This is a wrapper to the `uv_metrics_info` function.
|
||||
It returns the current set of event loop metrics.
|
||||
|
||||
The values are exact up to `Number.MAX_SAFE_INTEGER`. Use
|
||||
[`performanceNodeTiming.uvMetricsInfoBigInt`][] to obtain the full 64-bit
|
||||
values reported by libuv.
|
||||
|
||||
It is recommended to use this property inside a function whose execution was
|
||||
scheduled using `setImmediate` to avoid collecting metrics before finishing all
|
||||
operations scheduled during the current loop iteration.
|
||||
@@ -882,6 +886,41 @@ setImmediate(() => {
|
||||
});
|
||||
```
|
||||
|
||||
### `performanceNodeTiming.uvMetricsInfoBigInt`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {Object}
|
||||
* `loopCount` {bigint} Number of event loop iterations.
|
||||
* `events` {bigint} Number of events that have been processed by the event handler.
|
||||
* `eventsWaiting` {bigint} Number of events that were waiting to be processed when the event provider was called.
|
||||
|
||||
The same as [`performanceNodeTiming.uvMetricsInfo`][], except that the values
|
||||
are {bigint}s carrying the full 64-bit range reported by libuv.
|
||||
|
||||
Because `JSON.stringify()` cannot serialize {bigint} values, this property is
|
||||
not enumerable and is not included in the output of
|
||||
`performanceNodeTiming.toJSON()`. Copies of `performance.nodeTiming` made by
|
||||
spreading its enumerable properties, for example, remain serializable.
|
||||
|
||||
```cjs
|
||||
const { performance } = require('node:perf_hooks');
|
||||
|
||||
setImmediate(() => {
|
||||
console.log(performance.nodeTiming.uvMetricsInfoBigInt);
|
||||
});
|
||||
```
|
||||
|
||||
```mjs
|
||||
import { performance } from 'node:perf_hooks';
|
||||
|
||||
setImmediate(() => {
|
||||
console.log(performance.nodeTiming.uvMetricsInfoBigInt);
|
||||
});
|
||||
```
|
||||
|
||||
### `performanceNodeTiming.v8Start`
|
||||
|
||||
<!-- YAML
|
||||
@@ -3263,6 +3302,8 @@ dns.promises.resolve('localhost');
|
||||
[`perf_hooks.importHistogram()`]: #perf_hooksimporthistogramdata
|
||||
[`perf_hooks.monitorEventLoopDelay()`]: #perf_hooksmonitoreventloopdelayoptions
|
||||
[`perf_hooks.timerify()`]: #perf_hookstimerifyfn-options
|
||||
[`performanceNodeTiming.uvMetricsInfoBigInt`]: #performancenodetiminguvmetricsinfobigint
|
||||
[`performanceNodeTiming.uvMetricsInfo`]: #performancenodetiminguvmetricsinfo
|
||||
[`process.hrtime()`]: process.md#processhrtimetime
|
||||
[`timeOrigin`]: https://w3c.github.io/hr-time/#dom-performance-timeorigin
|
||||
[`window.performance.toJSON`]: https://developer.mozilla.org/en-US/docs/Web/API/Performance/toJSON
|
||||
|
||||
@@ -30,6 +30,7 @@ const {
|
||||
loopIdleTime,
|
||||
uvMetricsInfo,
|
||||
uvMetricsBuffer,
|
||||
uvMetricsBigIntBuffer,
|
||||
} = internalBinding('performance');
|
||||
|
||||
class PerformanceNodeTiming {
|
||||
@@ -138,6 +139,23 @@ class PerformanceNodeTiming {
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
// Not enumerable, so that copying the enumerable properties, e.g. with
|
||||
// `{ ...performance.nodeTiming }`, does not produce an object that
|
||||
// JSON.stringify() cannot serialize.
|
||||
uvMetricsInfoBigInt: {
|
||||
__proto__: null,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
get: () => {
|
||||
uvMetricsInfo();
|
||||
return {
|
||||
loopCount: uvMetricsBigIntBuffer[0],
|
||||
events: uvMetricsBigIntBuffer[1],
|
||||
eventsWaiting: uvMetricsBigIntBuffer[2],
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,6 +171,8 @@ class PerformanceNodeTiming {
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
// uvMetricsInfoBigInt is intentionally omitted: JSON.stringify() cannot
|
||||
// serialize bigint values.
|
||||
return {
|
||||
name: 'node',
|
||||
entryType: 'node',
|
||||
|
||||
@@ -191,7 +191,8 @@ class AliasedBufferBase final : public MemoryRetainer {
|
||||
V(uint32_t, Uint32Array) \
|
||||
V(float, Float32Array) \
|
||||
V(double, Float64Array) \
|
||||
V(int64_t, BigInt64Array)
|
||||
V(int64_t, BigInt64Array) \
|
||||
V(uint64_t, BigUint64Array)
|
||||
|
||||
#define V(NativeT, V8T) \
|
||||
typedef AliasedBufferBase<NativeT, v8::V8T> Aliased##V8T;
|
||||
|
||||
+28
-8
@@ -61,7 +61,12 @@ PerformanceState::PerformanceState(Isolate* isolate,
|
||||
offsetof(performance_state_internal, uv_metrics),
|
||||
3,
|
||||
root,
|
||||
MAYBE_FIELD_PTR(info, uv_metrics)) {
|
||||
MAYBE_FIELD_PTR(info, uv_metrics)),
|
||||
uv_metrics_bigint(isolate,
|
||||
offsetof(performance_state_internal, uv_metrics_bigint),
|
||||
3,
|
||||
root,
|
||||
MAYBE_FIELD_PTR(info, uv_metrics_bigint)) {
|
||||
if (info == nullptr) {
|
||||
// For performance states initialized from scratch, reset
|
||||
// all the milestones and initialize the time origin.
|
||||
@@ -89,11 +94,15 @@ PerformanceState::SerializeInfo PerformanceState::Serialize(
|
||||
for (size_t i = 0; i < uv_metrics.Length(); ++i) {
|
||||
uv_metrics[i] = 0;
|
||||
}
|
||||
for (size_t i = 0; i < uv_metrics_bigint.Length(); ++i) {
|
||||
uv_metrics_bigint[i] = 0;
|
||||
}
|
||||
|
||||
SerializeInfo info{root.Serialize(context, creator),
|
||||
milestones.Serialize(context, creator),
|
||||
observers.Serialize(context, creator),
|
||||
uv_metrics.Serialize(context, creator)};
|
||||
uv_metrics.Serialize(context, creator),
|
||||
uv_metrics_bigint.Serialize(context, creator)};
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -116,6 +125,7 @@ void PerformanceState::Deserialize(v8::Local<v8::Context> context,
|
||||
milestones.Deserialize(context);
|
||||
observers.Deserialize(context);
|
||||
uv_metrics.Deserialize(context);
|
||||
uv_metrics_bigint.Deserialize(context);
|
||||
|
||||
// Re-initialize the time origin and timestamp i.e. the process start time.
|
||||
Initialize(time_origin, time_origin_timestamp);
|
||||
@@ -128,6 +138,7 @@ std::ostream& operator<<(std::ostream& o,
|
||||
<< " " << i.milestones << ", // milestones\n"
|
||||
<< " " << i.observers << ", // observers\n"
|
||||
<< " " << i.uv_metrics << ", // uv_metrics\n"
|
||||
<< " " << i.uv_metrics_bigint << ", // uv_metrics_bigint\n"
|
||||
<< "}";
|
||||
return o;
|
||||
}
|
||||
@@ -280,12 +291,16 @@ 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);
|
||||
// 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);
|
||||
// libuv reports 64-bit counters. The doubles backing uvMetricsInfo are
|
||||
// exact up to Number.MAX_SAFE_INTEGER, while the uint64_t values backing
|
||||
// uvMetricsInfoBigInt carry the full range.
|
||||
PerformanceState* state = env->performance_state();
|
||||
const uint64_t values[] = {
|
||||
metrics.loop_count, metrics.events, metrics.events_waiting};
|
||||
for (size_t i = 0; i < arraysize(values); ++i) {
|
||||
state->uv_metrics[i] = static_cast<double>(values[i]);
|
||||
state->uv_metrics_bigint[i] = values[i];
|
||||
}
|
||||
}
|
||||
|
||||
void CreateELDHistogram(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -382,6 +397,11 @@ void CreatePerContextProperties(Local<Object> target,
|
||||
FIXED_ONE_BYTE_STRING(isolate, "uvMetricsBuffer"),
|
||||
state->uv_metrics.GetJSArray())
|
||||
.Check();
|
||||
target
|
||||
->Set(context,
|
||||
FIXED_ONE_BYTE_STRING(isolate, "uvMetricsBigIntBuffer"),
|
||||
state->uv_metrics_bigint.GetJSArray())
|
||||
.Check();
|
||||
|
||||
Local<Object> constants = Object::New(isolate);
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ class PerformanceState {
|
||||
AliasedBufferIndex milestones;
|
||||
AliasedBufferIndex observers;
|
||||
AliasedBufferIndex uv_metrics;
|
||||
AliasedBufferIndex uv_metrics_bigint;
|
||||
};
|
||||
|
||||
explicit PerformanceState(v8::Isolate* isolate,
|
||||
@@ -80,6 +81,7 @@ class PerformanceState {
|
||||
AliasedFloat64Array milestones;
|
||||
AliasedUint32Array observers;
|
||||
AliasedFloat64Array uv_metrics;
|
||||
AliasedBigUint64Array uv_metrics_bigint;
|
||||
|
||||
uint64_t performance_last_gc_start_mark = 0;
|
||||
uint16_t current_gc_type = 0;
|
||||
@@ -91,9 +93,10 @@ class PerformanceState {
|
||||
void Initialize(uint64_t time_origin, double time_origin_timestamp);
|
||||
void ResetMilestones();
|
||||
struct performance_state_internal {
|
||||
// doubles first so that they are always sizeof(double)-aligned
|
||||
// 64-bit fields first so that they are always 8-byte aligned
|
||||
double milestones[NODE_PERFORMANCE_MILESTONE_INVALID];
|
||||
double uv_metrics[3];
|
||||
uint64_t uv_metrics_bigint[3];
|
||||
uint32_t observers[NODE_PERFORMANCE_ENTRY_TYPE_INVALID];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -408,6 +408,7 @@ size_t SnapshotSerializer::Write(const ImmediateInfo::SerializeInfo& data) {
|
||||
// [ 4/8 bytes ] snapshot index of milestones
|
||||
// [ 4/8 bytes ] snapshot index of observers
|
||||
// [ 4/8 bytes ] snapshot index of uv_metrics
|
||||
// [ 4/8 bytes ] snapshot index of uv_metrics_bigint
|
||||
template <>
|
||||
performance::PerformanceState::SerializeInfo SnapshotDeserializer::Read() {
|
||||
Debug("Read<PerformanceState::SerializeInfo>()\n");
|
||||
@@ -417,6 +418,7 @@ performance::PerformanceState::SerializeInfo SnapshotDeserializer::Read() {
|
||||
result.milestones = ReadArithmetic<AliasedBufferIndex>();
|
||||
result.observers = ReadArithmetic<AliasedBufferIndex>();
|
||||
result.uv_metrics = ReadArithmetic<AliasedBufferIndex>();
|
||||
result.uv_metrics_bigint = ReadArithmetic<AliasedBufferIndex>();
|
||||
if (is_debug) {
|
||||
std::string str = ToStr(result);
|
||||
Debug("Read<PerformanceState::SerializeInfo>() %s\n", str);
|
||||
@@ -436,6 +438,7 @@ size_t SnapshotSerializer::Write(
|
||||
written_total += WriteArithmetic<AliasedBufferIndex>(data.milestones);
|
||||
written_total += WriteArithmetic<AliasedBufferIndex>(data.observers);
|
||||
written_total += WriteArithmetic<AliasedBufferIndex>(data.uv_metrics);
|
||||
written_total += WriteArithmetic<AliasedBufferIndex>(data.uv_metrics_bigint);
|
||||
|
||||
Debug("Write<PerformanceState::SerializeInfo>() wrote %d bytes\n",
|
||||
written_total);
|
||||
|
||||
+27
-1
@@ -13,6 +13,8 @@ function safeMetricsInfo(cb) {
|
||||
});
|
||||
}
|
||||
|
||||
const kZeroBigInt = { loopCount: 0n, events: 0n, eventsWaiting: 0n };
|
||||
|
||||
{
|
||||
const info = nodeTiming.uvMetricsInfo;
|
||||
assert.strictEqual(info.loopCount, 0);
|
||||
@@ -21,6 +23,7 @@ function safeMetricsInfo(cb) {
|
||||
// Adding checks for this property will make the test flaky
|
||||
// as it can be highly influenced by race conditions.
|
||||
assert.strictEqual(info.eventsWaiting, 0);
|
||||
assert.deepStrictEqual(nodeTiming.uvMetricsInfoBigInt, kZeroBigInt);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -31,24 +34,47 @@ function safeMetricsInfo(cb) {
|
||||
assert.strictEqual(info.loopCount, 0);
|
||||
assert.strictEqual(info.events, 0);
|
||||
assert.strictEqual(info.eventsWaiting, 0);
|
||||
assert.deepStrictEqual(nodeTiming.uvMetricsInfoBigInt, kZeroBigInt);
|
||||
}
|
||||
|
||||
{
|
||||
function openFile(info) {
|
||||
assert.strictEqual(info.loopCount, 1);
|
||||
const infoBigInt = nodeTiming.uvMetricsInfoBigInt;
|
||||
assert.strictEqual(infoBigInt.loopCount, 1n);
|
||||
|
||||
fs.open(__filename, 'r', (err) => {
|
||||
assert.ifError(err);
|
||||
});
|
||||
|
||||
const saved = { ...info };
|
||||
const savedBigInt = { ...infoBigInt };
|
||||
safeMetricsInfo((nextInfo) => {
|
||||
assert.notStrictEqual(nextInfo, info);
|
||||
assert.ok(nextInfo.loopCount > saved.loopCount);
|
||||
// Updating the shared buffer must not change earlier results.
|
||||
const nextInfoBigInt = nodeTiming.uvMetricsInfoBigInt;
|
||||
assert.notStrictEqual(nextInfoBigInt, infoBigInt);
|
||||
assert.ok(nextInfoBigInt.loopCount > savedBigInt.loopCount);
|
||||
// Updating the shared buffers must not change earlier results.
|
||||
assert.deepStrictEqual(info, saved);
|
||||
assert.deepStrictEqual(infoBigInt, savedBigInt);
|
||||
});
|
||||
}
|
||||
|
||||
safeMetricsInfo(openFile);
|
||||
}
|
||||
|
||||
{
|
||||
// Both representations are filled by the same native call, and libuv only
|
||||
// updates the metrics while the event loop is running, so back-to-back
|
||||
// synchronous reads must agree.
|
||||
safeMetricsInfo(() => {
|
||||
const info = nodeTiming.uvMetricsInfo;
|
||||
const infoBigInt = nodeTiming.uvMetricsInfoBigInt;
|
||||
for (const key of ['loopCount', 'events', 'eventsWaiting']) {
|
||||
assert.strictEqual(typeof info[key], 'number');
|
||||
assert.strictEqual(typeof infoBigInt[key], 'bigint');
|
||||
assert.strictEqual(BigInt(info[key]), infoBigInt[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -5,14 +5,22 @@ 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
|
||||
// The event loop metrics reported by libuv are 64-bit counters. The buffers
|
||||
// used to transfer them to JavaScript must not truncate them to 32 bits.
|
||||
const { uvMetricsBuffer, uvMetricsInfo } = internalBinding('performance');
|
||||
const {
|
||||
uvMetricsBuffer,
|
||||
uvMetricsBigIntBuffer,
|
||||
uvMetricsInfo,
|
||||
} = internalBinding('performance');
|
||||
assert.ok(uvMetricsBuffer instanceof Float64Array);
|
||||
assert.strictEqual(uvMetricsBuffer.length, 3);
|
||||
assert.ok(uvMetricsBigIntBuffer instanceof BigUint64Array);
|
||||
assert.strictEqual(uvMetricsBigIntBuffer.length, 3);
|
||||
|
||||
uvMetricsInfo();
|
||||
for (const value of uvMetricsBuffer) {
|
||||
for (let i = 0; i < uvMetricsBuffer.length; i++) {
|
||||
const value = uvMetricsBuffer[i];
|
||||
assert.ok(Number.isSafeInteger(value), `${value} is not a safe integer`);
|
||||
assert.ok(value >= 0, `${value} is negative`);
|
||||
assert.strictEqual(BigInt(value), uvMetricsBigIntBuffer[i]);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('node:assert');
|
||||
const { Worker } = require('node:worker_threads');
|
||||
|
||||
// The event loop metrics are tracked per event loop, so they are also
|
||||
// available in worker threads.
|
||||
const worker = new Worker(`
|
||||
const { parentPort } = require('node:worker_threads');
|
||||
const { performance } = require('node:perf_hooks');
|
||||
setImmediate(() => {
|
||||
const info = performance.nodeTiming.uvMetricsInfo;
|
||||
const infoBigInt = performance.nodeTiming.uvMetricsInfoBigInt;
|
||||
parentPort.postMessage({ info, infoBigInt });
|
||||
});
|
||||
`, { eval: true });
|
||||
|
||||
worker.on('message', common.mustCall(({ info, infoBigInt }) => {
|
||||
for (const key of ['loopCount', 'events', 'eventsWaiting']) {
|
||||
assert.strictEqual(typeof info[key], 'number');
|
||||
assert.strictEqual(typeof infoBigInt[key], 'bigint');
|
||||
assert.strictEqual(BigInt(info[key]), infoBigInt[key]);
|
||||
}
|
||||
assert.ok(infoBigInt.loopCount > 0n);
|
||||
}));
|
||||
@@ -10,6 +10,26 @@ assert.strictEqual(nodeTiming.name, 'node');
|
||||
assert.strictEqual(nodeTiming.entryType, 'node');
|
||||
|
||||
assert.strictEqual(nodeTiming.startTime, 0);
|
||||
|
||||
// uvMetricsInfoBigInt holds bigint values, which JSON.stringify() cannot
|
||||
// serialize, so it must not be part of the JSON representation, nor be copied
|
||||
// along with the enumerable properties.
|
||||
assert.strictEqual(typeof nodeTiming.uvMetricsInfoBigInt.loopCount, 'bigint');
|
||||
assert.strictEqual(
|
||||
Object.getOwnPropertyDescriptor(nodeTiming, 'uvMetricsInfoBigInt').enumerable,
|
||||
false);
|
||||
assert.strictEqual(
|
||||
Object.hasOwn(JSON.parse(JSON.stringify(nodeTiming)), 'uvMetricsInfoBigInt'),
|
||||
false);
|
||||
assert.strictEqual(
|
||||
Object.hasOwn(JSON.parse(JSON.stringify(performance)).nodeTiming,
|
||||
'uvMetricsInfoBigInt'),
|
||||
false);
|
||||
{
|
||||
const copy = { ...nodeTiming };
|
||||
assert.strictEqual(Object.hasOwn(copy, 'uvMetricsInfoBigInt'), false);
|
||||
assert.strictEqual(typeof JSON.stringify(copy), 'string');
|
||||
}
|
||||
const now = performance.now();
|
||||
assert.ok(nodeTiming.duration >= now);
|
||||
|
||||
|
||||
+1
@@ -147,5 +147,6 @@ export interface PerformanceBinding {
|
||||
markBootstrapComplete(): void;
|
||||
uvMetricsInfo(): void;
|
||||
uvMetricsBuffer: Float64Array;
|
||||
uvMetricsBigIntBuffer: BigUint64Array;
|
||||
now(): number;
|
||||
}
|
||||
Reference in new issue
Block a user