perf_hooks: add histogram option to timerify

Allows setting a `Histogram` object option on timerify to
record function execution times over time.

Signed-off-by: James M Snell <jasnell@gmail.com>

PR-URL: https://github.com/nodejs/node/pull/37475
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
James M Snell committed 2021-03-05 08:16:03 -08:00
1 parent 9c1274a123
commit b803bca4fa
4 files changed
+82 -5

No files matched your search

+8 -1
View File
@@ -210,10 +210,13 @@ added: v8.5.0
The [`timeOrigin`][] specifies the high resolution millisecond timestamp at The [`timeOrigin`][] specifies the high resolution millisecond timestamp at
which the current `node` process began, measured in Unix time. which the current `node` process began, measured in Unix time.
### `performance.timerify(fn)` ### `performance.timerify(fn[, options])`
<!-- YAML <!-- YAML
added: v8.5.0 added: v8.5.0
changes: changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37475
description: Added the histogram option.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37136 pr-url: https://github.com/nodejs/node/pull/37136
description: Re-implemented to use pure-JavaScript and the ability description: Re-implemented to use pure-JavaScript and the ability
@@ -221,6 +224,10 @@ changes:
--> -->
* `fn` {Function} * `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._ _This property is an extension by Node.js. It is not available in Web browsers._
+5
View File
@@ -42,6 +42,10 @@ const {
JSTransferable, JSTransferable,
} = require('internal/worker/js_transferable'); } = require('internal/worker/js_transferable');
function isHistogram(object) {
return object?.[kHandle] !== undefined;
}
class Histogram extends JSTransferable { class Histogram extends JSTransferable {
constructor(internal) { constructor(internal) {
super(); super();
@@ -193,6 +197,7 @@ module.exports = {
RecordableHistogram, RecordableHistogram,
InternalHistogram, InternalHistogram,
InternalRecordableHistogram, InternalRecordableHistogram,
isHistogram,
kDestroy, kDestroy,
kHandle, kHandle,
createHistogram, createHistogram,
+29 -4
View File
@@ -3,6 +3,7 @@
const { const {
FunctionPrototypeBind, FunctionPrototypeBind,
ObjectDefineProperties, ObjectDefineProperties,
MathCeil,
ReflectApply, ReflectApply,
ReflectConstruct, ReflectConstruct,
Symbol, Symbol,
@@ -13,6 +14,14 @@ const {
now, now,
} = require('internal/perf/perf'); } = require('internal/perf/perf');
const {
validateObject
} = require('internal/validators');
const {
isHistogram
} = require('internal/histogram');
const { const {
isConstructor, isConstructor,
} = internalBinding('util'); } = internalBinding('util');
@@ -29,8 +38,10 @@ const {
const kTimerified = Symbol('kTimerified'); const kTimerified = Symbol('kTimerified');
function processComplete(name, start, args) { function processComplete(name, start, args, histogram) {
const duration = now() - start; const duration = now() - start;
if (histogram !== undefined)
histogram.record(MathCeil(duration * 1e6));
const entry = const entry =
new InternalPerformanceEntry( new InternalPerformanceEntry(
name, name,
@@ -45,10 +56,23 @@ function processComplete(name, start, args) {
enqueue(entry); enqueue(entry);
} }
function timerify(fn) { function timerify(fn, options = {}) {
if (typeof fn !== 'function') if (typeof fn !== 'function')
throw new ERR_INVALID_ARG_TYPE('fn', 'function', fn); 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]; if (fn[kTimerified]) return fn[kTimerified];
const constructor = isConstructor(fn); const constructor = isConstructor(fn);
@@ -65,9 +89,10 @@ function timerify(fn) {
result, result,
fn.name, fn.name,
start, start,
args)); args,
histogram));
} }
processComplete(fn.name, start, args); processComplete(fn.name, start, args, histogram);
return result; return result;
} }
@@ -4,10 +4,15 @@ const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const { const {
createHistogram,
performance, performance,
PerformanceObserver PerformanceObserver
} = require('perf_hooks'); } = require('perf_hooks');
const {
setTimeout: sleep
} = require('timers/promises');
{ {
// Intentional non-op. Do not wrap in common.mustCall(); // Intentional non-op. Do not wrap in common.mustCall();
const n = performance.timerify(function noop() {}); const n = performance.timerify(function noop() {});
@@ -81,3 +86,38 @@ const {
assert.strictEqual(n.length, m.length); assert.strictEqual(n.length, m.length);
assert.strictEqual(n.name, 'timerified m'); 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());