perf_hooks: move non-standard performance properties to perf_hooks

`performance.eventLoopUtilization` and `performance.timerify` are not
part of the Web API. Expose these two functions directly on the
`perf_hooks` module.

PR-URL: https://github.com/nodejs/node/pull/60370
Fixes: https://github.com/nodejs/node/issues/60368
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
Chengzhong Wu authored and GitHub committed 2025-10-28 00:23:50 +01:00
1 parent 4fe325d93d
commit 51a57f2b36
6 files changed
+186 -131

No files matched your search

+155 -110
View File
@@ -120,6 +120,10 @@ the Resource Timeline. If `name` is provided, removes only the named resource.
added:
- v14.10.0
- v12.19.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/60370
description: Added `perf_hooks.eventLoopUtilization` alias.
-->
* `utilization1` {Object} The result of a previous call to
@@ -131,62 +135,9 @@ added:
* `active` {number}
* `utilization` {number}
The `eventLoopUtilization()` method returns an object that contains the
cumulative duration of time the event loop has been both idle and active as a
high resolution milliseconds timer. The `utilization` value is the calculated
Event Loop Utilization (ELU).
This is an alias of [`perf_hooks.eventLoopUtilization()`][].
If bootstrapping has not yet finished on the main thread the properties have
the value of `0`. The ELU is immediately available on [Worker threads][] since
bootstrap happens within the event loop.
Both `utilization1` and `utilization2` are optional parameters.
If `utilization1` is passed, then the delta between the current call's `active`
and `idle` times, as well as the corresponding `utilization` value are
calculated and returned (similar to [`process.hrtime()`][]).
If `utilization1` and `utilization2` are both passed, then the delta is
calculated between the two arguments. This is a convenience option because,
unlike [`process.hrtime()`][], calculating the ELU is more complex than a
single subtraction.
ELU is similar to CPU utilization, except that it only measures event loop
statistics and not CPU usage. It represents the percentage of time the event
loop has spent outside the event loop's event provider (e.g. `epoll_wait`).
No other CPU idle time is taken into consideration. The following is an example
of how a mostly idle process will have a high ELU.
```mjs
import { eventLoopUtilization } from 'node:perf_hooks';
import { spawnSync } from 'node:child_process';
setImmediate(() => {
const elu = eventLoopUtilization();
spawnSync('sleep', ['5']);
console.log(eventLoopUtilization(elu).utilization);
});
```
```cjs
'use strict';
const { eventLoopUtilization } = require('node:perf_hooks').performance;
const { spawnSync } = require('node:child_process');
setImmediate(() => {
const elu = eventLoopUtilization();
spawnSync('sleep', ['5']);
console.log(eventLoopUtilization(elu).utilization);
});
```
Although the CPU is mostly idle while running this script, the value of
`utilization` is `1`. This is because the call to
[`child_process.spawnSync()`][] blocks the event loop from proceeding.
Passing in a user-defined object instead of the result of a previous call to
`eventLoopUtilization()` will lead to undefined behavior. The return values
are not guaranteed to reflect any correct state of the event loop.
_This property is an extension by Node.js. It is not available in Web browsers._
### `performance.getEntries()`
@@ -424,6 +375,9 @@ which the current `node` process began, measured in Unix time.
<!-- YAML
added: v8.5.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/60370
description: Added `perf_hooks.timerify` alias.
- version: v16.0.0
pr-url: https://github.com/nodejs/node/pull/37475
description: Added the histogram option.
@@ -439,63 +393,10 @@ changes:
`perf_hooks.createHistogram()` that will record runtime durations in
nanoseconds.
This is an alias of [`perf_hooks.timerify()`][].
_This property is an extension by Node.js. It is not available in Web browsers._
Wraps a function within a new function that measures the running time of the
wrapped function. A `PerformanceObserver` must be subscribed to the `'function'`
event type in order for the timing details to be accessed.
```mjs
import { performance, PerformanceObserver } from 'node:perf_hooks';
function someFunction() {
console.log('hello world');
}
const wrapped = performance.timerify(someFunction);
const obs = new PerformanceObserver((list) => {
console.log(list.getEntries()[0].duration);
performance.clearMarks();
performance.clearMeasures();
obs.disconnect();
});
obs.observe({ entryTypes: ['function'] });
// A performance timeline entry will be created
wrapped();
```
```cjs
const {
performance,
PerformanceObserver,
} = require('node:perf_hooks');
function someFunction() {
console.log('hello world');
}
const wrapped = performance.timerify(someFunction);
const obs = new PerformanceObserver((list) => {
console.log(list.getEntries()[0].duration);
performance.clearMarks();
performance.clearMeasures();
obs.disconnect();
});
obs.observe({ entryTypes: ['function'] });
// A performance timeline entry will be created
wrapped();
```
If the wrapped function returns a promise, a finally handler will be attached
to the promise and the duration will be reported once the finally handler is
invoked.
### `performance.toJSON()`
<!-- YAML
@@ -1722,6 +1623,78 @@ added:
Returns a {RecordableHistogram}.
## `perf_hooks.eventLoopUtilization([utilization1[, utilization2]])`
<!-- YAML
added: REPLACEME
-->
* `utilization1` {Object} The result of a previous call to
`eventLoopUtilization()`.
* `utilization2` {Object} The result of a previous call to
`eventLoopUtilization()` prior to `utilization1`.
* Returns: {Object}
* `idle` {number}
* `active` {number}
* `utilization` {number}
The `eventLoopUtilization()` function returns an object that contains the
cumulative duration of time the event loop has been both idle and active as a
high resolution milliseconds timer. The `utilization` value is the calculated
Event Loop Utilization (ELU).
If bootstrapping has not yet finished on the main thread the properties have
the value of `0`. The ELU is immediately available on [Worker threads][] since
bootstrap happens within the event loop.
Both `utilization1` and `utilization2` are optional parameters.
If `utilization1` is passed, then the delta between the current call's `active`
and `idle` times, as well as the corresponding `utilization` value are
calculated and returned (similar to [`process.hrtime()`][]).
If `utilization1` and `utilization2` are both passed, then the delta is
calculated between the two arguments. This is a convenience option because,
unlike [`process.hrtime()`][], calculating the ELU is more complex than a
single subtraction.
ELU is similar to CPU utilization, except that it only measures event loop
statistics and not CPU usage. It represents the percentage of time the event
loop has spent outside the event loop's event provider (e.g. `epoll_wait`).
No other CPU idle time is taken into consideration. The following is an example
of how a mostly idle process will have a high ELU.
```mjs
import { eventLoopUtilization } from 'node:perf_hooks';
import { spawnSync } from 'node:child_process';
setImmediate(() => {
const elu = eventLoopUtilization();
spawnSync('sleep', ['5']);
console.log(eventLoopUtilization(elu).utilization);
});
```
```cjs
'use strict';
const { eventLoopUtilization } = require('node:perf_hooks');
const { spawnSync } = require('node:child_process');
setImmediate(() => {
const elu = eventLoopUtilization();
spawnSync('sleep', ['5']);
console.log(eventLoopUtilization(elu).utilization);
});
```
Although the CPU is mostly idle while running this script, the value of
`utilization` is `1`. This is because the call to
[`child_process.spawnSync()`][] blocks the event loop from proceeding.
Passing in a user-defined object instead of the result of a previous call to
`eventLoopUtilization()` will lead to undefined behavior. The return values
are not guaranteed to reflect any correct state of the event loop.
## `perf_hooks.monitorEventLoopDelay([options])`
<!-- YAML
@@ -1775,6 +1748,76 @@ console.log(h.percentile(50));
console.log(h.percentile(99));
```
## `perf_hooks.timerify(fn[, options])`
<!-- YAML
added: REPLACEME
-->
* `fn` {Function}
* `options` {Object}
* `histogram` {RecordableHistogram} A histogram object created using
`perf_hooks.createHistogram()` that will record runtime durations in
nanoseconds.
_This property is an extension by Node.js. It is not available in Web browsers._
Wraps a function within a new function that measures the running time of the
wrapped function. A `PerformanceObserver` must be subscribed to the `'function'`
event type in order for the timing details to be accessed.
```mjs
import { timerify, performance, PerformanceObserver } from 'node:perf_hooks';
function someFunction() {
console.log('hello world');
}
const wrapped = timerify(someFunction);
const obs = new PerformanceObserver((list) => {
console.log(list.getEntries()[0].duration);
performance.clearMarks();
performance.clearMeasures();
obs.disconnect();
});
obs.observe({ entryTypes: ['function'] });
// A performance timeline entry will be created
wrapped();
```
```cjs
const {
timerify,
performance,
PerformanceObserver,
} = require('node:perf_hooks');
function someFunction() {
console.log('hello world');
}
const wrapped = timerify(someFunction);
const obs = new PerformanceObserver((list) => {
console.log(list.getEntries()[0].duration);
performance.clearMarks();
performance.clearMeasures();
obs.disconnect();
});
obs.observe({ entryTypes: ['function'] });
// A performance timeline entry will be created
wrapped();
```
If the wrapped function returns a promise, a finally handler will be attached
to the promise and the duration will be reported once the finally handler is
invoked.
## Class: `Histogram`
<!-- YAML
@@ -2306,6 +2349,8 @@ dns.promises.resolve('localhost');
[Worker threads]: worker_threads.md#worker-threads
[`'exit'`]: process.md#event-exit
[`child_process.spawnSync()`]: child_process.md#child_processspawnsynccommand-args-options
[`perf_hooks.eventLoopUtilization()`]: #perf_hookseventlooputilizationutilization1-utilization2
[`perf_hooks.timerify()`]: #perf_hookstimerifyfn-options
[`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
+2 -3
View File
@@ -1838,7 +1838,7 @@ added:
-->
An object that can be used to query performance information from a worker
instance. Similar to [`perf_hooks.performance`][].
instance.
#### `performance.eventLoopUtilization([utilization1[, utilization2]])`
@@ -2240,8 +2240,7 @@ thread spawned will spawn another until the application crashes.
[`fs.open()`]: fs.md#fsopenpath-flags-mode-callback
[`markAsUntransferable()`]: #worker_threadsmarkasuntransferableobject
[`node:cluster` module]: cluster.md
[`perf_hooks.performance`]: perf_hooks.md#perf_hooksperformance
[`perf_hooks` `eventLoopUtilization()`]: perf_hooks.md#performanceeventlooputilizationutilization1-utilization2
[`perf_hooks` `eventLoopUtilization()`]: perf_hooks.md#perf_hookseventlooputilizationutilization1-utilization2
[`port.on('message')`]: #event-message
[`port.onmessage()`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/onmessage
[`port.postMessage()`]: #portpostmessagevalue-transferlist
+4
View File
@@ -28,6 +28,8 @@ const {
} = require('internal/histogram');
const monitorEventLoopDelay = require('internal/perf/event_loop_delay');
const { eventLoopUtilization } = require('internal/perf/event_loop_utilization');
const timerify = require('internal/perf/timerify');
module.exports = {
Performance,
@@ -38,6 +40,8 @@ module.exports = {
PerformanceObserverEntryList,
PerformanceResourceTiming,
monitorEventLoopDelay,
eventLoopUtilization,
timerify,
createHistogram,
performance,
};
@@ -6,10 +6,13 @@ const TIMEOUT = 10;
const SPIN_DUR = 50;
const assert = require('assert');
const { performance } = require('perf_hooks');
const { performance, eventLoopUtilization } = require('perf_hooks');
const { Worker, parentPort } = require('worker_threads');
const { nodeTiming, eventLoopUtilization } = performance;
// Verifies that `performance.eventLoopUtilization` is an alias of
// `perf_hooks.eventLoopUtilization`.
assert.strictEqual(performance.eventLoopUtilization, eventLoopUtilization);
const { nodeTiming } = performance;
const elu = eventLoopUtilization();
// Take into account whether this test was started as a Worker.
@@ -5,6 +5,7 @@ const assert = require('assert');
const {
createHistogram,
timerify,
performance,
PerformanceObserver
} = require('perf_hooks');
@@ -13,9 +14,12 @@ const {
setTimeout: sleep
} = require('timers/promises');
// Verifies that `performance.timerify` is an alias of `perf_hooks.timerify`.
assert.strictEqual(performance.timerify, timerify);
{
// Intentional non-op. Do not wrap in common.mustCall();
const n = performance.timerify(function noop() {});
const n = timerify(function noop() {});
const obs = new PerformanceObserver(common.mustCall((list) => {
const entries = list.getEntries();
@@ -36,7 +40,7 @@ const {
// performance timeline entry will not be reported.
const obs = new PerformanceObserver(common.mustNotCall());
obs.observe({ entryTypes: ['function'] });
const n = performance.timerify(() => {
const n = timerify(() => {
throw new Error('test');
});
assert.throws(() => n(), /^Error: test$/);
@@ -45,7 +49,7 @@ const {
{
class N {}
const n = performance.timerify(N);
const n = timerify(N);
const obs = new PerformanceObserver(common.mustCall((list) => {
const entries = list.getEntries();
@@ -66,7 +70,7 @@ const {
{
[1, {}, [], null, undefined, Infinity].forEach((input) => {
assert.throws(() => performance.timerify(input),
assert.throws(() => timerify(input),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
@@ -78,9 +82,9 @@ const {
// Function can be wrapped many times, also check length and name
{
const m = (a, b = 1) => {};
const n = performance.timerify(m);
const o = performance.timerify(m);
const p = performance.timerify(n);
const n = timerify(m);
const o = timerify(m);
const p = timerify(n);
assert.notStrictEqual(n, o);
assert.notStrictEqual(n, p);
assert.notStrictEqual(o, p);
@@ -97,7 +101,7 @@ const {
for (let i = 0; i < 1e3; i++)
_deadCode = i;
};
const n = performance.timerify(m, { histogram });
const n = timerify(m, { histogram });
assert.strictEqual(histogram.max, 0);
for (let i = 0; i < 10; i++) {
n();
@@ -106,7 +110,7 @@ const {
assert.ok(_deadCode >= 0);
assert.notStrictEqual(histogram.max, 0);
[1, '', {}, [], false].forEach((histogram) => {
assert.throws(() => performance.timerify(m, { histogram }), {
assert.throws(() => timerify(m, { histogram }), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
@@ -117,14 +121,14 @@ const {
const m = async (a, b = 1) => {
await sleep(10);
};
const n = performance.timerify(m, { histogram });
const n = timerify(m, { histogram });
assert.strictEqual(histogram.max, 0);
for (let i = 0; i < 10; i++) {
await n();
}
assert.notStrictEqual(histogram.max, 0);
[1, '', {}, [], false].forEach((histogram) => {
assert.throws(() => performance.timerify(m, { histogram }), {
assert.throws(() => timerify(m, { histogram }), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
@@ -132,17 +136,17 @@ const {
// Regression tests for https://github.com/nodejs/node/issues/40623
{
assert.strictEqual(performance.timerify(function func() {
assert.strictEqual(timerify(function func() {
return 1;
})(), 1);
assert.strictEqual(performance.timerify(function() {
assert.strictEqual(timerify(function() {
return 1;
})(), 1);
assert.strictEqual(performance.timerify(() => {
assert.strictEqual(timerify(() => {
return 1;
})(), 1);
class C {}
const wrap = performance.timerify(C);
const wrap = timerify(C);
assert.ok(new wrap() instanceof C);
assert.throws(() => wrap(), {
name: 'TypeError',
+1 -1
View File
@@ -10,7 +10,7 @@ const {
parentPort,
} = require('worker_threads');
const { performance } = require('perf_hooks');
const { eventLoopUtilization } = require('perf_hooks').performance;
const { eventLoopUtilization } = require('perf_hooks');
// Use argv to detect whether we're running as a Worker called by this test vs.
// this test also being called as a Worker.