@@ -2,24 +2,36 @@ import { Region, RegionsResponse } from "api/typesGenerated";
2
2
import { useEffect , useReducer } from "react" ;
3
3
import PerformanceObserver from "@fastly/performance-observer-polyfill"
4
4
import axios from "axios" ;
5
+ import { generateRandomString } from "utils/random" ;
5
6
6
7
8
+ export interface ProxyLatencyReport {
9
+ // accurate identifies if the latency was calculated using the
10
+ // PerformanceResourceTiming API. If this is false, then the
11
+ // latency is calculated using the total duration of the request
12
+ // and will be off by a good margin.
13
+ accurate : boolean
14
+ latencyMS : number
15
+ // at is when the latency was recorded.
16
+ at : Date
17
+ }
18
+
7
19
interface ProxyLatencyAction {
8
20
proxyID : string
9
- latencyMS : number
21
+ report : ProxyLatencyReport
10
22
}
11
23
12
24
const proxyLatenciesReducer = (
13
- state : Record < string , number > ,
25
+ state : Record < string , ProxyLatencyReport > ,
14
26
action : ProxyLatencyAction ,
15
- ) : Record < string , number > => {
27
+ ) : Record < string , ProxyLatencyReport > => {
16
28
// Just overwrite any existing latency.
17
- state [ action . proxyID ] = action . latencyMS
29
+ state [ action . proxyID ] = action . report
18
30
return state
19
31
}
20
32
21
- export const useProxyLatency = ( proxies ?: RegionsResponse ) : Record < string , number > => {
22
- const [ proxyLatenciesMS , dispatchProxyLatenciesMS ] = useReducer (
33
+ export const useProxyLatency = ( proxies ?: RegionsResponse ) : Record < string , ProxyLatencyReport > => {
34
+ const [ proxyLatencies , dispatchProxyLatencies ] = useReducer (
23
35
proxyLatenciesReducer ,
24
36
{ } ,
25
37
) ;
@@ -34,20 +46,23 @@ export const useProxyLatency = (proxies?: RegionsResponse): Record<string, numbe
34
46
// This is for the observer to know which requests are important to
35
47
// record.
36
48
const proxyChecks = proxies . regions . reduce ( ( acc , proxy ) => {
49
+ // Only run the latency check on healthy proxies.
37
50
if ( ! proxy . healthy ) {
38
51
return acc
39
52
}
40
53
41
- const url = new URL ( "/latency-check" , proxy . path_app_url )
54
+ // Add a random query param to the url to make sure we don't get a cached response.
55
+ // This is important in case there is some caching layer between us and the proxy.
56
+ const url = new URL ( `/latency-check?cache_bust=${ generateRandomString ( 6 ) } ` , proxy . path_app_url )
42
57
acc [ url . toString ( ) ] = proxy
43
58
return acc
44
59
} , { } as Record < string , Region > )
45
60
46
61
47
- // dispatchProxyLatenciesMSGuarded will assign the latency to the proxy
62
+ // dispatchProxyLatenciesGuarded will assign the latency to the proxy
48
63
// via the reducer. But it will only do so if the performance entry is
49
64
// a resource entry that we care about.
50
- const dispatchProxyLatenciesMSGuarded = ( entry :PerformanceEntry ) :void => {
65
+ const dispatchProxyLatenciesGuarded = ( entry :PerformanceEntry ) :void => {
51
66
if ( entry . entryType !== "resource" ) {
52
67
// We should never get these, but just in case.
53
68
return
@@ -56,26 +71,32 @@ export const useProxyLatency = (proxies?: RegionsResponse): Record<string, numbe
56
71
// The entry.name is the url of the request.
57
72
const check = proxyChecks [ entry . name ]
58
73
if ( ! check ) {
59
- // This is not a proxy request.
74
+ // This is not a proxy request, so ignore it .
60
75
return
61
76
}
62
77
63
78
// These docs are super useful.
64
79
// https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Resource_timing
65
80
let latencyMS = 0
81
+ let accurate = false
66
82
if ( "requestStart" in entry && ( entry as PerformanceResourceTiming ) . requestStart !== 0 ) {
67
83
// This is the preferred logic to get the latency.
68
84
const timingEntry = entry as PerformanceResourceTiming
69
- latencyMS = timingEntry . responseEnd - timingEntry . requestStart
85
+ latencyMS = timingEntry . responseStart - timingEntry . requestStart
86
+ accurate = true
70
87
} else {
71
88
// This is the total duration of the request and will be off by a good margin.
72
89
// This is a fallback if the better timing is not available.
73
90
console . log ( `Using fallback latency calculation for "${ entry . name } ". Latency will be incorrect and larger then actual.` )
74
91
latencyMS = entry . duration
75
92
}
76
- dispatchProxyLatenciesMS ( {
93
+ dispatchProxyLatencies ( {
77
94
proxyID : check . id ,
78
- latencyMS : latencyMS ,
95
+ report : {
96
+ latencyMS,
97
+ accurate,
98
+ at : new Date ( ) ,
99
+ } ,
79
100
} )
80
101
81
102
return
@@ -86,7 +107,7 @@ export const useProxyLatency = (proxies?: RegionsResponse): Record<string, numbe
86
107
const observer = new PerformanceObserver ( ( list ) => {
87
108
// If we get entries via this callback, then dispatch the events to the latency reducer.
88
109
list . getEntries ( ) . forEach ( ( entry ) => {
89
- dispatchProxyLatenciesMSGuarded ( entry )
110
+ dispatchProxyLatenciesGuarded ( entry )
90
111
} )
91
112
} )
92
113
@@ -112,13 +133,13 @@ export const useProxyLatency = (proxies?: RegionsResponse): Record<string, numbe
112
133
// We want to call this before we disconnect the observer to make sure we get all the
113
134
// proxy requests recorded.
114
135
observer . takeRecords ( ) . forEach ( ( entry ) => {
115
- dispatchProxyLatenciesMSGuarded ( entry )
136
+ dispatchProxyLatenciesGuarded ( entry )
116
137
} )
117
138
// At this point, we can be confident that all the proxy requests have been recorded
118
139
// via the performance observer. So we can disconnect the observer.
119
140
observer . disconnect ( )
120
141
} )
121
142
} , [ proxies ] )
122
143
123
- return proxyLatenciesMS
144
+ return proxyLatencies
124
145
}
0 commit comments