Skip to content

Commit 66de817

Browse files
committed
core(network-request): exclude dns from rtt estimates
1 parent d0233f5 commit 66de817

File tree

6 files changed

+67
-48
lines changed

6 files changed

+67
-48
lines changed

core/lib/dependency-graph/simulator/network-analyzer.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,15 @@ class NetworkAnalyzer {
193193
if (!Number.isFinite(timing.sendStart) || timing.sendStart < 0) return;
194194

195195
// Assume everything before sendStart was just DNS + (SSL)? + TCP handshake
196-
// 1 RT for DNS, 1 RT (maybe) for SSL, 1 RT for TCP
197-
let roundTrips = 1;
196+
// 1 RT (maybe) for SSL, 1 RT for TCP
197+
// DNS is not included because
198+
// 1) it is very variable in terms of how many hops it could be (as little as 0, if cached locally)
199+
// 2) it doesn't even communicate with the server, so it is useless for RTT calculation.
200+
let roundTrips = 0;
198201
if (!record.protocol.startsWith('h3')) roundTrips += 1; // TCP
199202
if (record.parsedURL.scheme === 'https') roundTrips += 1;
200-
return timing.sendStart / roundTrips;
203+
const dnsTime = timing.dnsStart >= 0 ? timing.dnsEnd - timing.dnsStart : 0;
204+
return (timing.sendStart - dnsTime) / roundTrips;
201205
});
202206
}
203207

@@ -227,13 +231,15 @@ class NetworkAnalyzer {
227231
// When connection was fresh...
228232
// TTFB = DNS + (SSL)? + TCP handshake + 1 RT for request + server response time
229233
if (!connectionReused) {
230-
roundTrips += 1; // DNS
234+
// We purposely exclude DNS from RTT estimate, as it is unrelated to RTT to the server.
231235
if (!record.protocol.startsWith('h3')) roundTrips += 1; // TCP
232236
if (record.parsedURL.scheme === 'https') roundTrips += 1; // SSL
233237
}
234238

235-
// subtract out our estimated server response time
236-
return Math.max((timing.receiveHeadersEnd - estimatedServerResponseTime) / roundTrips, 3);
239+
// subtract out our estimated server response time and dns time
240+
const dnsTime = timing.dnsStart >= 0 ? timing.dnsEnd - timing.dnsStart : 0;
241+
const duration = timing.receiveHeadersEnd - estimatedServerResponseTime - dnsTime;
242+
return Math.max(duration / roundTrips, 3);
237243
});
238244
}
239245

core/test/audits/largest-contentful-paint-element-test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,11 @@ describe('Performance: largest-contentful-paint-element audit', () => {
114114
expect(auditResult.details.items[1].items[0].phase).toBeDisplayString('TTFB');
115115
expect(auditResult.details.items[1].items[0].timing).toBeCloseTo(800, 0.1);
116116
expect(auditResult.details.items[1].items[1].phase).toBeDisplayString('Load Delay');
117-
expect(auditResult.details.items[1].items[1].timing).toBeCloseTo(651, 0.1);
117+
expect(auditResult.details.items[1].items[1].timing).toBeCloseTo(650.25, 0.1);
118118
expect(auditResult.details.items[1].items[2].phase).toBeDisplayString('Load Time');
119-
expect(auditResult.details.items[1].items[2].timing).toBeCloseTo(1813.7, 0.1);
119+
expect(auditResult.details.items[1].items[2].timing).toBeCloseTo(1812.8, 0.1);
120120
expect(auditResult.details.items[1].items[3].phase).toBeDisplayString('Render Delay');
121-
expect(auditResult.details.items[1].items[3].timing).toBeCloseTo(2539.2, 0.1);
121+
expect(auditResult.details.items[1].items[3].timing).toBeCloseTo(2537.9, 0.1);
122122
});
123123

