v145 · Web APIs · Timing Analysis
Timing Analysis
How to use Resource Timing fields to distinguish cache hits from network fetches, which fields reveal each phase, and how to compute meaningful percentiles for each population separately.
Resource Timing fields for population detection
| Field | Cache hit | Network fetch | Notes |
|---|---|---|---|
transferSize |
0 | > 0 | Primary classifier. 0 = served from cache or service worker with no-body response. |
encodedBodySize |
> 0 | > 0 | The encoded size of the response body. Present even for cache hits. |
connectStart |
0 | > 0 (or equals fetchStart if reused connection) |
0 for cache hits; non-zero when a new TCP connection was needed. |
domainLookupStart |
equals fetchStart |
equals fetchStart or later |
For cache hits, no DNS lookup occurs. |
duration |
< 5ms (typically) | 20–2000ms (network) | The most directly observable bimodal split. |
classifying entries
function classifyEntry(entry) {
// transferSize=0 with encodedBodySize>0 → cache or service worker
if (entry.transferSize === 0 && entry.encodedBodySize > 0) {
// Further distinguish memory cache vs disk cache by duration:
if (entry.duration < 1) return 'memory-cache';
return 'disk-cache';
}
// transferSize > 0 → actual network fetch
if (entry.connectStart > 0) return 'network-new-connection';
return 'network-reused-connection';
}
const entries = performance.getEntriesByType('resource');
const groups = { 'memory-cache': [], 'disk-cache': [], 'network-new-connection': [], 'network-reused-connection': [] };
for (const e of entries) {
const cls = classifyEntry(e);
groups[cls].push(e.duration);
}
for (const [cls, durations] of Object.entries(groups)) {
if (!durations.length) continue;
durations.sort((a, b) => a - b);
console.log(cls, 'p50:', durations[Math.floor(durations.length * 0.5)].toFixed(2) + 'ms');
}
why averages hide bimodal distributions
// Example: 90% cache hits at 2ms, 10% network at 400ms
// Average = (0.9 × 2) + (0.1 × 400) = 41.8ms
// p50 = 2ms (cache — correctly)
// p95 = 2ms (still cache)
// p99 = 400ms (network)
// The average (41.8ms) represents nothing real —
// no request actually takes ~42ms. The bimodal split
// reveals the true performance landscape:
// Fast path (cache): p50=2ms, p95=3ms — great
// Slow path (network): p50=400ms, p95=1200ms — needs work
// Actionable insight from bimodal analysis:
// → Improve cache hit rate (preload, service worker)
// → Optimise network path (CDN, compression, HTTP/3)
see also
scenario focus
Select a scenario to focus its rendered example and summary.