perf_hooks: add missing resource timing attributes by greenheadHQ · Pull Request #65017 · nodejs/node · GitHub
Skip to content
Merged
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
79 changes: 79 additions & 0 deletions doc/api/perf_hooks.md
41 changes: 40 additions & 1 deletion lib/internal/perf/resource_timing.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const { enqueue, bufferResourceTiming } = require('internal/perf/observe');
const { validateThisInternalField } = require('internal/validators');
const { kEnumerableProperty } = require('internal/util');

const kBodyInfo = Symbol('kBodyInfo');
const kCacheMode = Symbol('kCacheMode');
const kRequestedUrl = Symbol('kRequestedUrl');
const kTimingInfo = Symbol('kTimingInfo');
Expand Down Expand Up @@ -110,11 +111,22 @@ class PerformanceResourceTiming extends PerformanceEntry {
return this[kTimingInfo].finalNetworkRequestStartTime;
}

get responseStart() {
get finalResponseHeadersStart() {
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
return this[kTimingInfo].finalNetworkResponseStartTime;
}

get firstInterimResponseStart() {
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
return this[kTimingInfo].firstInterimNetworkResponseStartTime ?? 0;
}

get responseStart() {
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
return this[kTimingInfo].firstInterimNetworkResponseStartTime ||
this[kTimingInfo].finalNetworkResponseStartTime;
}

get responseEnd() {
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
return this[kTimingInfo].endTime;
Expand Down Expand Up @@ -148,6 +160,22 @@ class PerformanceResourceTiming extends PerformanceEntry {
return this[kResponseStatus];
}

get renderBlockingStatus() {
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
return this[kTimingInfo].renderBlocking === true ?
'blocking' : 'non-blocking';
}

get contentType() {
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
return this[kBodyInfo]?.contentType ?? '';
}

get contentEncoding() {
validateThisInternalField(this, kTimingInfo, 'PerformanceResourceTiming');
return this[kBodyInfo]?.contentEncoding ?? '';
}

toJSON() {
validateThisInternalField(this, kInitiatorType, 'PerformanceResourceTiming');
return {
Expand All @@ -167,13 +195,18 @@ class PerformanceResourceTiming extends PerformanceEntry {
connectEnd: this.connectEnd,
secureConnectionStart: this.secureConnectionStart,
requestStart: this.requestStart,
finalResponseHeadersStart: this.finalResponseHeadersStart,
firstInterimResponseStart: this.firstInterimResponseStart,
responseStart: this.responseStart,
responseEnd: this.responseEnd,
transferSize: this.transferSize,
encodedBodySize: this.encodedBodySize,
decodedBodySize: this.decodedBodySize,
deliveryType: this.deliveryType,
responseStatus: this.responseStatus,
renderBlockingStatus: this.renderBlockingStatus,
contentType: this.contentType,
contentEncoding: this.contentEncoding,
};
}
}
Expand All @@ -191,13 +224,18 @@ ObjectDefineProperties(PerformanceResourceTiming.prototype, {
connectEnd: kEnumerableProperty,
secureConnectionStart: kEnumerableProperty,
requestStart: kEnumerableProperty,
finalResponseHeadersStart: kEnumerableProperty,
firstInterimResponseStart: kEnumerableProperty,
responseStart: kEnumerableProperty,
responseEnd: kEnumerableProperty,
transferSize: kEnumerableProperty,
encodedBodySize: kEnumerableProperty,
decodedBodySize: kEnumerableProperty,
deliveryType: kEnumerableProperty,
responseStatus: kEnumerableProperty,
renderBlockingStatus: kEnumerableProperty,
contentType: kEnumerableProperty,
contentEncoding: kEnumerableProperty,
toJSON: kEnumerableProperty,
[SymbolToStringTag]: {
__proto__: null,
Expand All @@ -224,6 +262,7 @@ function createPerformanceResourceTiming(
// The spec doesn't say to validate it in the class construction.
resourceTiming[kTimingInfo] = timingInfo;
resourceTiming[kCacheMode] = cacheMode;
resourceTiming[kBodyInfo] = bodyInfo;
resourceTiming[kDeliveryType] = deliveryType;
resourceTiming[kResponseStatus] = responseStatus;

Expand Down
116 changes: 116 additions & 0 deletions test/parallel/test-perf-hooks-resourcetiming-attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
'use strict';

require('../common');
const assert = require('assert');
const {
PerformanceResourceTiming,
performance,
} = require('perf_hooks');

// Covers the IDL attributes finalResponseHeadersStart,
// firstInterimResponseStart, renderBlockingStatus, contentType and contentEncoding,
// and the spec requirement that responseStart prefers a non-zero
// firstInterimResponseStart over finalResponseHeadersStart.

function createTimingInfo(overrides = {}) {
return {
startTime: 0,
redirectStartTime: 0,
redirectEndTime: 0,
postRedirectStartTime: 0,
finalServiceWorkerStartTime: 0,
finalNetworkRequestStartTime: 0,
finalNetworkResponseStartTime: 0,
endTime: 0,
encodedBodySize: 0,
decodedBodySize: 0,
finalConnectionTimingInfo: null,
...overrides,
};
}

function markResourceTiming(timingInfo, bodyInfo) {
return performance.markResourceTiming(
timingInfo,
'http://localhost:8080',
'fetch',
{},
'',
bodyInfo,
200,
'',
);
}

// Default values when no optional timing or body metadata is present.
{
const resource = markResourceTiming(createTimingInfo(), {});

assert.strictEqual(resource.finalResponseHeadersStart, 0);
assert.strictEqual(resource.firstInterimResponseStart, 0);
assert.strictEqual(resource.responseStart, 0);
assert.strictEqual(resource.renderBlockingStatus, 'non-blocking');
assert.strictEqual(resource.contentType, '');
assert.strictEqual(resource.contentEncoding, '');
}

// responseStart falls back to the final response when no interim response timing was recorded.
{
const resource = markResourceTiming(createTimingInfo({
finalNetworkResponseStartTime: 123,
firstInterimNetworkResponseStartTime: 0,
}), {});

assert.strictEqual(resource.finalResponseHeadersStart, 123);
assert.strictEqual(resource.firstInterimResponseStart, 0);
assert.strictEqual(resource.responseStart, 123);
}

// Values reflected from timing info and body info.
{
const timingInfo = createTimingInfo({
finalNetworkResponseStartTime: 123,
firstInterimNetworkResponseStartTime: 45,
renderBlocking: true,
});
const bodyInfo = {
contentType: 'text/html',
contentEncoding: 'gzip',
};
const resource = markResourceTiming(timingInfo, bodyInfo);

assert.strictEqual(resource.finalResponseHeadersStart, 123);
assert.strictEqual(resource.firstInterimResponseStart, 45);
assert.strictEqual(resource.responseStart, 45);
assert.strictEqual(resource.renderBlockingStatus, 'blocking');
assert.strictEqual(resource.contentType, 'text/html');
assert.strictEqual(resource.contentEncoding, 'gzip');

const json = resource.toJSON();
assert.strictEqual(json.finalResponseHeadersStart, 123);
assert.strictEqual(json.firstInterimResponseStart, 45);
assert.strictEqual(json.responseStart, 45);
assert.strictEqual(json.renderBlockingStatus, 'blocking');
assert.strictEqual(json.contentType, 'text/html');
assert.strictEqual(json.contentEncoding, 'gzip');
}

// The attributes are enumerable getters on the prototype and perform a
// brand check like the other PerformanceResourceTiming attributes.
for (const name of [
'finalResponseHeadersStart',
'firstInterimResponseStart',
'renderBlockingStatus',
'contentType',
'contentEncoding',
]) {
const desc = Object.getOwnPropertyDescriptor(
PerformanceResourceTiming.prototype, name);
assert.strictEqual(desc.enumerable, true, name);
assert.strictEqual(typeof desc.get, 'function', name);
assert.throws(() => desc.get.call({}), {
code: 'ERR_INVALID_THIS',
}, name);
}

performance.clearResourceTimings();
67 changes: 67 additions & 0 deletions test/parallel/test-perf-hooks-resourcetiming-fetch.mjs
Loading
Loading