diff --git a/benchmark/perf_hooks/usertiming.js b/benchmark/perf_hooks/usertiming.js
new file mode 100644
index 00000000000000..ae797351ad78cc
--- /dev/null
+++ b/benchmark/perf_hooks/usertiming.js
@@ -0,0 +1,31 @@
+'use strict';
+
+const common = require('../common.js');
+
+const {
+ PerformanceObserver,
+ performance,
+} = require('perf_hooks');
+
+const bench = common.createBenchmark(main, {
+ n: [1e5]
+});
+
+function test() {
+ performance.mark('a');
+ setImmediate(() => {
+ performance.mark('b');
+ performance.measure('a to b', 'a', 'b');
+ });
+}
+
+function main({ n }) {
+ const obs = new PerformanceObserver(() => {
+ bench.end(n);
+ });
+ obs.observe({ entryTypes: ['measure'], buffered: true });
+
+ bench.start();
+ for (let i = 0; i < n; i++)
+ test();
+}
diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md
index 92203b8112f416..167bf753160f7a 100644
--- a/doc/api/deprecations.md
+++ b/doc/api/deprecations.md
@@ -2743,6 +2743,22 @@ Previously, `index.js` and extension searching lookups would apply to
With this deprecation, all ES module main entry point resolutions require
an explicit [`"exports"` or `"main"` entry][] with the exact file extension.
+### DEP0XXX: Extension PerformanceEntry properties
+
+
+Type: Runtime
+
+The `'gc'`, `'http2'`, and `'http'` {PerformanceEntry} object types have
+additional properties assigned to them that provide additional information.
+These properties are now available within the standard `detail` property
+of the `PerformanceEntry` object. The existing accessors have been
+deprecated and should no longer be used.
+
[Legacy URL API]: url.md#url_legacy_url_api
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
[RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 6f7b0188d8697c..a9d068d16d3213 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1891,6 +1891,16 @@ The `package.json` [`"exports"`][] field does not export the requested subpath.
Because exports are encapsulated, private internal modules that are not exported
cannot be imported through the package resolution, unless using an absolute URL.
+
+### `ERR_PERFORMANCE_INVALID_TIMESTAMP`
+
+An invalid timestamp value was provided for a performance mark or measure.
+
+
+### `ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS`
+
+Invalid options were provided for a performance measure.
+
### `ERR_PROTO_ACCESS`
diff --git a/doc/api/perf_hooks.md b/doc/api/perf_hooks.md
index bbd8a3c1a99efb..b2ce8d24c8ecb6 100644
--- a/doc/api/perf_hooks.md
+++ b/doc/api/perf_hooks.md
@@ -23,7 +23,7 @@ const obs = new PerformanceObserver((items) => {
console.log(items.getEntries()[0].duration);
performance.clearMarks();
});
-obs.observe({ entryTypes: ['measure'] });
+obs.observe({ type: 'measure' });
performance.measure('Start to Now');
performance.mark('A');
@@ -115,12 +115,20 @@ 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.
-### `performance.mark([name])`
+### `performance.mark([name[, options]])`
* `name` {string}
+* `options` {Object}
+ * `detail` {any} Additional optional detail to include with the mark.
+ * `startTime` {number} An optional timestamp to be used as the mark time.
+ **Defaults**: `performance.now()`.
Creates a new `PerformanceMark` entry in the Performance Timeline. A
`PerformanceMark` is a subclass of `PerformanceEntry` whose
@@ -128,10 +136,13 @@ Creates a new `PerformanceMark` entry in the Performance Timeline. A
`performanceEntry.duration` is always `0`. Performance marks are used
to mark specific significant moments in the Performance Timeline.
-### `performance.measure(name[, startMark[, endMark]])`
+### `performance.measure(name[, startMarkOrOptions[, endMark]])`
* `name` {string}
-* `startMark` {string} Optional.
-* `endMark` {string} Optional.
+* `startMarkOrOptions` {string|Object} Optional.
+ * `detail` {Object} Additional optional detail to include with the measure.
+ * `duration` {number} Duration between start and end times.
+ * `end` {number|string} Timestamp to be used as the end time, or a string
+ identifying a previously recorded mark.
+ * `start` {number|string} Timestamp to be used as the start time, or a string
+ identifying a previously recorded mark.
+* `endMark` {string} Optional. Must be omitted if `startMarkOrOptions` is an
+ {Object}.
Creates a new `PerformanceMeasure` entry in the Performance Timeline. A
`PerformanceMeasure` is a subclass of `PerformanceEntry` whose
@@ -152,7 +170,7 @@ Creates a new `PerformanceMeasure` entry in the Performance Timeline. A
The `startMark` argument may identify any *existing* `PerformanceMark` in the
Performance Timeline, or *may* identify any of the timestamp properties
provided by the `PerformanceNodeTiming` class. If the named `startMark` does
-not exist, then `startMark` is set to [`timeOrigin`][] by default.
+not exist, an error is thrown.
The optional `endMark` argument must identify any *existing* `PerformanceMark`
in the Performance Timeline or any of the timestamp properties provided by the
@@ -195,6 +213,11 @@ which the current `node` process began, measured in Unix time.
### `performance.timerify(fn)`
* `fn` {Function}
@@ -227,11 +250,24 @@ obs.observe({ entryTypes: ['function'] });
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: `PerformanceEntry`
+### `performanceEntry.details`
+
+
+* {any}
+
+Additional detail specific to the `entryType`.
+
### `performanceEntry.duration`
* {number}
@@ -294,6 +335,11 @@ The name of the performance entry.
### `performanceEntry.kind`
* {number}
@@ -319,6 +365,72 @@ added: v8.5.0
The high resolution millisecond timestamp marking the starting time of the
Performance Entry.
+### Garbage Collection ('gc') Details
+
+When `performanceEntry.type` is equal to `'gc'`, the `performanceEntry.details`
+property will be an {Object} with two properties:
+
+* `kind` {number} One of:
+ * `perf_hooks.constants.NODE_PERFORMANCE_GC_MAJOR`
+ * `perf_hooks.constants.NODE_PERFORMANCE_GC_MINOR`
+ * `perf_hooks.constants.NODE_PERFORMANCE_GC_INCREMENTAL`
+ * `perf_hooks.constants.NODE_PERFORMANCE_GC_WEAKCB`
+* `flags` {number} One of:
+ * `perf_hooks.constants.NODE_PERFORMANCE_GC_FLAGS_NO`
+ * `perf_hooks.constants.NODE_PERFORMANCE_GC_FLAGS_CONSTRUCT_RETAINED`
+ * `perf_hooks.constants.NODE_PERFORMANCE_GC_FLAGS_FORCED`
+ * `perf_hooks.constants.NODE_PERFORMANCE_GC_FLAGS_SYNCHRONOUS_PHANTOM_PROCESSING`
+ * `perf_hooks.constants.NODE_PERFORMANCE_GC_FLAGS_ALL_AVAILABLE_GARBAGE`
+ * `perf_hooks.constants.NODE_PERFORMANCE_GC_FLAGS_ALL_EXTERNAL_MEMORY`
+ * `perf_hooks.constants.NODE_PERFORMANCE_GC_FLAGS_SCHEDULE_IDLE`
+
+### HTTP/2 ('http2') Details
+
+When `performanceEntry.type` is equal to `'http2'`, the
+`performanceEntry.details` property will be an {Object} containing
+additional performance information.
+
+If `performanceEntry.name` is equal to `Http2Stream`, the `details`
+will contain the following properties:
+
+* `bytesRead` {number} The number of `DATA` frame bytes received for this
+ `Http2Stream`.
+* `bytesWritten` {number} The number of `DATA` frame bytes sent for this
+ `Http2Stream`.
+* `id` {number} The identifier of the associated `Http2Stream`
+* `timeToFirstByte` {number} The number of milliseconds elapsed between the
+ `PerformanceEntry` `startTime` and the reception of the first `DATA` frame.
+* `timeToFirstByteSent` {number} The number of milliseconds elapsed between
+ the `PerformanceEntry` `startTime` and sending of the first `DATA` frame.
+* `timeToFirstHeader` {number} The number of milliseconds elapsed between the
+ `PerformanceEntry` `startTime` and the reception of the first header.
+
+If `performanceEntry.name` is equal to `Http2Session`, the `details` will
+contain the following properties:
+
+* `bytesRead` {number} The number of bytes received for this `Http2Session`.
+* `bytesWritten` {number} The number of bytes sent for this `Http2Session`.
+* `framesReceived` {number} The number of HTTP/2 frames received by the
+ `Http2Session`.
+* `framesSent` {number} The number of HTTP/2 frames sent by the `Http2Session`.
+* `maxConcurrentStreams` {number} The maximum number of streams concurrently
+ open during the lifetime of the `Http2Session`.
+* `pingRTT` {number} The number of milliseconds elapsed since the transmission
+ of a `PING` frame and the reception of its acknowledgment. Only present if
+ a `PING` frame has been sent on the `Http2Session`.
+* `streamAverageDuration` {number} The average duration (in milliseconds) for
+ all `Http2Stream` instances.
+* `streamCount` {number} The number of `Http2Stream` instances processed by
+ the `Http2Session`.
+* `type` {string} Either `'server'` or `'client'` to identify the type of
+ `Http2Session`.
+
+### Timerify ('function') Details
+
+When `performanceEntry.type` is equal to `'function'`, the
+`performanceEntry.details` property will be an {Array} listing
+the input arguments to the timed function.
+
## Class: `PerformanceNodeTiming`
* `options` {Object}
+ * `type` {string} A single {PerformanceEntry} type. Must not be given
+ if `entryTypes` is already specified.
* `entryTypes` {string[]} An array of strings identifying the types of
- `PerformanceEntry` instances the observer is interested in. If not
+ {PerformanceEntry} instances the observer is interested in. If not
provided an error will be thrown.
- * `buffered` {boolean} If true, the notification callback will be
- called using `setImmediate()` and multiple `PerformanceEntry` instance
- notifications will be buffered internally. If `false`, notifications will
- be immediate and synchronous. **Default:** `false`.
-
-Subscribes the `PerformanceObserver` instance to notifications of new
-`PerformanceEntry` instances identified by `options.entryTypes`.
-When `options.buffered` is `false`, the `callback` will be invoked once for
-every `PerformanceEntry` instance:
+Subscribes the {PerformanceObserver} instance to notifications of new
+{PerformanceEntry} instances identified either by `options.entryTypes`
+or `options.type`:
```js
const {
@@ -483,22 +596,7 @@ const {
const obs = new PerformanceObserver((list, observer) => {
// Called three times synchronously. `list` contains one item.
});
-obs.observe({ entryTypes: ['mark'] });
-
-for (let n = 0; n < 3; n++)
- performance.mark(`test${n}`);
-```
-
-```js
-const {
- performance,
- PerformanceObserver
-} = require('perf_hooks');
-
-const obs = new PerformanceObserver((list, observer) => {
- // Called once. `list` contains three items.
-});
-obs.observe({ entryTypes: ['mark'], buffered: true });
+obs.observe({ type: 'mark' });
for (let n = 0; n < 3; n++)
performance.mark(`test${n}`);
@@ -549,7 +647,7 @@ const obs = new PerformanceObserver((perfObserverList, observer) => {
*/
observer.disconnect();
});
-obs.observe({ entryTypes: ['mark'], buffered: true });
+obs.observe({ type: 'mark' });
performance.mark('test');
performance.mark('meow');
@@ -603,7 +701,7 @@ const obs = new PerformanceObserver((perfObserverList, observer) => {
console.log(perfObserverList.getEntriesByName('test', 'measure')); // []
observer.disconnect();
});
-obs.observe({ entryTypes: ['mark', 'measure'], buffered: true });
+obs.observe({ entryTypes: ['mark', 'measure'] });
performance.mark('test');
performance.mark('meow');
@@ -647,7 +745,7 @@ const obs = new PerformanceObserver((perfObserverList, observer) => {
*/
observer.disconnect();
});
-obs.observe({ entryTypes: ['mark'], buffered: true });
+obs.observe({ type: 'mark' });
performance.mark('test');
performance.mark('meow');
diff --git a/lib/_http_server.js b/lib/_http_server.js
index a0b1ea7720066d..a1795c89822971 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -88,9 +88,7 @@ const {
DTRACE_HTTP_SERVER_REQUEST,
DTRACE_HTTP_SERVER_RESPONSE
} = require('internal/dtrace');
-const { observerCounts, constants } = internalBinding('performance');
const { setTimeout, clearTimeout } = require('timers');
-const { NODE_PERFORMANCE_ENTRY_TYPE_HTTP } = constants;
const dc = require('diagnostics_channel');
const onRequestStartChannel = dc.channel('http.server.request.start');
@@ -192,21 +190,16 @@ function ServerResponse(req) {
this.shouldKeepAlive = false;
}
- const httpObserverCount = observerCounts[NODE_PERFORMANCE_ENTRY_TYPE_HTTP];
- if (httpObserverCount > 0) {
- this[kServerResponseStatistics] = {
- startTime: process.hrtime()
- };
- }
+ this[kServerResponseStatistics] = {
+ startTime: process.hrtime()
+ };
}
ObjectSetPrototypeOf(ServerResponse.prototype, OutgoingMessage.prototype);
ObjectSetPrototypeOf(ServerResponse, OutgoingMessage);
ServerResponse.prototype._finish = function _finish() {
DTRACE_HTTP_SERVER_RESPONSE(this.socket);
- if (this[kServerResponseStatistics] !== undefined) {
- emitStatistics(this[kServerResponseStatistics]);
- }
+ emitStatistics(this[kServerResponseStatistics]);
FunctionPrototypeCall(OutgoingMessage.prototype._finish, this);
};
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 631f31291353eb..3facf31d179f47 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1341,6 +1341,9 @@ E('ERR_PACKAGE_PATH_NOT_EXPORTED', (pkgPath, subpath, base = undefined) => {
return `Package subpath '${subpath}' is not defined by "exports" in ${
pkgPath}package.json${base ? ` imported from ${base}` : ''}`;
}, Error);
+E('ERR_PERFORMANCE_INVALID_TIMESTAMP',
+ '%d is not a valid timestamp', TypeError);
+E('ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS', '%s', TypeError);
E('ERR_REQUIRE_ESM',
(filename, parentPath = null, packageJsonPath = null) => {
let msg = `Must use import to load ES Module: ${filename}`;
diff --git a/lib/internal/http.js b/lib/internal/http.js
index b17687d4d81624..f87dc8aa6cd01b 100644
--- a/lib/internal/http.js
+++ b/lib/internal/http.js
@@ -8,7 +8,9 @@ const {
} = primordials;
const { setUnrefTimeout } = require('internal/timers');
-const { PerformanceEntry, notify } = internalBinding('performance');
+
+const { InternalPerformanceEntry } = require('internal/perf/perf');
+const { enqueue } = require('internal/perf/observe');
let utcCache;
@@ -27,20 +29,17 @@ function resetCache() {
utcCache = undefined;
}
-class HttpRequestTiming extends PerformanceEntry {
- constructor(statistics) {
- super();
- this.name = 'HttpRequest';
- this.entryType = 'http';
- const startTime = statistics.startTime;
- const diff = process.hrtime(startTime);
- this.duration = diff[0] * 1000 + diff[1] / 1e6;
- this.startTime = startTime[0] * 1000 + startTime[1] / 1e6;
- }
-}
-
function emitStatistics(statistics) {
- notify('http', new HttpRequestTiming(statistics));
+ const startTime = statistics.startTime;
+ const diff = process.hrtime(startTime);
+ const entry = new InternalPerformanceEntry(
+ 'HttpRequest',
+ 'http',
+ startTime[0] * 1000 + startTime[1] / 1e6,
+ diff[0] * 1000 + diff[1] / 1e6,
+ undefined,
+ );
+ enqueue(entry);
}
module.exports = {
diff --git a/lib/internal/perf/event_loop_delay.js b/lib/internal/perf/event_loop_delay.js
new file mode 100644
index 00000000000000..6d27863dcf710c
--- /dev/null
+++ b/lib/internal/perf/event_loop_delay.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const {
+} = primordials;
+
+const {
+ ELDHistogram: _ELDHistogram,
+} = internalBinding('performance');
+
+const {
+ validateInteger,
+ validateObject,
+} = require('internal/validators');
+
+const {
+ Histogram,
+ kHandle,
+} = require('internal/histogram');
+
+class ELDHistogram extends Histogram {
+ constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
+ enable() { return this[kHandle].enable(); }
+ disable() { return this[kHandle].disable(); }
+}
+
+function monitorEventLoopDelay(options = {}) {
+ validateObject(options, 'options');
+
+ const { resolution = 10 } = options;
+ validateInteger(resolution, 'options.resolution', 1);
+
+ return new ELDHistogram(new _ELDHistogram(resolution));
+}
+
+module.exports = monitorEventLoopDelay;
diff --git a/lib/internal/perf/event_loop_utilization.js b/lib/internal/perf/event_loop_utilization.js
new file mode 100644
index 00000000000000..398c4ad4e42f58
--- /dev/null
+++ b/lib/internal/perf/event_loop_utilization.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const nodeTiming = require('internal/perf/nodetiming');
+
+const { now } = require('internal/perf/perf');
+
+function eventLoopUtilization(util1, util2) {
+ const ls = nodeTiming.loopStart;
+
+ if (ls <= 0) {
+ return { idle: 0, active: 0, utilization: 0 };
+ }
+
+ if (util2) {
+ const idle = util1.idle - util2.idle;
+ const active = util1.active - util2.active;
+ return { idle, active, utilization: active / (idle + active) };
+ }
+
+ const idle = nodeTiming.idleTime;
+ const active = now() - ls - idle;
+
+ if (!util1) {
+ return { idle, active, utilization: active / (idle + active) };
+ }
+
+ const idle_delta = idle - util1.idle;
+ const active_delta = active - util1.active;
+ const utilization = active_delta / (idle_delta + active_delta);
+ return { idle: idle_delta, active: active_delta, utilization };
+}
+
+module.exports = eventLoopUtilization;
diff --git a/lib/internal/perf/nodetiming.js b/lib/internal/perf/nodetiming.js
new file mode 100644
index 00000000000000..17b8ce2c0061dc
--- /dev/null
+++ b/lib/internal/perf/nodetiming.js
@@ -0,0 +1,172 @@
+'use strict';
+
+const {
+ ObjectDefineProperties,
+ ObjectSetPrototypeOf,
+ SafeArrayIterator,
+ SafeSet,
+} = primordials;
+
+const {
+ kReadOnlyAttributes,
+ PerformanceEntry,
+ now,
+} = require('internal/perf/perf');
+
+const {
+ customInspectSymbol: kInspect,
+} = require('internal/util');
+
+const { inspect } = require('util');
+
+const {
+ loopIdleTime,
+ milestones,
+ timeOrigin,
+ constants: {
+ NODE_PERFORMANCE_MILESTONE_NODE_START,
+ NODE_PERFORMANCE_MILESTONE_V8_START,
+ NODE_PERFORMANCE_MILESTONE_LOOP_START,
+ NODE_PERFORMANCE_MILESTONE_LOOP_EXIT,
+ NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE,
+ NODE_PERFORMANCE_MILESTONE_ENVIRONMENT
+ },
+} = internalBinding('performance');
+
+function getMilestoneTimestamp(milestoneIdx) {
+ const ns = milestones[milestoneIdx];
+ if (ns === -1)
+ return ns;
+ return ns / 1e6 - timeOrigin;
+}
+
+const readOnlyAttributes = new SafeSet(new SafeArrayIterator([
+ 'nodeStart',
+ 'v8Start',
+ 'environment',
+ 'loopStart',
+ 'loopExit',
+ 'bootstrapComplete',
+]));
+
+class PerformanceNodeTiming {
+ constructor() {
+ ObjectDefineProperties(this, {
+ name: {
+ enumerable: true,
+ configurable: true,
+ value: 'node'
+ },
+
+ entryType: {
+ enumerable: true,
+ configurable: true,
+ value: 'node'
+ },
+
+ startTime: {
+ enumerable: true,
+ configurable: true,
+ value: 0
+ },
+
+ duration: {
+ enumerable: true,
+ configurable: true,
+ get: now
+ },
+
+ nodeStart: {
+ enumerable: true,
+ configurable: true,
+ get() {
+ return getMilestoneTimestamp(NODE_PERFORMANCE_MILESTONE_NODE_START);
+ }
+ },
+
+ v8Start: {
+ enumerable: true,
+ configurable: true,
+ get() {
+ return getMilestoneTimestamp(NODE_PERFORMANCE_MILESTONE_V8_START);
+ }
+ },
+
+ environment: {
+ enumerable: true,
+ configurable: true,
+ get() {
+ return getMilestoneTimestamp(NODE_PERFORMANCE_MILESTONE_ENVIRONMENT);
+ }
+ },
+
+ loopStart: {
+ enumerable: true,
+ configurable: true,
+ get() {
+ return getMilestoneTimestamp(NODE_PERFORMANCE_MILESTONE_LOOP_START);
+ }
+ },
+
+ loopExit: {
+ enumerable: true,
+ configurable: true,
+ get() {
+ return getMilestoneTimestamp(NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
+ }
+ },
+
+ bootstrapComplete: {
+ enumerable: true,
+ configurable: true,
+ get() {
+ return getMilestoneTimestamp(
+ NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE);
+ }
+ },
+
+ idleTime: {
+ enumerable: true,
+ configurable: true,
+ get: loopIdleTime,
+ }
+ });
+ }
+
+ [kInspect](depth, options) {
+ if (depth < 0) return this;
+
+ const opts = {
+ ...options,
+ depth: options.depth == null ? null : options.depth - 1
+ };
+
+ return `PerformanceNodeTiming ${inspect(this.toJSON(), opts)}`;
+ }
+
+ toJSON() {
+ return {
+ name: 'node',
+ entryType: 'node',
+ startTime: this.startTime,
+ duration: this.duration,
+ nodeStart: this.nodeStart,
+ v8Start: this.v8Start,
+ bootstrapComplete: this.bootstrapComplete,
+ environment: this.environment,
+ loopStart: this.loopStart,
+ loopExit: this.loopExit,
+ idleTime: this.idleTime,
+ };
+ }
+
+ static get [kReadOnlyAttributes]() {
+ return readOnlyAttributes;
+ }
+}
+
+ObjectSetPrototypeOf(
+ PerformanceNodeTiming.prototype,
+ PerformanceEntry.prototype);
+
+module.exports = new PerformanceNodeTiming();
diff --git a/lib/internal/perf/observe.js b/lib/internal/perf/observe.js
new file mode 100644
index 00000000000000..eb9730d7a8079f
--- /dev/null
+++ b/lib/internal/perf/observe.js
@@ -0,0 +1,352 @@
+'use strict';
+
+const {
+ ArrayFrom,
+ ArrayIsArray,
+ ArrayPrototypeFilter,
+ ArrayPrototypeIncludes,
+ ArrayPrototypePush,
+ ArrayPrototypeSlice,
+ ArrayPrototypeSort,
+ ObjectDefineProperties,
+ ObjectFreeze,
+ ObjectKeys,
+ SafeMap,
+ SafeSet,
+ Symbol,
+} = primordials;
+
+const {
+ observerCounts,
+ installGarbageCollectionTracking,
+ removeGarbageCollectionTracking,
+ setupObservers,
+ constants: {
+ NODE_PERFORMANCE_ENTRY_TYPE_GC,
+ NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION,
+ NODE_PERFORMANCE_ENTRY_TYPE_HTTP2,
+ NODE_PERFORMANCE_ENTRY_TYPE_HTTP,
+ }
+} = internalBinding('performance');
+
+const {
+ isPerformanceEntry,
+ InternalPerformanceEntry,
+} = require('internal/perf/perf');
+
+const {
+ codes: {
+ ERR_INVALID_ARG_TYPE,
+ ERR_INVALID_ARG_VALUE,
+ ERR_MISSING_ARGS,
+ },
+} = require('internal/errors');
+
+const {
+ validateCallback,
+ validateObject,
+} = require('internal/validators');
+
+const {
+ customInspectSymbol: kInspect,
+ deprecate,
+} = require('internal/util');
+
+const {
+ setImmediate
+} = require('timers');
+
+const { inspect } = require('util');
+
+const kBuffer = Symbol('kBuffer');
+const kCallback = Symbol('kCallback');
+const kDispatch = Symbol('kDispatch');
+const kEntryTypes = Symbol('kEntryTypes');
+const kMaybeBuffer = Symbol('kMaybeBuffer');
+const kDeprecatedFields = Symbol('kDeprecatedFields');
+const kType = Symbol('kType');
+
+const kDeprecationMessage =
+ 'Custom PerformanceEntry accessors are deprecated. ' +
+ 'Please use the detail property.';
+
+const kTypeSingle = 0;
+const kTypeMultiple = 1;
+
+const kSupportedEntryTypes = ObjectFreeze([
+ 'function',
+ 'gc',
+ 'http',
+ 'http2',
+ 'mark',
+ 'measure',
+]);
+
+const kObservers = new SafeSet();
+const kPending = new SafeSet();
+let isPending = false;
+
+function queuePending() {
+ if (isPending) return;
+ isPending = true;
+ setImmediate(() => {
+ isPending = false;
+ for (const pending of kPending)
+ pending[kDispatch]();
+ kPending.clear();
+ });
+}
+
+function maybeDecrementObserverCounts(entryTypes) {
+ for (const type of entryTypes) {
+ let observerType;
+ switch (type) {
+ case 'gc':
+ observerType = NODE_PERFORMANCE_ENTRY_TYPE_GC;
+ break;
+ case 'function':
+ observerType = NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION;
+ break;
+ case 'http2':
+ observerType = NODE_PERFORMANCE_ENTRY_TYPE_HTTP2;
+ break;
+ case 'http':
+ observerType = NODE_PERFORMANCE_ENTRY_TYPE_HTTP;
+ break;
+ }
+ if (observerType !== undefined) {
+ observerCounts[observerType]--;
+
+ if (observerType === NODE_PERFORMANCE_ENTRY_TYPE_GC &&
+ observerCounts[observerType] === 0) {
+ removeGarbageCollectionTracking();
+ }
+ }
+ }
+}
+
+function maybeIncrementObserverCount(type) {
+ let observerType;
+ switch (type) {
+ case 'gc':
+ observerType = NODE_PERFORMANCE_ENTRY_TYPE_GC;
+ break;
+ case 'function':
+ observerType = NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION;
+ break;
+ case 'http2':
+ observerType = NODE_PERFORMANCE_ENTRY_TYPE_HTTP2;
+ break;
+ case 'http':
+ observerType = NODE_PERFORMANCE_ENTRY_TYPE_HTTP;
+ break;
+ }
+ if (observerType !== undefined) {
+ observerCounts[observerType]++;
+
+ if (observerType === NODE_PERFORMANCE_ENTRY_TYPE_GC)
+ installGarbageCollectionTracking();
+ }
+}
+
+class PerformanceObserverEntryList {
+ constructor(entries) {
+ this[kBuffer] = ArrayPrototypeSort(entries, (first, second) => {
+ if (first.startTime < second.startTime) return -1;
+ if (first.startTime > second.startTime) return 1;
+ return 0;
+ });
+ }
+
+ getEntries() {
+ return ArrayPrototypeSlice(this[kBuffer]);
+ }
+
+ getEntriesByType(type) {
+ type = `${type}`;
+ return ArrayPrototypeFilter(
+ this[kBuffer],
+ (entry) => entry.entryType === type);
+ }
+
+ getEntriesByName(name) {
+ name = `${name}`;
+ return ArrayPrototypeFilter(
+ this[kBuffer],
+ (entry) => entry.name === name);
+ }
+
+ [kInspect](depth, options) {
+ if (depth < 0) return this;
+
+ const opts = {
+ ...options,
+ depth: options.depth == null ? null : options.depth - 1
+ };
+
+ return `PerformanceObserverEntryList ${inspect(this[kBuffer], opts)}`;
+ }
+}
+
+class PerformanceObserver {
+ [kBuffer] = [];
+ [kEntryTypes] = new SafeSet();
+ [kType] = undefined;
+
+ constructor(callback) {
+ validateCallback(callback);
+ this[kCallback] = callback;
+ }
+
+ observe(options = {}) {
+ validateObject(options, 'options');
+ const {
+ entryTypes,
+ type,
+ } = { ...options };
+ if (entryTypes === undefined && type === undefined)
+ throw new ERR_MISSING_ARGS('options.entryTypes', 'options.type');
+
+ switch (this[kType]) {
+ case undefined:
+ if (entryTypes !== undefined) this[kType] = kTypeMultiple;
+ if (type !== undefined) this[kType] = kTypeSingle;
+ break;
+ case kTypeSingle:
+ if (entryTypes !== undefined)
+ throw new ERR_INVALID_ARG_VALUE('options.entryTypes', entryTypes);
+ break;
+ case kTypeMultiple:
+ if (type !== undefined)
+ throw new ERR_INVALID_ARG_VALUE('options.type', type);
+ break;
+ }
+
+ if (this[kType] === kTypeMultiple) {
+ if (!ArrayIsArray(entryTypes)) {
+ throw new ERR_INVALID_ARG_TYPE(
+ 'options.entryTypes',
+ 'string[]',
+ entryTypes);
+ }
+ maybeDecrementObserverCounts(this[kEntryTypes]);
+ this[kEntryTypes].clear();
+ for (let n = 0; n < entryTypes.length; n++) {
+ if (ArrayPrototypeIncludes(kSupportedEntryTypes, entryTypes[n])) {
+ this[kEntryTypes].add(entryTypes[n]);
+ maybeIncrementObserverCount(entryTypes[n]);
+ }
+ }
+ } else {
+ if (!ArrayPrototypeIncludes(kSupportedEntryTypes, type))
+ return;
+ this[kEntryTypes].add(type);
+ maybeIncrementObserverCount(type);
+ }
+
+ if (this[kEntryTypes].size)
+ kObservers.add(this);
+ else
+ this.disconnect();
+ }
+
+ disconnect() {
+ maybeDecrementObserverCounts(this[kEntryTypes]);
+ kObservers.delete(this);
+ kPending.delete(this);
+ this[kBuffer] = [];
+ this[kEntryTypes].clear();
+ this[kType] = undefined;
+ }
+
+ takeRecords() {
+ const list = this[kBuffer];
+ this[kBuffer] = [];
+ return new PerformanceObserverEntryList(list);
+ }
+
+ static get supportedEntryTypes() {
+ return kSupportedEntryTypes;
+ }
+
+ [kMaybeBuffer](entry) {
+ if (!this[kEntryTypes].has(entry.entryType))
+ return;
+ ArrayPrototypePush(this[kBuffer], entry);
+ kPending.add(this);
+ if (kPending.size)
+ queuePending();
+ }
+
+ [kDispatch]() { this[kCallback](this.takeRecords(), this); }
+
+ [kInspect](depth, options) {
+ if (depth < 0) return this;
+
+ const opts = {
+ ...options,
+ depth: options.depth == null ? null : options.depth - 1
+ };
+
+ return `PerformanceObserver ${inspect({
+ connected: kObservers.has(this),
+ pending: kPending.has(this),
+ entryTypes: ArrayFrom(this[kEntryTypes]),
+ buffer: this[kBuffer],
+ }, opts)}`;
+ }
+}
+
+function enqueue(entry) {
+ if (!isPerformanceEntry(entry))
+ throw new ERR_INVALID_ARG_TYPE('entry', 'PerformanceEntry', entry);
+
+ for (const obs of kObservers) {
+ obs[kMaybeBuffer](entry);
+ }
+}
+
+function observerCallback(name, type, startTime, duration, details) {
+ const entry =
+ new InternalPerformanceEntry(
+ name,
+ type,
+ startTime,
+ duration,
+ details);
+
+ if (details !== undefined) {
+ // GC, HTTP2, and HTTP PerformanceEntry used additional
+ // properties directly off the entry. Those have been
+ // moved into the details property. The existing accessors
+ // are still included but are deprecated.
+ entry[kDeprecatedFields] = new SafeMap();
+
+ const detailKeys = ObjectKeys(details);
+ const props = {};
+ for (let n = 0; n < detailKeys.length; n++) {
+ const key = detailKeys[n];
+ entry[kDeprecatedFields].set(key, details[key]);
+ props[key] = {
+ configurable: true,
+ enumerable: true,
+ get: deprecate(() => {
+ return entry[kDeprecatedFields].get(key);
+ }, kDeprecationMessage, 'DEP0XXX'),
+ set: deprecate((value) => {
+ entry[kDeprecatedFields].set(key, value);
+ }, kDeprecationMessage, 'DEP0XXX'),
+ };
+ }
+ ObjectDefineProperties(entry, props);
+ }
+
+ enqueue(entry);
+}
+
+setupObservers(observerCallback);
+
+module.exports = {
+ PerformanceObserver,
+ enqueue,
+};
diff --git a/lib/internal/perf/perf.js b/lib/internal/perf/perf.js
new file mode 100644
index 00000000000000..aca1dd582c3a66
--- /dev/null
+++ b/lib/internal/perf/perf.js
@@ -0,0 +1,94 @@
+'use strict';
+
+const {
+ ObjectSetPrototypeOf,
+ Symbol,
+ TypeError,
+} = primordials;
+
+const {
+ timeOrigin,
+} = internalBinding('performance');
+
+const {
+ customInspectSymbol: kInspect,
+} = require('internal/util');
+
+const { inspect } = require('util');
+
+const kName = Symbol('kName');
+const kType = Symbol('kType');
+const kStart = Symbol('kStart');
+const kDuration = Symbol('kDuration');
+const kDetail = Symbol('kDetail');
+const kReadOnlyAttributes = Symbol('kReadOnlyAttributes');
+
+function now() {
+ const hr = process.hrtime();
+ return (hr[0] * 1000 + hr[1] / 1e6) - timeOrigin;
+}
+
+function isPerformanceEntry(obj) {
+ return obj?.[kName] !== undefined;
+}
+
+class PerformanceEntry {
+ constructor() {
+ // eslint-disable-next-line no-restricted-syntax
+ throw new TypeError('illegal constructor');
+ }
+
+ get name() { return this[kName]; }
+
+ get entryType() { return this[kType]; }
+
+ get startTime() { return this[kStart]; }
+
+ get duration() { return this[kDuration]; }
+
+ get detail() { return this[kDetail]; }
+
+ [kInspect](depth, options) {
+ if (depth < 0) return this;
+
+ const opts = {
+ ...options,
+ depth: options.depth == null ? null : options.depth - 1
+ };
+
+ return `${this.constructor.name} ${inspect(this.toJSON(), opts)}`;
+ }
+
+ toJSON() {
+ return {
+ name: this.name,
+ entryType: this.entryType,
+ startTime: this.startTime,
+ duration: this.duration,
+ detail: this.detail,
+ };
+ }
+}
+
+class InternalPerformanceEntry {
+ constructor(name, type, start, duration, detail) {
+ this[kName] = name;
+ this[kType] = type;
+ this[kStart] = start;
+ this[kDuration] = duration;
+ this[kDetail] = detail;
+ }
+}
+
+InternalPerformanceEntry.prototype.constructor = PerformanceEntry;
+ObjectSetPrototypeOf(
+ InternalPerformanceEntry.prototype,
+ PerformanceEntry.prototype);
+
+module.exports = {
+ kReadOnlyAttributes,
+ PerformanceEntry,
+ InternalPerformanceEntry,
+ isPerformanceEntry,
+ now,
+};
diff --git a/lib/internal/perf/timerify.js b/lib/internal/perf/timerify.js
new file mode 100644
index 00000000000000..4f9a46f2057776
--- /dev/null
+++ b/lib/internal/perf/timerify.js
@@ -0,0 +1,103 @@
+'use strict';
+
+const {
+ FunctionPrototypeBind,
+ ObjectDefineProperties,
+ ReflectApply,
+ ReflectConstruct,
+ Symbol,
+} = primordials;
+
+const {
+ now,
+ InternalPerformanceEntry,
+} = require('internal/perf/perf');
+
+const {
+ isConstructor
+} = internalBinding('util');
+
+const {
+ codes: {
+ ERR_INVALID_ARG_TYPE,
+ },
+} = require('internal/errors');
+
+const {
+ enqueue
+} = require('internal/perf/observe');
+
+const kTimerified = Symbol('kTimerified');
+
+function processComplete(name, start, args) {
+ const duration = now() - start;
+ const entry =
+ new InternalPerformanceEntry(
+ name,
+ 'function',
+ start,
+ duration,
+ args);
+
+ for (let n = 0; n < args.length; n++)
+ entry[n] = args[n];
+
+ enqueue(entry);
+}
+
+function timerify(fn) {
+ if (typeof fn !== 'function')
+ throw new ERR_INVALID_ARG_TYPE('fn', 'function', fn);
+
+ if (fn[kTimerified]) return fn[kTimerified];
+
+ const constructor = isConstructor(fn);
+
+ function timerified(...args) {
+ const start = now();
+ const result = constructor ?
+ ReflectConstruct(fn, args, fn) :
+ ReflectApply(fn, this, args);
+ if (!constructor && typeof result?.finally === 'function') {
+ return result.finally(
+ FunctionPrototypeBind(
+ processComplete,
+ result,
+ fn.name,
+ start,
+ args));
+ }
+ processComplete(fn.name, start, args);
+ return result;
+ }
+
+ ObjectDefineProperties(timerified, {
+ [kTimerified]: {
+ configurable: false,
+ enumerable: false,
+ value: timerified,
+ },
+ length: {
+ configurable: false,
+ enumerable: true,
+ value: fn.length,
+ },
+ name: {
+ configurable: false,
+ enumerable: true,
+ value: `timerified ${fn.name}`
+ }
+ });
+
+ ObjectDefineProperties(fn, {
+ [kTimerified]: {
+ configurable: false,
+ enumerable: false,
+ value: timerified,
+ }
+ });
+
+ return timerified;
+}
+
+module.exports = timerify;
diff --git a/lib/internal/perf/usertiming.js b/lib/internal/perf/usertiming.js
new file mode 100644
index 00000000000000..e526443c2a73f1
--- /dev/null
+++ b/lib/internal/perf/usertiming.js
@@ -0,0 +1,153 @@
+'use strict';
+
+const {
+ ObjectKeys,
+ SafeMap,
+} = primordials;
+
+const {
+ kReadOnlyAttributes,
+ InternalPerformanceEntry,
+ now,
+} = require('internal/perf/perf');
+
+const { enqueue } = require('internal/perf/observe');
+
+const nodeTiming = require('internal/perf/nodetiming');
+
+const {
+ validateNumber,
+ validateObject,
+ validateString,
+} = require('internal/validators');
+
+const {
+ codes: {
+ ERR_INVALID_ARG_VALUE,
+ ERR_INVALID_PERFORMANCE_MARK,
+ ERR_PERFORMANCE_INVALID_TIMESTAMP,
+ ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS,
+ },
+} = require('internal/errors');
+
+const marks = new SafeMap();
+const nodeTimingReadOnlyAttributes =
+ nodeTiming.constructor[kReadOnlyAttributes];
+
+function getMark(name) {
+ if (name === undefined) return;
+ if (typeof name === 'number') {
+ if (name < 0)
+ throw new ERR_PERFORMANCE_INVALID_TIMESTAMP(name);
+ return name;
+ }
+ name = `${name}`;
+ if (nodeTimingReadOnlyAttributes.has(name))
+ return nodeTiming[name];
+ const ts = marks.get(name);
+ if (ts === undefined)
+ throw new ERR_INVALID_PERFORMANCE_MARK(name);
+ return ts;
+}
+
+class PerformanceMark extends InternalPerformanceEntry {
+ constructor(name, options = {}) {
+ name = `${name}`;
+ if (nodeTimingReadOnlyAttributes.has(name))
+ throw new ERR_INVALID_ARG_VALUE('name', name);
+ validateObject(options, 'options');
+ const {
+ detail,
+ startTime = now(),
+ } = options;
+ validateNumber(startTime, 'startTime');
+ if (startTime < 0)
+ throw new ERR_PERFORMANCE_INVALID_TIMESTAMP(startTime);
+ marks.set(name, startTime);
+ super(name, 'mark', startTime, 0, detail);
+ enqueue(this);
+ }
+}
+
+class PerformanceMeasure extends InternalPerformanceEntry {
+ constructor(name, start, duration, detail) {
+ super(name, 'measure', start, duration, detail);
+ enqueue(this);
+ }
+}
+
+function mark(name, options = {}) {
+ return new PerformanceMark(name, options);
+}
+
+function calculateStartDuration(startOrMeasureOptions, endMark) {
+ startOrMeasureOptions ??= 0;
+ let detail;
+ let start;
+ let end;
+ let duration;
+ if (typeof startOrMeasureOptions === 'object' &&
+ ObjectKeys(startOrMeasureOptions).length) {
+ ({
+ start,
+ end,
+ duration,
+ detail,
+ } = startOrMeasureOptions);
+ if (endMark !== undefined) {
+ throw new ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS(
+ 'endMark must not be specified');
+ }
+ if (start === undefined && end === undefined) {
+ throw new ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS(
+ 'One of options.start or options.end is required');
+ }
+ if (start !== undefined && end !== undefined && duration !== undefined) {
+ throw new ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS(
+ 'Must not have options.start, options.end, and ' +
+ 'options.duration specified');
+ }
+ start = getMark(start);
+ duration = getMark(duration);
+ } else {
+ start = getMark(startOrMeasureOptions);
+ }
+
+ end = getMark(endMark || end) ??
+ ((start !== undefined && duration !== undefined) ?
+ start + duration : now());
+
+ start ??= (duration !== undefined) ? end - duration : 0;
+
+ duration ??= end - start;
+
+ return { start, duration, detail };
+}
+
+function measure(name, startOrMeasureOptions, endMark) {
+ validateString(name, 'name');
+ const {
+ start,
+ duration,
+ detail
+ } = calculateStartDuration(startOrMeasureOptions, endMark);
+ return new PerformanceMeasure(name, start, duration, detail);
+}
+
+function clearMarks(name) {
+ if (name !== undefined) {
+ name = `${name}`;
+ if (nodeTimingReadOnlyAttributes.has(name))
+ throw new ERR_INVALID_ARG_VALUE('name', name);
+ marks.delete(name);
+ return;
+ }
+ marks.clear();
+}
+
+module.exports = {
+ PerformanceMark,
+ clearMarks,
+ mark,
+ measure,
+};
diff --git a/lib/perf_hooks.js b/lib/perf_hooks.js
index 4572ec88446af2..45246740ca48bf 100644
--- a/lib/perf_hooks.js
+++ b/lib/perf_hooks.js
@@ -1,655 +1,109 @@
'use strict';
const {
- ArrayIsArray,
- ArrayPrototypeFilter,
- ArrayPrototypeForEach,
- ArrayPrototypeIncludes,
- ArrayPrototypeMap,
- ArrayPrototypePush,
- ArrayPrototypeSplice,
- ArrayPrototypeUnshift,
- Boolean,
- NumberIsSafeInteger,
- ObjectDefineProperties,
ObjectDefineProperty,
- ObjectKeys,
- SafeSet,
- Symbol,
+ ObjectDefineProperties,
+ ObjectSetPrototypeOf,
+ TypeError,
} = primordials;
const {
- ELDHistogram: _ELDHistogram,
- PerformanceEntry,
- mark: _mark,
- clearMark: _clearMark,
- measure: _measure,
- milestones,
- observerCounts,
- setupObservers,
- timeOrigin,
timeOriginTimestamp,
- timerify,
constants,
- installGarbageCollectionTracking,
- removeGarbageCollectionTracking,
- loopIdleTime,
} = internalBinding('performance');
const {
- NODE_PERFORMANCE_ENTRY_TYPE_NODE,
- NODE_PERFORMANCE_ENTRY_TYPE_MARK,
- NODE_PERFORMANCE_ENTRY_TYPE_MEASURE,
- NODE_PERFORMANCE_ENTRY_TYPE_GC,
- NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION,
- NODE_PERFORMANCE_ENTRY_TYPE_HTTP2,
- NODE_PERFORMANCE_ENTRY_TYPE_HTTP,
-
- NODE_PERFORMANCE_MILESTONE_NODE_START,
- NODE_PERFORMANCE_MILESTONE_V8_START,
- NODE_PERFORMANCE_MILESTONE_LOOP_START,
- NODE_PERFORMANCE_MILESTONE_LOOP_EXIT,
- NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE,
- NODE_PERFORMANCE_MILESTONE_ENVIRONMENT
-} = constants;
-
-const L = require('internal/linkedlist');
-const kInspect = require('internal/util').customInspectSymbol;
-
-const {
- ERR_INVALID_ARG_TYPE,
- ERR_INVALID_ARG_VALUE,
- ERR_VALID_PERFORMANCE_ENTRY_TYPE,
- ERR_INVALID_PERFORMANCE_MARK
-} = require('internal/errors').codes;
+ now,
+ PerformanceEntry
+} = require('internal/perf/perf');
+const { PerformanceObserver } = require('internal/perf/observe');
const {
- Histogram,
- kHandle,
-} = require('internal/histogram');
-
-const {
- validateCallback,
- validateFunction,
- validateObject,
-} = require('internal/validators');
-
-const { setImmediate } = require('timers');
-const kCallback = Symbol('callback');
-const kTypes = Symbol('types');
-const kEntries = Symbol('entries');
-const kBuffer = Symbol('buffer');
-const kBuffering = Symbol('buffering');
-const kQueued = Symbol('queued');
-const kTimerified = Symbol('timerified');
-const kInsertEntry = Symbol('insert-entry');
-const kGetEntries = Symbol('get-entries');
-const kIndex = Symbol('index');
-const kMarks = Symbol('marks');
-
-const observers = {};
-const observerableTypes = [
- 'node',
- 'mark',
- 'measure',
- 'gc',
- 'function',
- 'http2',
- 'http'
-];
-
-const IDX_STREAM_STATS_ID = 0;
-const IDX_STREAM_STATS_TIMETOFIRSTBYTE = 1;
-const IDX_STREAM_STATS_TIMETOFIRSTHEADER = 2;
-const IDX_STREAM_STATS_TIMETOFIRSTBYTESENT = 3;
-const IDX_STREAM_STATS_SENTBYTES = 4;
-const IDX_STREAM_STATS_RECEIVEDBYTES = 5;
-
-const IDX_SESSION_STATS_TYPE = 0;
-const IDX_SESSION_STATS_PINGRTT = 1;
-const IDX_SESSION_STATS_FRAMESRECEIVED = 2;
-const IDX_SESSION_STATS_FRAMESSENT = 3;
-const IDX_SESSION_STATS_STREAMCOUNT = 4;
-const IDX_SESSION_STATS_STREAMAVERAGEDURATION = 5;
-const IDX_SESSION_STATS_DATA_SENT = 6;
-const IDX_SESSION_STATS_DATA_RECEIVED = 7;
-const IDX_SESSION_STATS_MAX_CONCURRENT_STREAMS = 8;
-
-let http2;
-let sessionStats;
-let streamStats;
-
-function collectHttp2Stats(entry) {
- if (http2 === undefined) http2 = internalBinding('http2');
- switch (entry.name) {
- case 'Http2Stream':
- if (streamStats === undefined)
- streamStats = http2.streamStats;
- entry.id =
- streamStats[IDX_STREAM_STATS_ID] >>> 0;
- entry.timeToFirstByte =
- streamStats[IDX_STREAM_STATS_TIMETOFIRSTBYTE];
- entry.timeToFirstHeader =
- streamStats[IDX_STREAM_STATS_TIMETOFIRSTHEADER];
- entry.timeToFirstByteSent =
- streamStats[IDX_STREAM_STATS_TIMETOFIRSTBYTESENT];
- entry.bytesWritten =
- streamStats[IDX_STREAM_STATS_SENTBYTES];
- entry.bytesRead =
- streamStats[IDX_STREAM_STATS_RECEIVEDBYTES];
- break;
- case 'Http2Session':
- if (sessionStats === undefined)
- sessionStats = http2.sessionStats;
- entry.type =
- sessionStats[IDX_SESSION_STATS_TYPE] >>> 0 === 0 ? 'server' : 'client';
- entry.pingRTT =
- sessionStats[IDX_SESSION_STATS_PINGRTT];
- entry.framesReceived =
- sessionStats[IDX_SESSION_STATS_FRAMESRECEIVED];
- entry.framesSent =
- sessionStats[IDX_SESSION_STATS_FRAMESSENT];
- entry.streamCount =
- sessionStats[IDX_SESSION_STATS_STREAMCOUNT];
- entry.streamAverageDuration =
- sessionStats[IDX_SESSION_STATS_STREAMAVERAGEDURATION];
- entry.bytesWritten =
- sessionStats[IDX_SESSION_STATS_DATA_SENT];
- entry.bytesRead =
- sessionStats[IDX_SESSION_STATS_DATA_RECEIVED];
- entry.maxConcurrentStreams =
- sessionStats[IDX_SESSION_STATS_MAX_CONCURRENT_STREAMS];
- break;
- }
-}
-
-function now() {
- const hr = process.hrtime();
- return hr[0] * 1000 + hr[1] / 1e6;
-}
-
-function getMilestoneTimestamp(milestoneIdx) {
- const ns = milestones[milestoneIdx];
- if (ns === -1)
- return ns;
- return ns / 1e6 - timeOrigin;
-}
-
-class PerformanceNodeTiming extends PerformanceEntry {
- constructor() {
- super();
-
- ObjectDefineProperties(this, {
- name: {
- enumerable: true,
- configurable: true,
- value: 'node'
- },
-
- entryType: {
- enumerable: true,
- configurable: true,
- value: 'node'
- },
-
- startTime: {
- enumerable: true,
- configurable: true,
- value: 0
- },
-
- duration: {
- enumerable: true,
- configurable: true,
- get() {
- return now() - timeOrigin;
- }
- },
-
- nodeStart: {
- enumerable: true,
- configurable: true,
- get() {
- return getMilestoneTimestamp(NODE_PERFORMANCE_MILESTONE_NODE_START);
- }
- },
-
- v8Start: {
- enumerable: true,
- configurable: true,
- get() {
- return getMilestoneTimestamp(NODE_PERFORMANCE_MILESTONE_V8_START);
- }
- },
-
- environment: {
- enumerable: true,
- configurable: true,
- get() {
- return getMilestoneTimestamp(NODE_PERFORMANCE_MILESTONE_ENVIRONMENT);
- }
- },
-
- loopStart: {
- enumerable: true,
- configurable: true,
- get() {
- return getMilestoneTimestamp(NODE_PERFORMANCE_MILESTONE_LOOP_START);
- }
- },
-
- loopExit: {
- enumerable: true,
- configurable: true,
- get() {
- return getMilestoneTimestamp(NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
- }
- },
-
- bootstrapComplete: {
- enumerable: true,
- configurable: true,
- get() {
- return getMilestoneTimestamp(
- NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE);
- }
- },
-
- idleTime: {
- enumerable: true,
- configurable: true,
- get() {
- return loopIdleTime();
- }
- }
- });
- }
- [kInspect]() {
- return {
- name: 'node',
- entryType: 'node',
- startTime: this.startTime,
- duration: this.duration,
- nodeStart: this.nodeStart,
- v8Start: this.v8Start,
- bootstrapComplete: this.bootstrapComplete,
- environment: this.environment,
- loopStart: this.loopStart,
- loopExit: this.loopExit
- };
- }
-}
-
-const nodeTiming = new PerformanceNodeTiming();
-
-// Maintains a list of entries as a linked list stored in insertion order.
-class PerformanceObserverEntryList {
- constructor() {
- ObjectDefineProperties(this, {
- [kEntries]: {
- writable: true,
- enumerable: false,
- value: {}
- }
- });
- L.init(this[kEntries]);
- }
-
- [kInsertEntry](entry) {
- const item = { entry };
- L.append(this[kEntries], item);
- }
-
- [kGetEntries](name, type) {
- const ret = [];
- const list = this[kEntries];
- if (!L.isEmpty(list)) {
- let item = L.peek(list);
- while (item && item !== list) {
- const entry = item.entry;
- if ((name && entry.name !== name) ||
- (type && entry.entryType !== type)) {
- item = item._idlePrev;
- continue;
- }
- sortedInsert(ret, entry);
- item = item._idlePrev;
- }
- }
- return ret;
- }
-
- // While the items are stored in insertion order, getEntries() is
- // required to return items sorted by startTime.
- getEntries() {
- return this[kGetEntries]();
- }
-
- getEntriesByType(type) {
- return this[kGetEntries](undefined, `${type}`);
- }
-
- getEntriesByName(name, type) {
- return this[kGetEntries](`${name}`, type !== undefined ? `${type}` : type);
- }
-}
-
-class PerformanceObserver {
- constructor(callback) {
- validateCallback(callback);
- ObjectDefineProperties(this, {
- [kTypes]: {
- enumerable: false,
- writable: true,
- value: {}
- },
- [kCallback]: {
- enumerable: false,
- writable: true,
- value: callback
- },
- [kBuffer]: {
- enumerable: false,
- writable: true,
- value: new PerformanceObserverEntryList()
- },
- [kBuffering]: {
- enumerable: false,
- writable: true,
- value: false
- },
- [kQueued]: {
- enumerable: false,
- writable: true,
- value: false
- }
- });
- }
-
- disconnect() {
- const observerCountsGC = observerCounts[NODE_PERFORMANCE_ENTRY_TYPE_GC];
- const types = this[kTypes];
- ArrayPrototypeForEach(ObjectKeys(types), (key) => {
- const item = types[key];
- if (item) {
- L.remove(item);
- observerCounts[key]--;
- }
- });
- this[kTypes] = {};
- if (observerCountsGC === 1 &&
- observerCounts[NODE_PERFORMANCE_ENTRY_TYPE_GC] === 0) {
- removeGarbageCollectionTracking();
- }
- }
-
- observe(options) {
- validateObject(options, 'options');
- const { entryTypes } = options;
- if (!ArrayIsArray(entryTypes)) {
- throw new ERR_INVALID_ARG_VALUE('options.entryTypes', entryTypes);
- }
- const filteredEntryTypes =
- ArrayPrototypeMap(ArrayPrototypeFilter(entryTypes, filterTypes),
- mapTypes);
- if (filteredEntryTypes.length === 0) {
- throw new ERR_VALID_PERFORMANCE_ENTRY_TYPE();
- }
- this.disconnect();
- const observerCountsGC = observerCounts[NODE_PERFORMANCE_ENTRY_TYPE_GC];
- this[kBuffer][kEntries] = [];
- L.init(this[kBuffer][kEntries]);
- this[kBuffering] = Boolean(options.buffered);
- ArrayPrototypeForEach(filteredEntryTypes, (entryType) => {
- const list = getObserversList(entryType);
- if (this[kTypes][entryType]) return;
- const item = { obs: this };
- this[kTypes][entryType] = item;
- L.append(list, item);
- observerCounts[entryType]++;
- });
- if (observerCountsGC === 0 &&
- observerCounts[NODE_PERFORMANCE_ENTRY_TYPE_GC] === 1) {
- installGarbageCollectionTracking();
- }
- }
-}
+ PerformanceMark,
+ mark,
+ measure,
+ clearMarks,
+} = require('internal/perf/usertiming');
+
+const eventLoopUtilization = require('internal/perf/event_loop_utilization');
+const monitorEventLoopDelay = require('internal/perf/event_loop_delay');
+const nodeTiming = require('internal/perf/nodetiming');
+const timerify = require('internal/perf/timerify');
+const { customInspectSymbol: kInspect } = require('internal/util');
+const { inspect } = require('util');
class Performance {
constructor() {
- this[kIndex] = {
- [kMarks]: new SafeSet()
- };
- }
-
- get nodeTiming() {
- return nodeTiming;
- }
-
- get timeOrigin() {
- return timeOriginTimestamp;
+ // eslint-disable-next-line no-restricted-syntax
+ throw new TypeError('Illegal constructor');
}
- now() {
- return now() - timeOrigin;
- }
+ [kInspect](depth, options) {
+ if (depth < 0) return this;
- mark(name) {
- name = `${name}`;
- _mark(name);
- this[kIndex][kMarks].add(name);
- }
-
- measure(name, startMark, endMark) {
- name = `${name}`;
- const marks = this[kIndex][kMarks];
- if (arguments.length >= 3) {
- if (!marks.has(endMark) && !(endMark in nodeTiming))
- throw new ERR_INVALID_PERFORMANCE_MARK(endMark);
- else
- endMark = `${endMark}`;
- }
- startMark = startMark !== undefined ? `${startMark}` : '';
- _measure(name, startMark, endMark);
- }
-
- clearMarks(name) {
- if (name !== undefined) {
- name = `${name}`;
- this[kIndex][kMarks].delete(name);
- _clearMark(name);
- } else {
- this[kIndex][kMarks].clear();
- _clearMark();
- }
- }
-
- timerify(fn) {
- validateFunction(fn, 'fn');
- if (fn[kTimerified])
- return fn[kTimerified];
- const ret = timerify(fn, fn.length);
- ObjectDefineProperty(fn, kTimerified, {
- enumerable: false,
- configurable: true,
- writable: false,
- value: ret
- });
- ObjectDefineProperties(ret, {
- [kTimerified]: {
- enumerable: false,
- configurable: true,
- writable: false,
- value: ret
- },
- name: {
- enumerable: false,
- configurable: true,
- writable: false,
- value: `timerified ${fn.name}`
- }
- });
- return ret;
- }
-
- eventLoopUtilization(util1, util2) {
- const ls = nodeTiming.loopStart;
-
- if (ls <= 0) {
- return { idle: 0, active: 0, utilization: 0 };
- }
-
- if (util2) {
- const idle = util1.idle - util2.idle;
- const active = util1.active - util2.active;
- return { idle, active, utilization: active / (idle + active) };
- }
-
- const idle = nodeTiming.idleTime;
- const active = performance.now() - ls - idle;
-
- if (!util1) {
- return { idle, active, utilization: active / (idle + active) };
- }
-
- const idle_delta = idle - util1.idle;
- const active_delta = active - util1.active;
- const utilization = active_delta / (idle_delta + active_delta);
- return { idle: idle_delta, active: active_delta, utilization };
- }
+ const opts = {
+ ...options,
+ depth: options.depth == null ? null : options.depth - 1
+ };
- [kInspect]() {
- return {
+ return `Performance ${inspect({
nodeTiming: this.nodeTiming,
timeOrigin: this.timeOrigin,
- idleTime: this.idleTime,
- };
- }
-}
-
-const performance = new Performance();
-
-function getObserversList(type) {
- let list = observers[type];
- if (list === undefined) {
- list = observers[type] = {};
- L.init(list);
- }
- return list;
-}
-
-function doNotify(observer) {
- observer[kQueued] = false;
- observer[kCallback](observer[kBuffer], observer);
- observer[kBuffer][kEntries] = [];
- L.init(observer[kBuffer][kEntries]);
-}
-
-// Set up the callback used to receive PerformanceObserver notifications
-function observersCallback(entry) {
- const type = mapTypes(entry.entryType);
-
- if (type === NODE_PERFORMANCE_ENTRY_TYPE_HTTP2)
- collectHttp2Stats(entry);
-
- const list = getObserversList(type);
-
- let current = L.peek(list);
-
- while (current && current.obs) {
- const observer = current.obs;
- // First, add the item to the observers buffer
- const buffer = observer[kBuffer];
- buffer[kInsertEntry](entry);
- // Second, check to see if we're buffering
- if (observer[kBuffering]) {
- // If we are, schedule a setImmediate call if one hasn't already
- if (!observer[kQueued]) {
- observer[kQueued] = true;
- // Use setImmediate instead of nextTick to give more time
- // for multiple entries to collect.
- setImmediate(doNotify, observer);
- }
- } else {
- // If not buffering, notify immediately
- doNotify(observer);
- }
- current = current._idlePrev;
+ }, opts)}`;
+ }
+}
+
+class InternalPerformance {}
+InternalPerformance.prototype.constructor = Performance.prototype.constructor;
+ObjectSetPrototypeOf(InternalPerformance.prototype, Performance.prototype);
+
+ObjectDefineProperties(Performance.prototype, {
+ clearMarks: {
+ configurable: true,
+ enumerable: false,
+ value: clearMarks,
+ },
+ eventLoopUtilization: {
+ configurable: true,
+ enumerable: false,
+ value: eventLoopUtilization,
+ },
+ mark: {
+ configurable: true,
+ enumerable: false,
+ value: mark,
+ },
+ measure: {
+ configurable: true,
+ enumerable: false,
+ value: measure,
+ },
+ nodeTiming: {
+ configurable: true,
+ enumerable: false,
+ value: nodeTiming,
+ },
+ now: {
+ configurable: true,
+ enumerable: false,
+ value: now,
+ },
+ timerify: {
+ configurable: true,
+ enumerable: false,
+ value: timerify,
+ },
+ timeOrigin: {
+ configurable: true,
+ enumerable: true,
+ value: timeOriginTimestamp,
}
-}
-setupObservers(observersCallback);
-
-function filterTypes(i) {
- return ArrayPrototypeIncludes(observerableTypes, `${i}`);
-}
-
-function mapTypes(i) {
- switch (i) {
- case 'node': return NODE_PERFORMANCE_ENTRY_TYPE_NODE;
- case 'mark': return NODE_PERFORMANCE_ENTRY_TYPE_MARK;
- case 'measure': return NODE_PERFORMANCE_ENTRY_TYPE_MEASURE;
- case 'gc': return NODE_PERFORMANCE_ENTRY_TYPE_GC;
- case 'function': return NODE_PERFORMANCE_ENTRY_TYPE_FUNCTION;
- case 'http2': return NODE_PERFORMANCE_ENTRY_TYPE_HTTP2;
- case 'http': return NODE_PERFORMANCE_ENTRY_TYPE_HTTP;
- }
-}
-
-// The specification requires that PerformanceEntry instances are sorted
-// according to startTime. Unfortunately, they are not necessarily created
-// in that same order, and can be reported to the JS layer in any order,
-// which means we need to keep the list sorted as we insert.
-function getInsertLocation(list, entryStartTime) {
- let start = 0;
- let end = list.length;
- while (start < end) {
- const pivot = (end + start) >>> 1;
- if (list[pivot].startTime === entryStartTime)
- return pivot;
- if (list[pivot].startTime < entryStartTime)
- start = pivot + 1;
- else
- end = pivot;
- }
- return start;
-}
-
-function sortedInsert(list, entry) {
- const entryStartTime = entry.startTime;
- if (list.length === 0 ||
- (list[list.length - 1].startTime < entryStartTime)) {
- ArrayPrototypePush(list, entry);
- return;
- }
- if (list[0] && (list[0].startTime > entryStartTime)) {
- ArrayPrototypeUnshift(list, entry);
- return;
- }
- const location = getInsertLocation(list, entryStartTime);
- ArrayPrototypeSplice(list, location, 0, entry);
-}
-
-class ELDHistogram extends Histogram {
- constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
- enable() { return this[kHandle].enable(); }
- disable() { return this[kHandle].disable(); }
-}
-
-function monitorEventLoopDelay(options = {}) {
- validateObject(options, 'options');
- const { resolution = 10 } = options;
- if (typeof resolution !== 'number') {
- throw new ERR_INVALID_ARG_TYPE('options.resolution',
- 'number', resolution);
- }
- if (resolution <= 0 || !NumberIsSafeInteger(resolution)) {
- throw new ERR_INVALID_ARG_VALUE.RangeError('resolution', resolution);
- }
- return new ELDHistogram(new _ELDHistogram(resolution));
-}
+});
module.exports = {
- performance,
+ performance: new InternalPerformance(),
+ PerformanceEntry,
+ PerformanceMark,
PerformanceObserver,
monitorEventLoopDelay
};
diff --git a/node.gyp b/node.gyp
index 01ef5e3b6a2f27..c346cff4036af6 100644
--- a/node.gyp
+++ b/node.gyp
@@ -193,6 +193,13 @@
'lib/internal/modules/esm/translators.js',
'lib/internal/net.js',
'lib/internal/options.js',
+ 'lib/internal/perf/perf.js',
+ 'lib/internal/perf/nodetiming.js',
+ 'lib/internal/perf/usertiming.js',
+ 'lib/internal/perf/observe.js',
+ 'lib/internal/perf/event_loop_delay.js',
+ 'lib/internal/perf/event_loop_utilization.js',
+ 'lib/internal/perf/timerify.js',
'lib/internal/policy/manifest.js',
'lib/internal/policy/sri.js',
'lib/internal/priority_queue.js',
diff --git a/src/env-inl.h b/src/env-inl.h
index 4cb68e1c5dea8f..32f9a053832a3e 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -883,11 +883,6 @@ inline performance::PerformanceState* Environment::performance_state() {
return performance_state_.get();
}
-inline std::unordered_map*
- Environment::performance_marks() {
- return &performance_marks_;
-}
-
inline IsolateData* Environment::isolate_data() const {
return isolate_data_;
}
diff --git a/src/env.h b/src/env.h
index 829e2f9fb07c72..7e31b4777ed8d5 100644
--- a/src/env.h
+++ b/src/env.h
@@ -257,6 +257,8 @@ constexpr size_t kFsStatsBufferLength =
V(fingerprint_string, "fingerprint") \
V(flags_string, "flags") \
V(fragment_string, "fragment") \
+ V(frames_received_string, "framesReceived") \
+ V(frames_sent_string, "framesSent") \
V(function_string, "function") \
V(get_data_clone_error_string, "_getDataCloneError") \
V(get_shared_array_buffer_id_string, "_getSharedArrayBufferId") \
@@ -268,6 +270,7 @@ constexpr size_t kFsStatsBufferLength =
V(host_string, "host") \
V(hostmaster_string, "hostmaster") \
V(http_1_1_string, "http/1.1") \
+ V(id_string, "id") \
V(identity_string, "identity") \
V(ignore_string, "ignore") \
V(infoaccess_string, "infoAccess") \
@@ -305,6 +308,7 @@ constexpr size_t kFsStatsBufferLength =
V(library_string, "library") \
V(mac_string, "mac") \
V(max_buffer_string, "maxBuffer") \
+ V(max_concurrent_streams_string, "maxConcurrentStreams") \
V(message_port_constructor_string, "MessagePort") \
V(message_port_string, "messagePort") \
V(message_string, "message") \
@@ -351,6 +355,7 @@ constexpr size_t kFsStatsBufferLength =
V(path_string, "path") \
V(pending_handle_string, "pendingHandle") \
V(pid_string, "pid") \
+ V(ping_rtt_string, "pingRTT") \
V(pipe_source_string, "pipeSource") \
V(pipe_string, "pipe") \
V(pipe_target_string, "pipeTarget") \
@@ -398,6 +403,8 @@ constexpr size_t kFsStatsBufferLength =
V(stats_string, "stats") \
V(status_string, "status") \
V(stdio_string, "stdio") \
+ V(stream_average_duration_string, "streamAverageDuration") \
+ V(stream_count_string, "streamCount") \
V(subject_string, "subject") \
V(subjectaltname_string, "subjectaltname") \
V(syscall_string, "syscall") \
@@ -405,6 +412,9 @@ constexpr size_t kFsStatsBufferLength =
V(thread_id_string, "threadId") \
V(ticketkeycallback_string, "onticketkeycallback") \
V(timeout_string, "timeout") \
+ V(time_to_first_byte_string, "timeToFirstByte") \
+ V(time_to_first_byte_sent_string, "timeToFirstByteSent") \
+ V(time_to_first_header_string, "timeToFirstHeader") \
V(tls_ticket_string, "tlsTicket") \
V(transfer_string, "transfer") \
V(ttl_string, "ttl") \
@@ -1105,7 +1115,6 @@ class Environment : public MemoryRetainer {
EnabledDebugList* enabled_debug_list() { return &enabled_debug_list_; }
inline performance::PerformanceState* performance_state();
- inline std::unordered_map* performance_marks();
void CollectUVExceptionInfo(v8::Local context,
int errorno,
@@ -1438,7 +1447,6 @@ class Environment : public MemoryRetainer {
uint64_t environment_start_time_;
std::unique_ptr performance_state_;
- std::unordered_map performance_marks_;
bool has_run_bootstrapping_code_ = false;
bool has_serialized_options_ = false;
diff --git a/src/node_http2.cc b/src/node_http2.cc
index 930167418e18db..6b137103a5a967 100644
--- a/src/node_http2.cc
+++ b/src/node_http2.cc
@@ -39,7 +39,6 @@ using v8::Uint8Array;
using v8::Undefined;
using v8::Value;
-using node::performance::PerformanceEntry;
namespace http2 {
namespace {
@@ -465,6 +464,7 @@ Http2Session::Http2Session(Http2State* http2_state,
session_type_(type),
http2_state_(http2_state) {
MakeWeak();
+ statistics_.session_type = type;
statistics_.start_time = uv_hrtime();
// Capture the configuration options for this session
@@ -540,71 +540,121 @@ std::string Http2Session::diagnostic_name() const {
std::to_string(static_cast(get_async_id())) + ")";
}
+MaybeLocal