Fix timing: use performance.now() instead of Resource Timing API
Resource Timing entries are added asynchronously after fetch resolves, causing a race condition. The simple performance.now() around fetch gives accurate latency measurements without this issue.
This commit is contained in:
parent
0038f23460
commit
ed602fdb5f
56
src/main.js
56
src/main.js
@ -32,71 +32,37 @@ const hostState = HOSTS.map(host => ({
|
||||
status: 'pending', // 'online', 'offline', 'pending', 'error'
|
||||
}))
|
||||
|
||||
// Extract timing from Resource Timing API
|
||||
function getTimingForUrl(url) {
|
||||
const entries = performance.getEntriesByName(url, 'resource')
|
||||
if (entries.length > 0) {
|
||||
const entry = entries[entries.length - 1]
|
||||
// Use responseEnd - requestStart for actual network time
|
||||
// Fall back to duration if detailed timing not available (cross-origin)
|
||||
let latency
|
||||
if (entry.requestStart > 0 && entry.responseEnd > 0) {
|
||||
latency = entry.responseEnd - entry.requestStart
|
||||
} else {
|
||||
// For opaque responses, duration includes queuing time but is still more accurate
|
||||
latency = entry.duration
|
||||
}
|
||||
return Math.round(latency)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Measure latency using HEAD request with Resource Timing API
|
||||
// Measure latency using HEAD request
|
||||
async function measureLatency(url) {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT)
|
||||
|
||||
// Add cache-busting parameter
|
||||
const targetUrl = new URL(url)
|
||||
const cacheBuster = `${Date.now()}-${Math.random().toString(36).slice(2)}`
|
||||
targetUrl.searchParams.set('_cb', cacheBuster)
|
||||
const finalUrl = targetUrl.toString()
|
||||
targetUrl.searchParams.set('_cb', Date.now().toString())
|
||||
|
||||
let fetchError = null
|
||||
const start = performance.now()
|
||||
|
||||
try {
|
||||
await fetch(finalUrl, {
|
||||
await fetch(targetUrl.toString(), {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
cache: 'no-store',
|
||||
signal: controller.signal,
|
||||
})
|
||||
} catch (err) {
|
||||
fetchError = err
|
||||
}
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
// Always check timing - even 4xx/5xx responses have timing data
|
||||
const latency = getTimingForUrl(finalUrl)
|
||||
performance.clearResourceTimings()
|
||||
|
||||
// If we got timing data, return it regardless of fetch error
|
||||
if (latency !== null) {
|
||||
const latency = Math.round(performance.now() - start)
|
||||
clearTimeout(timeoutId)
|
||||
return { latency, error: null }
|
||||
}
|
||||
} catch (err) {
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
// No timing data - determine error type
|
||||
if (fetchError?.name === 'AbortError') {
|
||||
return { latency: null, error: 'timeout' }
|
||||
}
|
||||
if (err.name === 'AbortError') {
|
||||
return { latency: null, error: 'timeout' }
|
||||
}
|
||||
|
||||
if (fetchError) {
|
||||
return { latency: null, error: 'unreachable' }
|
||||
}
|
||||
|
||||
// Fetch succeeded but no timing (shouldn't happen)
|
||||
return { latency: null, error: null }
|
||||
}
|
||||
|
||||
// Get latency color based on value
|
||||
|
||||
Loading…
Reference in New Issue
Block a user