Valutazione delle prestazioni di caricamento sul campo con Navigation Timing e Resource Timing

Apprendi le nozioni di base sull'utilizzo delle API di navigazione e di gestione delle risorse per valutare le prestazioni di caricamento sul campo.

Se hai utilizzato la limitazione della connessione nel riquadro Rete negli strumenti per sviluppatori di un browser (o Lighthouse in Chrome) per valutare le prestazioni di caricamento, sai quanto sono utili questi strumenti per l'ottimizzazione delle prestazioni. Puoi misurare rapidamente l'impatto delle ottimizzazioni delle prestazioni con una velocità di connessione di base coerente e stabile. L'unico problema è che si tratta di test sintetici, che generano dati di lab, non dati sul campo.

I test sintetici non sono intrinsecamente malia, ma non sono rappresentativi della velocità di caricamento del tuo sito web per gli utenti reali. Per farlo, sono necessari dati dei campi che puoi raccogliere dalle API Navigation Timing e Resource Timing.

API per aiutarti a valutare le prestazioni di caricamento sul campo

Navigation Timing e Resource Timing sono due API simili con sovrapposizioni significative che misurano due cose distinte:

  • Navigazione Tempi, che misura la velocità delle richieste per i documenti HTML (ovvero, le richieste di navigazione).
  • Il tempo delle risorse misura la velocità delle richieste per le risorse dipendenti dai documenti, come CSS, JavaScript, immagini e così via.

Queste API espongono i propri dati in un buffer di inserimento delle prestazioni, a cui è possibile accedere nel browser con JavaScript. Esistono diversi modi per eseguire query su un buffer delle prestazioni, ma uno dei modi più comuni è utilizzare performance.getEntriesByType:

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

performance.getEntriesByType accetta una stringa che descrive il tipo di voci che vuoi recuperare dal buffer delle voci delle prestazioni. 'navigation' e 'resource' recuperano le tempistiche rispettivamente per le API Navigation Timing e Resource Timing.

La quantità di informazioni fornite da queste API può essere difficoltosa, ma sono la chiave per misurare le prestazioni del caricamento sul campo, poiché puoi raccogliere questi tempi dagli utenti mentre visitano il tuo sito web.

La durata e i tempi di una richiesta di rete

Raccogliere e analizzare i tempi di navigazione e delle risorse è un po' come un'archeologia, in quanto si sta ricostruendo la vita fugace di una richiesta di rete a posteriori. A volte può essere utile visualizzare i concetti e, per quanto riguarda le richieste di rete, possono essere utili gli strumenti per sviluppatori del tuo browser.

Diagramma dei tempi di rete come mostrato in DevTools di Chrome. Le tempistiche illustrate si riferiscono all'accodamento delle richieste, alla negoziazione della connessione, alla richiesta stessa e alla risposta in barre codificate a colori.
Una visualizzazione di una richiesta di rete nel riquadro di rete di DevTools

La durata di una richiesta di rete è suddivisa in fasi distinte, come la ricerca DNS, la creazione della connessione, la negoziazione TLS e così via. Queste tempistiche sono rappresentate come DOMHighResTimestamp. A seconda del browser, la granularità dei tempi di esecuzione può essere ridotta al microsecondo o arrotondata ai millisecondi. Esaminiamo queste fasi in dettaglio e la loro relazione con le tempistiche di navigazione e di gestione delle risorse.

ricerca DNS

Quando un utente accede a un URL, al sistema DNS (Domain Name System) viene richiesto di tradurre un dominio in un indirizzo IP. Questo processo potrebbe richiedere molto tempo, anche sul campo. I tempi di navigazione e quelli delle risorse espongono due tempistiche relative al DNS:

  • domainLookupStart è l'inizio della ricerca DNS.
  • domainLookupEnd è il termine della ricerca DNS.

Il calcolo del tempo totale di ricerca DNS può essere effettuato sottraendo la metrica iniziale dalla metrica finale:

// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;

Negoziazione della connessione

Un altro fattore che contribuisce alle prestazioni di caricamento è la negoziazione della connessione, ovvero la latenza registrata durante la connessione a un server web. Se è coinvolto HTTPS, il processo includerà anche la data e l'ora della negoziazione TLS. La fase di connessione prevede tre tempistiche:

  • connectStart indica quando il browser inizia ad aprire una connessione a un server web.
  • secureConnectionStart indica quando il client avvia la negoziazione TLS.
  • connectEnd indica che la connessione al server web è stata stabilita.

La misurazione del tempo totale di connessione è simile a quella del tempo totale di ricerca DNS: devi sottrarre il tempo di inizio da quello di fine. Tuttavia, esiste un'ulteriore proprietà secureConnectionStart che potrebbe essere 0 se HTTPS non viene utilizzato o se la connessione è permanente. Se vuoi misurare la durata della negoziazione TLS, tieni presente che:

// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}

Al termine della ricerca DNS e della negoziazione della connessione, entrano in gioco le tempistiche relative al recupero dei documenti e delle relative risorse dipendenti.

Richieste e risposte

Le prestazioni del caricamento sono influenzate da due tipi di fattori:

  • Fattori estrinseci: riguardano la latenza e la larghezza di banda. Oltre a scegliere una società di hosting e una rete CDN, sono (per lo più) fuori dal nostro controllo, in quanto gli utenti possono accedere al web da qualsiasi luogo.
  • Fattori intrinseci: si tratta di elementi come le architetture lato client e server, nonché le dimensioni delle risorse e la nostra capacità di ottimizzare gli elementi, che sono sotto il nostro controllo.

