Come valutare le prestazioni di caricamento sul campo con Navigation Timing e Resource Timing

Scopri le nozioni di base per l'utilizzo delle API Navigation e Resource Timing per valutare il rendimento del caricamento sul campo.

Data di pubblicazione: 8 ottobre 2021

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

I test sintetici non sono intrinsecamente errati, ma non rappresentano la velocità di caricamento del sito web per gli utenti reali. Ciò richiede dati sul campo, 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 una sovrapposizione significativa che misurano due cose distinte:

  • Tempi di navigazione misura la velocità delle richieste di documenti HTML (ovvero richieste di navigazione).
  • Il tempo delle risorse misura la velocità delle richieste per le risorse dipendenti dal documento come CSS, JavaScript, immagini e altri tipi di risorse.

Queste API mostrano i propri dati in un buffer di voci sul rendimento, a cui è possibile accedere nel browser con JavaScript. Esistono diversi modi per eseguire query su un buffer di prestazioni, ma un modo comune è 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 da recuperare dal buffer delle voci sul rendimento. 'navigation' e 'resource' recuperano gli orari per le API Navigation Timing e Resource Timing, rispettivamente.

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

La durata e i tempi di una richiesta di rete

La raccolta e l'analisi delle tempistiche di navigazione e delle risorse sono un po' come l'archeologia, in quanto si ricostruisce la vita fugace di una richiesta di rete a seguito del fatto. A volte è utile visualizzare i concetti e, per quanto riguarda le richieste di rete, gli strumenti per sviluppatori del browser possono essere d'aiuto.

Tempi di rete come mostrato in DevTools di Chrome. I tempi indicati si riferiscono alla coda delle richieste, alla negoziazione della connessione, alla richiesta stessa e alla risposta in barre codificate a colori.
Visualizzazione di una richiesta di rete nel riquadro Rete di Chrome DevTools

Il ciclo di vita di una richiesta di rete ha fasi distinte, come la ricerca DNS, l'instaurazione della connessione, la negoziazione TLS e altre sorgenti di latenza. Questi tempi sono rappresentati come DOMHighResTimestamp. A seconda del browser, la granularità dei tempi può essere fino al microsecondo o essere arrotondata per eccesso ai millisecondi. Ti consigliamo di esaminare queste fasi in dettaglio e di capire come si relazionano ai tempi di navigazione e di risorse.

ricerca DNS

Quando un utente accede a un URL, viene eseguita una query sul DNS (Domain Name System) per tradurre un dominio in un indirizzo IP. Questa procedura potrebbe richiedere molto tempo, che ti consigliamo di misurare sul campo. Il tempo di navigazione e il tempo delle risorse mostrano due tempi relativi al DNS:

  • domainLookupStart è il momento in cui inizia la ricerca DNS.
  • domainLookupEnd è il momento in cui termina la ricerca DNS.

Il calcolo del tempo totale di ricerca DNS può essere eseguito 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 che si verifica quando ci si connette a un server web. Se è coinvolto HTTPS, questo processo includerà anche il tempo di 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 inizia la negoziazione TLS.
  • connectEnd indica che la connessione al server web è stata stabilita.

La misurazione del tempo di connessione totale è simile alla misurazione del tempo di ricerca DNS totale: sottrai il tempo di inizio dal tempo di fine. Tuttavia, esiste un'altra proprietà secureConnectionStart che può essere 0 se non viene utilizzato HTTPS o se la connessione è persistente. Se vuoi misurare il tempo della negoziazione TLS, devi tenere presente quanto segue:

// 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 i tempi relativi al recupero dei documenti e delle relative risorse dipendenti.

Richieste e risposte

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

  • Fattori esterni:ad esempio latenza e larghezza di banda. Oltre a scegliere una società di hosting e eventualmente una CDN, non sono (quasi) sotto il nostro controllo, poiché gli utenti possono accedere al web da qualsiasi luogo.
  • Fattori intrinseci: sono aspetti come le architetture lato server e lato client, nonché le dimensioni delle risorse e la nostra capacità di ottimizzare per quegli aspetti, che sono sotto il nostro controllo.

