diff --git a/site/src/contexts/useProxyLatency.ts b/site/src/contexts/useProxyLatency.ts index 926202f784..02d50e66d2 100644 --- a/site/src/contexts/useProxyLatency.ts +++ b/site/src/contexts/useProxyLatency.ts @@ -4,6 +4,8 @@ import PerformanceObserver from "@fastly/performance-observer-polyfill" import axios from "axios" import { generateRandomString } from "utils/random" +const proxyIntervalSeconds = 30 // seconds + export interface ProxyLatencyReport { // accurate identifies if the latency was calculated using the // PerformanceResourceTiming API. If this is false, then the @@ -17,6 +19,8 @@ export interface ProxyLatencyReport { interface ProxyLatencyAction { proxyID: string + // cached indicates if the latency was loaded from a cache (local storage) + cached: boolean report: ProxyLatencyReport } @@ -59,8 +63,13 @@ export const useProxyLatency = ( // This latestFetchRequest is used to trigger a refetch of the proxy latencies. const [latestFetchRequest, setLatestFetchRequest] = useState( - new Date().toISOString(), + // The initial state is the current time minus the interval. Any proxies that have a latency after this + // in the cache are still valid. + new Date(new Date().getTime() - proxyIntervalSeconds * 1000).toISOString(), ) + + // Refetch will always set the latestFetchRequest to the current time, making all the cached latencies + // stale and triggering a refetch of all proxies in the list. const refetch = () => { const d = new Date() setLatestFetchRequest(d.toISOString()) @@ -73,6 +82,8 @@ export const useProxyLatency = ( return } + const storedLatencies = loadStoredLatencies() + // proxyMap is a map of the proxy path_app_url to the proxy object. // This is for the observer to know which requests are important to // record. @@ -82,6 +93,28 @@ export const useProxyLatency = ( return acc } + // Do not run latency checks if a cached check exists below the latestFetchRequest Date. + // This prevents fetching latencies too often. + // 1. Fetch the latest stored latency for the given proxy. + // 2. If the latest latency is after the latestFetchRequest, then skip the latency check. + if (storedLatencies && storedLatencies[proxy.id]) { + const fetchRequestDate = new Date(latestFetchRequest) + const latest = storedLatencies[proxy.id].reduce((prev, next) => + prev.at > next.at ? prev : next, + ) + + if (latest && latest.at > fetchRequestDate) { + // dispatch the cached latency. This latency already went through the + // guard logic below, so we can just dispatch it again directly. + dispatchProxyLatencies({ + proxyID: proxy.id, + cached: true, + report: latest, + }) + return acc + } + } + // Add a random query param to the url to make sure we don't get a cached response. // This is important in case there is some caching layer between us and the proxy. const url = new URL( @@ -131,6 +164,7 @@ export const useProxyLatency = ( } const update = { proxyID: check.id, + cached: false, report: { latencyMS, accurate, @@ -203,7 +237,13 @@ const loadStoredLatencies = (): Record => { return {} } - return JSON.parse(str) + return JSON.parse(str, (key, value) => { + // By default json loads dates as strings. We want to convert them back to 'Date's. + if (key === "at") { + return new Date(value) + } + return value + }) } const updateStoredLatencies = (action: ProxyLatencyAction): void => {