Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf_hooks: add histogram option to timerify #37475

Closed
wants to merge 2 commits into from
Closed
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
9 changes: 8 additions & 1 deletion doc/api/perf_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,17 +210,24 @@ added: v8.5.0
The [`timeOrigin`][] specifies the high resolution millisecond timestamp at
which the current `node` process began, measured in Unix time.

### `performance.timerify(fn)`
### `performance.timerify(fn[, options])`
<!-- YAML
added: v8.5.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37475
description: Added the histogram option.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37136
description: Re-implemented to use pure-JavaScript and the ability
to time async functions.
-->

* `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._

Expand Down
5 changes: 5 additions & 0 deletions lib/internal/histogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const {
JSTransferable,
} = require('internal/worker/js_transferable');

function isHistogram(object) {
return object?.[kHandle] !== undefined;
}

class Histogram extends JSTransferable {
constructor(internal) {
super();
Expand Down Expand Up @@ -193,6 +197,7 @@ module.exports = {
RecordableHistogram,
InternalHistogram,
InternalRecordableHistogram,
isHistogram,
kDestroy,
kHandle,
createHistogram,
Expand Down
33 changes: 29 additions & 4 deletions lib/internal/perf/timerify.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const {
FunctionPrototypeBind,
ObjectDefineProperties,
MathCeil,
ReflectApply,
ReflectConstruct,
Symbol,
Expand All @@ -13,6 +14,14 @@ const {
now,
} = require('internal/perf/perf');

const {
validateObject
} = require('internal/validators');

const {
isHistogram
} = require('internal/histogram');

const {
isConstructor,
} = internalBinding('util');
Expand All @@ -29,8 +38,10 @@ const {

const kTimerified = Symbol('kTimerified');

function processComplete(name, start, args) {
function processComplete(name, start, args, histogram) {
const duration = now() - start;
if (histogram !== undefined)
histogram.record(MathCeil(duration * 1e6));
const entry =
new InternalPerformanceEntry(
name,
Expand All @@ -45,10 +56,23 @@ function processComplete(name, start, args) {
enqueue(entry);
}

function timerify(fn) {
function timerify(fn, options = {}) {
if (typeof fn !== 'function')
throw new ERR_INVALID_ARG_TYPE('fn', 'function', fn);

validateObject(options, 'options');
const {
histogram
} = options;

if (histogram !== undefined &&
(!isHistogram(histogram) || typeof histogram.record !== 'function')) {
throw new ERR_INVALID_ARG_TYPE(
'options.histogram',
'RecordableHistogram',
histogram);
}

if (fn[kTimerified]) return fn[kTimerified];

const constructor = isConstructor(fn);
Expand All @@ -65,9 +89,10 @@ function timerify(fn) {
result,
fn.name,
start,
args));
args,
histogram));
}
processComplete(fn.name, start, args);
processComplete(fn.name, start, args, histogram);
return result;
}

Expand Down
40 changes: 40 additions & 0 deletions test/parallel/test-performance-function.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ const common = require('../common');
const assert = require('assert');

const {
createHistogram,
performance,
PerformanceObserver
} = require('perf_hooks');

const {
setTimeout: sleep
} = require('timers/promises');

{
// Intentional non-op. Do not wrap in common.mustCall();
const n = performance.timerify(function noop() {});
Expand Down Expand Up @@ -81,3 +86,38 @@ const {
assert.strictEqual(n.length, m.length);
assert.strictEqual(n.name, 'timerified m');
}

(async () => {
const histogram = createHistogram();
const m = (a, b = 1) => {};
const n = performance.timerify(m, { histogram });
assert.strictEqual(histogram.max, 0);
for (let i = 0; i < 10; i++) {
n();
await sleep(10);
}
assert.notStrictEqual(histogram.max, 0);
[1, '', {}, [], false].forEach((histogram) => {
assert.throws(() => performance.timerify(m, { histogram }), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
})().then(common.mustCall());

(async () => {
const histogram = createHistogram();
const m = async (a, b = 1) => {
await sleep(10);
};
const n = performance.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 }), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
})().then(common.mustCall());