Entrambi i tipi di fattori influiscono sulle prestazioni del caricamento. Le tempistiche relative a questi fattori sono fondamentali, in quanto descrivono il tempo necessario per il download delle risorse. Sia Tempi navigazione che Tempi risorse descrivono le prestazioni di caricamento con le seguenti metriche:

  • fetchStart segna quando il browser inizia a recuperare una risorsa (Tempi risorse) o un documento per una richiesta di navigazione (Tempi di navigazione). Questo dato precede la richiesta effettiva ed è il punto in cui il browser controlla le cache (ad esempio, istanze HTTP e Cache).
  • workerStart contrassegna quando inizia la gestione di una richiesta all'interno del gestore di eventi fetch di un service worker. Il valore sarà 0 se nessun service worker controlla la pagina corrente.
  • requestStart indica quando il browser effettua la richiesta.
  • responseStart indica quando arriva il primo byte della risposta.
  • responseEnd è l'arrivo dell'ultimo byte della risposta.

Questi tempi consentono di misurare diversi aspetti delle prestazioni di caricamento, come la ricerca nella cache all'interno di un service worker e il tempo di download:

// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}

Puoi anche misurare altri aspetti della latenza di richiesta/risposta:

const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;

Altre misurazioni che puoi effettuare

Le tempistiche della navigazione e delle risorse sono utili per andare oltre gli esempi precedenti. Ecco altre situazioni con tempistiche pertinenti che potrebbe valere la pena esaminare:

  • Reindirizzamenti di pagine:i reindirizzamenti sono una fonte trascurata di maggiore latenza, in particolare le catene di reindirizzamenti. La latenza viene aggiunta in diversi modi, ad esempio tramite hop da HTTP a HTTP e reindirizzamenti 301 302/non memorizzati nella cache. Le tempistiche redirectStart, redirectEnd e redirectCount sono utili per valutare la latenza di reindirizzamento.
  • Unload di documenti: nelle pagine che eseguono codice in un gestore di eventi unload, il browser deve eseguire il codice prima di poter passare alla pagina successiva. unloadEventStart e unloadEventEnd misurano l'unload dei documenti.
  • Elaborazione di documenti:i tempi di elaborazione dei documenti potrebbero non essere significativi, a meno che il sito web non invii payload HTML molto grandi. Se la tua situazione descrive la tua situazione, potrebbero interessarti le tempistiche domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd e domComplete.

Tempistiche di acquisizione nel codice dell'applicazione

Tutti gli esempi mostrati finora utilizzano performance.getEntriesByType, ma esistono altri modi per eseguire query sul buffer di inserimento delle prestazioni, come performance.getEntriesByName e performance.getEntries. Questi metodi sono validi quando è necessaria solo un'analisi leggera. In altre situazioni, però, possono introdurre un lavoro eccessivo del thread principale eseguendo l'iterazione su un numero elevato di voci o persino eseguendo ripetutamente il polling del buffer delle prestazioni per trovarne di nuove.

L'approccio consigliato per raccogliere le voci dal buffer di inserimento delle prestazioni è utilizzare un elemento PerformanceObserver. PerformanceObserver rimane in ascolto delle voci relative alle prestazioni e le fornisce man mano che vengono aggiunte al buffer:

// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});

Questo metodo di raccolta dei tempi può sembrare complicato rispetto all'accesso diretto al buffer di ingresso delle prestazioni, ma è preferibile collegare il thread principale con un lavoro che non ha uno scopo critico e rivolto all'utente.

Chiamata a casa

Una volta raccolte tutte le tempistiche, puoi inviarle a un endpoint per ulteriori analisi. Puoi farlo in due modi: con navigator.sendBeacon o fetch con l'opzione keepalive impostata. Entrambi i metodi invieranno una richiesta a un endpoint specificato in modo non bloccante e la richiesta verrà messa in coda in modo tale da superare la sessione della pagina corrente, se necessario:

// Caution: If you have lots of performance entries, don't
// do this. This is an example for illustrative purposes.
const data = JSON.stringify(performance.getEntries()));

// The endpoint to transmit the encoded data to
const endpoint = '/analytics';

// Check for fetch keepalive support
if ('keepalive' in Request.prototype) {
  fetch(endpoint, {
    method: 'POST',
    body: data,
    keepalive: true,
    headers: {
      'Content-Type': 'application/json'
    }
  });
} else if ('sendBeacon' in navigator) {
  // Use sendBeacon as a fallback
  navigator.sendBeacon(endpoint, data);
}

In questo esempio, la stringa JSON arriverà in un payload POST che potrai decodificare e elaborare/archiviare nel backend di un'applicazione in base alle tue esigenze.

In sintesi

Una volta raccolte le metriche, spetta a te capire come analizzare i dati dei campi. Durante l'analisi dei dati dei campi, esistono alcune regole generali da seguire per assicurarsi di trarre conclusioni significative:

  • Evita le medie, che non sono rappresentative dell'esperienza di alcun utente e potrebbero essere distorte da valori anomali.
  • Affidati ai percentili. Nei set di dati di metriche relative alle prestazioni basate sul tempo, "basso è meglio". Ciò significa che quando dai la priorità ai percentili bassi, presti attenzione solo alle esperienze più veloci.
  • Dai la priorità alla long tail dei valori. Quando dai priorità alle esperienze al 75° percentile o superiore, ci metti l'attenzione sull'argomento: sulle esperienze più lente.

Questa guida non è intesa come una risorsa esaustiva sulla navigazione o sui tempi delle risorse, ma solo un punto di partenza. Di seguito sono riportate alcune risorse aggiuntive che potresti trovare utili:

Con queste API e i dati che forniscono, potrai comprendere meglio le prestazioni di caricamento per gli utenti reali, il che ti darà maggiore sicurezza nella diagnosi e nella risoluzione dei problemi relativi alle prestazioni di caricamento sul campo.