124124
it('doesn\'t throw an error when there is nothing to show', async () => {

core/test/computed/metrics/lcp-breakdown-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ describe('LCPBreakdown', () => {
129129
const result = await LCPBreakdown.request(data, {computedCache: new Map()});
130130

131131
expect(result.ttfb).toBeCloseTo(800, 0.1);
132-
expect(result.loadStart).toBeCloseTo(2579.5, 0.1);
133-
expect(result.loadEnd).toBeCloseTo(5804, 0.1);
132+
expect(result.loadStart).toBeCloseTo(2578.2, 0.1);
133+
expect(result.loadEnd).toBeCloseTo(5801, 0.1);
134134
});
135135

136136
it('returns observed for image LCP', async () => {

core/test/computed/metrics/time-to-first-byte-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ describe('Metrics: TTFB', () => {
9191

9292
// 3 * 150 RTT + 99 server response time
9393
// 99 Comes from (100ms observed TTFB - 1ms observed RTT)
94-
expect(result.timing).toEqual(549);
94+
expect(result.timing).toEqual(548.5);
9595
expect(result.timestamp).toBeUndefined();
9696
});
9797

@@ -105,7 +105,7 @@ describe('Metrics: TTFB', () => {
105105

106106
// 4 * 150 RTT + 99.1 server response time
107107
// 99.1 Comes from (100ms observed TTFB - 0.9ms observed RTT)
108-
expect(result.timing).toEqual(699.1);
108+
expect(result.timing).toEqual(699);
109109
expect(result.timestamp).toBeUndefined();
110110
});
111111

core/test/fixtures/fraggle-rock/reports/sample-flow-result.json

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16563,7 +16563,7 @@
1656316563
"description": "First Contentful Paint marks the time at which the first text or image is painted. [Learn more about the First Contentful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/first-contentful-paint/).",
1656416564
"score": 1,
1656516565
"scoreDisplayMode": "numeric",
16566-
"numericValue": 866.7026,
16566+
"numericValue": 866.7448999999999,
1656716567
"numericUnit": "millisecond",
1656816568
"displayValue": "0.9 s"
1656916569
},
@@ -16573,7 +16573,7 @@
1657316573
"description": "Largest Contentful Paint marks the time at which the largest text or image is painted. [Learn more about the Largest Contentful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)",
1657416574
"score": 0.98,
1657516575
"scoreDisplayMode": "numeric",
16576-
"numericValue": 1803.0092,
16576+
"numericValue": 1803.2048,
1657716577
"numericUnit": "millisecond",
1657816578
"displayValue": "1.8 s"
1657916579
},
@@ -16583,7 +16583,7 @@
1658316583
"description": "First Meaningful Paint measures when the primary content of a page is visible. [Learn more about the First Meaningful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/first-meaningful-paint/).",
1658416584
"score": 1,
1658516585
"scoreDisplayMode": "numeric",
16586-
"numericValue": 866.7026,
16586+
"numericValue": 866.7448999999999,
1658716587
"numericUnit": "millisecond",
1658816588
"displayValue": "0.9 s"
1658916589
},
@@ -16593,7 +16593,7 @@
1659316593
"description": "Speed Index shows how quickly the contents of a page are visibly populated. [Learn more about the Speed Index metric](https://developer.chrome.com/docs/lighthouse/performance/speed-index/).",
1659416594
"score": 1,
1659516595
"scoreDisplayMode": "numeric",
16596-
"numericValue": 866.7026,
16596+
"numericValue": 866.7448999999999,
1659716597
"numericUnit": "millisecond",
1659816598
"displayValue": "0.9 s"
1659916599
},
@@ -16752,7 +16752,7 @@
1675216752
"description": "Time to Interactive is the amount of time it takes for the page to become fully interactive. [Learn more about the Time to Interactive metric](https://developer.chrome.com/docs/lighthouse/performance/interactive/).",
1675316753
"score": 1,
1675416754
"scoreDisplayMode": "numeric",
16755-
"numericValue": 925.2026,
16755+
"numericValue": 925.2448999999999,
1675616756
"numericUnit": "millisecond",
1675716757
"displayValue": "0.9 s"
1675816758
},
@@ -17185,10 +17185,10 @@
1718517185
"numTasksOver50ms": 0,
1718617186
"numTasksOver100ms": 0,
1718717187
"numTasksOver500ms": 0,
17188-
"rtt": 0.003,
17188+
"rtt": 0.0045,
1718917189
"throughput": 48594453.24888648,
17190-
"maxRtt": 0.0468,
17191-
"maxServerLatency": 3.5377,
17190+
"maxRtt": 0.0702,
17191+
"maxServerLatency": 3.53605,
1719217192
"totalByteWeight": 127688,
1719317193
"totalTaskTime": 113.59800000000007,
1719417194
"mainDocumentTransferSize": 3596
@@ -17616,7 +17616,7 @@
1761617616
"description": "Network round trip times (RTT) have a large impact on performance. If the RTT to an origin is high, it's an indication that servers closer to the user could improve performance. [Learn more about the Round Trip Time](https://hpbn.co/primer-on-latency-and-bandwidth/).",
1761717617
"score": null,
1761817618
"scoreDisplayMode": "informative",
17619-
"numericValue": 0.0468,
17619+
"numericValue": 0.0702,
1762017620
"numericUnit": "millisecond",
1762117621
"displayValue": "0 ms",
1762217622
"details": {
@@ -17637,19 +17637,19 @@
1763717637
"items": [
1763817638
{
1763917639
"origin": "https://www.mikescerealshack.co",
17640-
"rtt": 0.0468
17640+
"rtt": 0.0702
1764117641
},
1764217642
{
1764317643
"origin": "https://events.mikescerealshack.co",
17644-
"rtt": 0.0039
17644+
"rtt": 0.00585
1764517645
},
1764617646
{
1764717647
"origin": "https://fonts.googleapis.com",
17648-
"rtt": 0.0033000000000000004
17648+
"rtt": 0.00495
1764917649
},
1765017650
{
1765117651
"origin": "https://fonts.gstatic.com",
17652-
"rtt": 0.003
17652+
"rtt": 0.0045
1765317653
}
1765417654
],
1765517655
"sortedBy": [
@@ -17663,7 +17663,7 @@
1766317663
"description": "Server latencies can impact web performance. If the server latency of an origin is high, it's an indication the server is overloaded or has poor backend performance. [Learn more about server response time](https://hpbn.co/primer-on-web-performance/#analyzing-the-resource-waterfall).",
1766417664
"score": null,
1766517665
"scoreDisplayMode": "informative",
17666-
"numericValue": 3.5377,
17666+
"numericValue": 3.53605,
1766717667
"numericUnit": "millisecond",
1766817668
"displayValue": "0 ms",
1766917669
"details": {
@@ -17684,19 +17684,19 @@
1768417684
"items": [
1768517685
{
1768617686
"origin": "https://fonts.googleapis.com",
17687-
"serverResponseTime": 3.5377
17687+
"serverResponseTime": 3.53605
1768817688
},
1768917689
{
1769017690
"origin": "https://www.mikescerealshack.co",
17691-
"serverResponseTime": 2.5712
17691+
"serverResponseTime": 2.5478000000000005
1769217692
},
1769317693
{
1769417694
"origin": "https://events.mikescerealshack.co",
17695-
"serverResponseTime": 1.5821
17695+
"serverResponseTime": 1.5801500000000002
1769617696
},
1769717697
{
1769817698
"origin": "https://fonts.gstatic.com",
17699-
"serverResponseTime": 0.9365
17699+
"serverResponseTime": 0.935
1770017700
}
1770117701
],
1770217702
"sortedBy": [
@@ -18033,7 +18033,7 @@
1803318033
"items": [
1803418034
{
1803518035
"phase": "TTFB",
18036-
"timing": 602.5712,
18036+
"timing": 602.5478,
1803718037
"percent": "33%"
1803818038
},
1803918039
{
@@ -18043,12 +18043,12 @@
1804318043
},
1804418044
{
1804518045
"phase": "Load Time",
18046-
"timing": 234.3445723189716,
18046+
"timing": 234.45876538040773,
1804718047
"percent": "13%"
1804818048
},
1804918049
{
1805018050
"phase": "Render Delay",
18051-
"timing": 966.0934276810284,
18051+
"timing": 966.1982346195922,
1805218052
"percent": "54%"
1805318053
}
1805418054
]
@@ -18137,12 +18137,12 @@
1813718137
{
1813818138
"url": "https://www.mikescerealshack.co/_next/static/chunks/framework.9d524150d48315f49e80.js",
1813918139
"duration": 76,
18140-
"startTime": 907.7026
18140+
"startTime": 907.7448999999999
1814118141
},
1814218142
{
1814318143
"url": "https://www.mikescerealshack.co/_next/static/chunks/main-1f8481d632114a408557.js",
1814418144
"duration": 63,
18145-
"startTime": 752.7026
18145+
"startTime": 752.7448999999999
1814618146
}
1814718147
],
1814818148
"sortedBy": [
@@ -23021,31 +23021,31 @@
2302123021
"core/lib/i18n/i18n.js | seconds": [
2302223022
{
2302323023
"values": {
23024-
"timeInMs": 866.7026
23024+
"timeInMs": 866.7448999999999
2302523025
},
2302623026
"path": "audits[first-contentful-paint].displayValue"
2302723027
},
2302823028
{
2302923029
"values": {
23030-
"timeInMs": 1803.0092
23030+
"timeInMs": 1803.2048
2303123031
},
2303223032
"path": "audits[largest-contentful-paint].displayValue"
2303323033
},
2303423034
{
2303523035
"values": {
23036-
"timeInMs": 866.7026
23036+
"timeInMs": 866.7448999999999
2303723037
},
2303823038
"path": "audits[first-meaningful-paint].displayValue"
2303923039
},
2304023040
{
2304123041
"values": {
23042-
"timeInMs": 866.7026
23042+
"timeInMs": 866.7448999999999
2304323043
},
2304423044
"path": "audits[speed-index].displayValue"
2304523045
},
2304623046
{
2304723047
"values": {
23048-
"timeInMs": 925.2026
23048+
"timeInMs": 925.2448999999999
2304923049
},
2305023050
"path": "audits.interactive.displayValue"
2305123051
},
@@ -23101,13 +23101,13 @@
2310123101
},
2310223102
{
2310323103
"values": {
23104-
"timeInMs": 0.0468
23104+
"timeInMs": 0.0702
2310523105
},
2310623106
"path": "audits[network-rtt].displayValue"
2310723107
},
2310823108
{
2310923109
"values": {
23110-
"timeInMs": 3.5377
23110+
"timeInMs": 3.53605
2311123111
},
2311223112
"path": "audits[network-server-latency].displayValue"
2311323113
}

core/test/lib/dependency-graph/simulator/network-analyzer-test.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,21 @@ describe('DependencyGraph/Simulator/NetworkAnalyzer', () => {
161161
});
162162

163163
it('should infer from sendStart when available', () => {
164-
const timing = {sendStart: 150};
164+
const timing = {sendStart: 100, dnsStart: -1, dnsEnd: -1};
165165
// this record took 150ms before Chrome could send the request
166-
// i.e. DNS (maybe) + queuing (maybe) + TCP handshake took ~100ms
167-
// 150ms / 3 round trips ~= 50ms RTT
166+
// i.e. queuing (maybe) + TCP handshake took ~100ms
167+
// 100ms / 2 round trips ~= 50ms RTT
168+
const record = createRecord({networkRequestTime: 0, networkEndTime: 1, timing});
169+
const result = NetworkAnalyzer.estimateRTTByOrigin([record], {coarseEstimateMultiplier: 1});
170+
const expected = {min: 50, max: 50, avg: 50, median: 50};
171+
assert.deepStrictEqual(result.get('https://example.com'), expected);
172+
});
173+
174+
it('should infer from sendStart when available, and exclude dns', () => {
175+
const timing = {sendStart: 150, dnsStart: 0, dnsEnd: 50};
176+
// this record took 150ms before Chrome could send the request
177+
// i.e. queuing (maybe) + TCP handshake took ~100ms
178+
// (150ms - 50ms) / 2 round trips ~= 50ms RTT
168179
const record = createRecord({networkRequestTime: 0, networkEndTime: 1, timing});
169180
const result = NetworkAnalyzer.estimateRTTByOrigin([record], {coarseEstimateMultiplier: 1});
170181
const expected = {min: 50, max: 50, avg: 50, median: 50};
@@ -187,7 +198,7 @@ describe('DependencyGraph/Simulator/NetworkAnalyzer', () => {
187198
});
188199

189200
it('should infer from TTFB when available', () => {
190-
const timing = {receiveHeadersEnd: 1000};
201+
const timing = {receiveHeadersEnd: 1000, dnsStart: 0, dnsEnd: 150};
191202
const record = createRecord({networkRequestTime: 0, networkEndTime: 1, timing,
192203
resourceType: 'Other'});
193204
const result = NetworkAnalyzer.estimateRTTByOrigin([record], {
@@ -198,12 +209,14 @@ describe('DependencyGraph/Simulator/NetworkAnalyzer', () => {
198209
// which needs ~4 RTs. We don't know its resource type so it'll be assumed that 40% of it was
199210
// server response time.
200211
// 600 ms / 4 = 150ms
212+
// But: one of those RTs is from DNS, which we want to ignore
213+
// (600ms - 150ms) / 3 RTs = 150ms
201214
const expected = {min: 150, max: 150, avg: 150, median: 150};
202215
assert.deepStrictEqual(result.get('https://example.com'), expected);
203216
});
204217

205218
it('should handle untrustworthy connection information', () => {
206-
const timing = {sendStart: 150};
219+
const timing = {sendStart: 100};
207220
const recordA = createRecord({networkRequestTime: 0, networkEndTime: 1, timing,
208221
connectionReused: true});
209222
const recordB = createRecord({

0 commit comments

Comments
 (0)