Skip to content

Commit a4ba6e9

Browse files
committed
Add bandwidth estimation algorithm
1 parent 26e3c42 commit a4ba6e9

File tree

1 file changed

+112
-0
lines changed

1 file changed

+112
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
function isLikelyStaticResource(initiatorType: string) {
11+
switch (initiatorType) {
12+
case 'css':
13+
case 'script':
14+
case 'font':
15+
case 'img':
16+
case 'image':
17+
case 'input':
18+
case 'link':
19+
return true;
20+
default:
21+
return false;
22+
}
23+
}
24+
25+
export default function estimateBandwidth(): number {
26+
// Estimate the current bandwidth for downloading static resources given resources already
27+
// loaded.
28+
// $FlowFixMe[method-unbinding]
29+
if (typeof performance.getEntriesByType === 'function') {
30+
let count = 0;
31+
let bits = 0;
32+
const resourceEntries = performance.getEntriesByType('resource');
33+
for (let i = 0; i < resourceEntries.length; i++) {
34+
const entry = resourceEntries[i];
35+
// $FlowFixMe[prop-missing]
36+
const transferSize: number = entry.transferSize;
37+
// $FlowFixMe[prop-missing]
38+
const initiatorType: string = entry.initiatorType;
39+
const duration = entry.duration;
40+
if (
41+
!transferSize ||
42+
!duration ||
43+
!isLikelyStaticResource(initiatorType)
44+
) {
45+
// Skip cached, cross-orgin entries and resources likely to be dynamically generated.
46+
continue;
47+
}
48+
// Find any overlapping entries that were transferring at the same time since the total
49+
// bps at the time will include those bytes.
50+
let overlappingBytes = 0;
51+
// $FlowFixMe[prop-missing]
52+
const parentEndTime: number = entry.responseEnd;
53+
let j;
54+
for (j = i + 1; j < resourceEntries.length; j++) {
55+
const overlapEntry = resourceEntries[j];
56+
const overlapStartTime = overlapEntry.startTime;
57+
if (overlapStartTime > parentEndTime) {
58+
break;
59+
}
60+
// $FlowFixMe[prop-missing]
61+
const overlapTransferSize: number = overlapEntry.transferSize;
62+
// $FlowFixMe[prop-missing]
63+
const overlapInitiatorType: string = overlapEntry.initiatorType;
64+
if (
65+
!overlapTransferSize ||
66+
!isLikelyStaticResource(overlapInitiatorType)
67+
) {
68+
// Skip cached, cross-orgin entries and resources likely to be dynamically generated.
69+
continue;
70+
}
71+
// $FlowFixMe[prop-missing]
72+
const overlapEndTime: number = overlapEntry.responseEnd;
73+
const overlapFactor =
74+
overlapEndTime < parentEndTime
75+
? 1
76+
: (parentEndTime - overlapStartTime) /
77+
(overlapEndTime - overlapStartTime);
78+
overlappingBytes += overlapTransferSize * overlapFactor;
79+
}
80+
// Skip past any entries we already considered overlapping. Otherwise we'd have to go
81+
// back to consider previous entries when we then handled them.
82+
i = j - 1;
83+
84+
const bps =
85+
((transferSize + overlappingBytes) * 8) / (entry.duration / 1000);
86+
bits += bps;
87+
count++;
88+
if (count > 10) {
89+
// We have enough to get an average.
90+
break;
91+
}
92+
}
93+
if (count > 0) {
94+
return bits / count / 1e6;
95+
}
96+
}
97+
98+
// Fallback to the navigator.connection estimate if available
99+
// $FlowFixMe[prop-missing]
100+
if (navigator.connection) {
101+
// $FlowFixMe
102+
const downlink: ?number = navigator.connection.downlink;
103+
if (typeof downlink === 'number') {
104+
return downlink;
105+
}
106+
}
107+
108+
// Otherwise, use a default of 5mbps to compute heuristics.
109+
// This can happen commonly in Safari if all static resources and images are loaded
110+
// cross-orgin.
111+
return 5;
112+
}

0 commit comments

Comments
 (0)