Entrambi i tipi di fattori influiscono sulle prestazioni di caricamento. I tempi relativi a questi fattori sono fondamentali, in quanto descrivono il tempo necessario per scaricare le risorse. Sia Navigation Timing che Resource Timing descrivono le prestazioni di caricamento con le seguenti metriche:

  • fetchStart viene contrassegnato quando il browser inizia a recuperare una risorsa (Tempi risorse) o un documento per una richiesta di navigazione (Tempi di navigazione). Questa è la prima della richiesta effettiva e il punto in cui il browser controlla le cache (ad esempio, HTTP e istanze 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 quando nessun service worker controlla la pagina corrente.
  • requestStart è il momento in cui il browser effettua la richiesta.
  • responseStart è il momento in cui arriva il primo byte della risposta.
  • responseEnd indica l'arrivo dell'ultimo byte della risposta.

Questi tempi ti consentono di misurare più aspetti del rendimento del caricamento, ad esempio la ricerca nella cache all'interno di un worker di servizio 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 e 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 eseguire

I parametri Navigation Timing e Resource Timing sono utili per più di quanto descritto negli esempi precedenti. Ecco alcune altre situazioni con tempistiche pertinenti che potrebbero valere la pena di essere esplorate:

  • Reindirizzamenti di pagina:i reindirizzamenti sono una fonte trascurata di ulteriore latenza, in particolare le catene di reindirizzamento. La latenza viene aggiunta in diversi modi, ad esempio tramite i passaggi da HTTP a HTTPs, nonché i reindirizzamenti 302/301 non memorizzati nella cache. I tempi redirectStart, redirectEnd e redirectCount sono utili per valutare la latenza del reindirizzamento.
  • Scarico del documento:nelle pagine che eseguono codice in un unload gestore eventi, il browser deve eseguire il codice prima di poter passare alla pagina successiva. unloadEventStart e unloadEventEnd misurano lo scarico dei documenti.
  • Elaborazione di documenti: il tempo di elaborazione dei documenti potrebbe non essere significativo, a meno che il tuo sito web non invii payload HTML di grandi dimensioni. Se questa è la tua situazione, le tempistiche di domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd e domComplete potrebbero interessarti.

Come ottenere i tempi nel codice

Tutti gli esempi mostrati finora utilizzano performance.getEntriesByType, ma esistono altri modi per eseguire query sul buffer delle voci relative al rendimento, ad esempio performance.getEntriesByName e performance.getEntries. Questi metodi sono adatti quando è necessaria solo un'analisi superficiale. In altre situazioni, tuttavia, possono introdurre un lavoro eccessivo nel thread principale eseguendo l'iterazione su un numero elevato di voci o addirittura eseguendo ripetutamente il polling del buffer di prestazioni per trovarne di nuove.

L'approccio consigliato per raccogliere le voci dal buffer di voci relative alle prestazioni è utilizzare un elemento PerformanceObserver. PerformanceObserver ascolta le 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 delle tempistiche potrebbe sembrare scomodo rispetto all'accesso diretto al buffer di immissione delle prestazioni, ma è preferibile collegare il thread principale a un lavoro che non serve a uno scopo critico e rivolto agli utenti.

Come chiamare a casa

Una volta raccolti tutti i tempi necessari, puoi inviarli a un endpoint per ulteriori analisi. Puoi farlo in due modi: con navigator.sendBeacon o con 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 da superare la sessione della pagina corrente, se necessario:

// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
  // 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());

  // Send the data!
  navigator.sendBeacon('/analytics', data);
}

In questo esempio, la stringa JSON arriverà in un payload POST che puoi decodificare, elaborare e memorizzare in un backend dell'applicazione in base alle esigenze.

Conclusione

Una volta raccolte le metriche, sta a te capire come analizzare i dati di campo. Quando si analizzano i dati dei campi, è necessario seguire alcune regole generali per assicurarsi di trarre conclusioni significative:

  • Evita le medie, in quanto non sono rappresentative dell'esperienza di un singolo utente e potrebbero essere distorte da valori anomali.
  • Affidati ai percentili. Nei set di dati delle metriche sul rendimento basate sul tempo, un valore più basso è migliore. Ciò significa che, quando dai la priorità ai percentile bassi, presti attenzione solo alle esperienze più rapide.
  • Dai la priorità alla long tail dei valori. Quando dai la priorità alle esperienze con un percentile pari o superiore al 75%, ti concentri dove serve: sulle esperienze più lente.

Questa guida non vuole essere una risorsa esaustiva sulla navigazione o sulla temporizzazione delle risorse, ma un punto di partenza. Ecco alcune risorse aggiuntive che potrebbero esserti utili:

Con queste API e i dati che forniscono, avrai a disposizione gli strumenti necessari per comprendere meglio l'esperienza degli utenti reali in termini di prestazioni di caricamento, il che ti consentirà di diagnosticare e risolvere più facilmente i problemi di prestazioni di caricamento sul campo.