Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions benchmark/perf_hooks/nodetiming-uvmetricsinfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
const bench = common.createBenchmark(main, {
n: [1e6],
events: [1, 1000, 10000],
api: ['number', 'bigint'],
});

async function runEvents(events) {
Expand All @@ -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);
}
43 changes: 42 additions & 1 deletion doc/api/perf_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -854,14 +854,18 @@ added:
- v20.18.0
-->

* Returns: {Object}
* Type: {Object}
* `loopCount` {number} Number of event loop iterations.
* `events` {number} Number of events that have been processed by the event handler.
* `eventsWaiting` {number} Number of events that were waiting to be processed when the event provider was called.

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.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions lib/internal/perf/nodetiming.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const {
loopIdleTime,
uvMetricsInfo,
uvMetricsBuffer,
uvMetricsBigIntBuffer,
} = internalBinding('performance');

class PerformanceNodeTiming {
Expand Down Expand Up @@ -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],
};
},
},
});
}

Expand All @@ -153,6 +171,8 @@ class PerformanceNodeTiming {
}

toJSON() {
// uvMetricsInfoBigInt is intentionally omitted: JSON.stringify() cannot
// serialize bigint values.
return {
name: 'node',
entryType: 'node',
Expand Down
3 changes: 2 additions & 1 deletion src/aliased_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
34 changes: 28 additions & 6 deletions src/node_perf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

Expand All @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -280,10 +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);
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. 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) {
Expand Down Expand Up @@ -380,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);

Expand Down
9 changes: 6 additions & 3 deletions src/node_perf_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class PerformanceState {
AliasedBufferIndex milestones;
AliasedBufferIndex observers;
AliasedBufferIndex uv_metrics;
AliasedBufferIndex uv_metrics_bigint;
};

explicit PerformanceState(v8::Isolate* isolate,
Expand All @@ -79,7 +80,8 @@ class PerformanceState {
AliasedUint8Array root;
AliasedFloat64Array milestones;
AliasedUint32Array observers;
AliasedInt32Array uv_metrics;
AliasedFloat64Array uv_metrics;
AliasedBigUint64Array uv_metrics_bigint;

uint64_t performance_last_gc_start_mark = 0;
uint16_t current_gc_type = 0;
Expand All @@ -91,10 +93,11 @@ 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];
int32_t uv_metrics[3];
};
};

Expand Down
3 changes: 3 additions & 0 deletions src/node_snapshotable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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);
Expand All @@ -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);
Expand Down
28 changes: 27 additions & 1 deletion test/fixtures/test-nodetiming-uvmetricsinfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ function safeMetricsInfo(cb) {
});
}

const kZeroBigInt = { loopCount: 0n, events: 0n, eventsWaiting: 0n };

{
const info = nodeTiming.uvMetricsInfo;
assert.strictEqual(info.loopCount, 0);
Expand All @@ -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);
}

{
Expand All @@ -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]);
}
});
}
26 changes: 26 additions & 0 deletions test/parallel/test-performance-nodetiming-uvmetricsinfo-buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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 buffers
// used to transfer them to JavaScript must not truncate them to 32 bits.
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 (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]);
}
Loading
Loading