Always check timing data even on fetch error (4xx/5xx)
Resource Timing API records latency even for error responses. Now we check for timing data regardless of fetch success/failure, only reporting unreachable if no timing data is available.
This commit is contained in:
parent
8be7002ad9
commit
0038f23460
70
src/main.js
70
src/main.js
@ -32,6 +32,25 @@ const hostState = HOSTS.map(host => ({
|
|||||||
status: 'pending', // 'online', 'offline', 'pending', 'error'
|
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 with Resource Timing API
|
||||||
async function measureLatency(url) {
|
async function measureLatency(url) {
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
@ -43,6 +62,8 @@ async function measureLatency(url) {
|
|||||||
targetUrl.searchParams.set('_cb', cacheBuster)
|
targetUrl.searchParams.set('_cb', cacheBuster)
|
||||||
const finalUrl = targetUrl.toString()
|
const finalUrl = targetUrl.toString()
|
||||||
|
|
||||||
|
let fetchError = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetch(finalUrl, {
|
await fetch(finalUrl, {
|
||||||
method: 'HEAD',
|
method: 'HEAD',
|
||||||
@ -50,39 +71,32 @@ async function measureLatency(url) {
|
|||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
})
|
})
|
||||||
|
|
||||||
clearTimeout(timeoutId)
|
|
||||||
|
|
||||||
// Get accurate timing from Resource Timing API
|
|
||||||
const entries = performance.getEntriesByName(finalUrl, '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
|
|
||||||
}
|
|
||||||
// Clean up the entry
|
|
||||||
performance.clearResourceTimings()
|
|
||||||
return { latency: Math.round(latency), error: null }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback if no timing entry found
|
|
||||||
return { latency: null, error: null }
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
clearTimeout(timeoutId)
|
fetchError = err
|
||||||
performance.clearResourceTimings()
|
}
|
||||||
|
|
||||||
if (err.name === 'AbortError') {
|
clearTimeout(timeoutId)
|
||||||
return { latency: null, error: 'timeout' }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return { latency, error: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
// No timing data - determine error type
|
||||||
|
if (fetchError?.name === 'AbortError') {
|
||||||
|
return { latency: null, error: 'timeout' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fetchError) {
|
||||||
return { latency: null, error: 'unreachable' }
|
return { latency: null, error: 'unreachable' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch succeeded but no timing (shouldn't happen)
|
||||||
|
return { latency: null, error: null }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get latency color based on value
|
// Get latency color based on value
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user