È molto utile disporre di metriche incentrate sull'utente che puoi misurare in modo universale su qualsiasi sito web. Queste metriche ti consentono di:
- Scopri in che modo gli utenti reali utilizzano il web nel suo complesso.
- Confronta il tuo sito con quello di un concorrente.
- Monitora dati utili e strategici nei tuoi strumenti di analisi senza dover scrivere codice personalizzato.
Le metriche universali offrono una buona base di riferimento, ma in molti casi occorre misurare più semplicemente queste metriche per acquisire l'esperienza completa per il tuo sito specifico.
Le metriche personalizzate ti consentono di misurare aspetti dell'esperienza sul tuo sito che potrebbero essere applicabili solo al tuo sito, ad esempio:
- Il tempo necessario a un'app a pagina singola (APS) per passare da una "pagina" all'altra.
- Il tempo necessario a una pagina per visualizzare i dati recuperati da un database per gli utenti che hanno eseguito l'accesso.
- Tempo impiegato da un'app con rendering lato server (SSR) per l'hydration.
- La percentuale di hit della cache per le risorse caricate dai visitatori di ritorno.
- La latenza degli eventi di clic o tastiera in un gioco.
API per misurare le metriche personalizzate
In passato, gli sviluppatori web non disponevano di molte API di basso livello per misurare il rendimento e, di conseguenza, hanno dovuto ricorrere a hack per valutare il rendimento di un sito.
Ad esempio, è possibile determinare se il thread principale è bloccato a causa di attività JavaScript di lunga durata eseguendo un ciclo requestAnimationFrame
e calcolando il delta tra ogni frame. Se il delta è notevolmente superiore alla frequenza fotogrammi del display, puoi segnalarlo come attività lunga. Tuttavia, questi hack non sono consigliati perché influiscono sulle prestazioni stesse (ad esempio scaricando la batteria).
La prima regola per una misurazione efficace del rendimento è assicurarsi che le tecniche di misurazione del rendimento non causino problemi di rendimento. Pertanto, per qualsiasi metrica personalizzata che misuri sul tuo sito, è meglio utilizzare una delle seguenti API, se possibile.
API Performance Observer
L'API Performance Observer è il meccanismo che raccoglie e mostra i dati di tutte le altre API per le prestazioni discusse in questa pagina. Comprendere è fondamentale per ottenere dati di qualità.
Puoi utilizzare PerformanceObserver
per iscriverti passivamente agli eventi relativi al rendimento. In questo modo, i callback dell'API vengono attivati durante i periodi di inattività, il che significa che in genere non interferiscono con il rendimento della pagina.
Per creare un PerformanceObserver
, passalo un callback da eseguire ogni volta che vengono inviate nuove voci relative alle prestazioni. Poi, indica all'osservatore i tipi di voci da ascoltare utilizzando il metodo observe()
:
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
po.observe({type: 'some-entry-type'});
Le sezioni seguenti elencano tutti i vari tipi di voce disponibili per l'osservazione, ma nei browser più recenti è possibile controllare quali tipi di voce sono disponibili tramite la proprietà PerformanceObserver.supportedEntryTypes
statica.
Osservare le voci che si sono già verificate
Per impostazione predefinita, gli oggetti PerformanceObserver
possono osservare le voci solo man mano che si presentano. Ciò può causare problemi se vuoi eseguire il caricamento differito del codice di analisi del rendimento in modo che non blocchi le risorse con priorità più elevata.
Per ottenere le voci storiche (dopo che si sono verificate), imposta il flag buffered
su true
quando chiami observe()
. Il browser includerà le voci storiche del proprio buffer delle voci sul rendimento la prima volta che viene chiamato il tuo PerformanceObserver
callback, fino alle dimensioni massime del buffer per quel tipo.
po.observe({
type: 'some-entry-type',
buffered: true,
});
API Performance legacy da evitare
Prima dell'API Performance Observer, gli sviluppatori potevano accedere alle voci relative alle prestazioni utilizzando i seguenti tre metodi definiti nell'oggetto performance
:
Sebbene queste API siano ancora supportate, il loro utilizzo non è consigliato perché non ti consentono di ascoltare quando vengono emesse nuove voci. Inoltre, molte nuove API (ad esempio largest-contentful-paint
) non vengono esposte tramite l'oggetto performance
, ma solo tramite PerformanceObserver
.
A meno che tu non abbia bisogno specificamente della compatibilità con Internet Explorer, ti consigliamo di evitare questi metodi nel codice e di utilizzare PerformanceObserver
in futuro.
API User Timing
L'API User Timing è un'API di misurazione generica per le metriche basate sul tempo. Ti consente di contrassegnare arbitrariamente dei punti nel tempo e di misurare in un secondo momento la durata tra questi punti.
// Record the time immediately before running a task.
performance.mark('myTask:start');
await doMyTask();
// Record the time immediately after running a task.
performance.mark('myTask:end');
// Measure the delta between the start and end of the task
performance.measure('myTask', 'myTask:start', 'myTask:end');
Sebbene API come Date.now()
o performance.now()
offrano funzionalità simili, il vantaggio dell'utilizzo dell'API User Timing è che si integra bene con gli strumenti di misurazione del rendimento. Ad esempio, Chrome DevTools mostra le misurazioni dei tempi utente nel riquadro Rendimento. Inoltre, molti provider di analisi tengono automaticamente traccia di tutte le misurazioni effettuate e inviano i dati sulla durata al loro backend di analisi.
Per registrare le misurazioni di Tempo di utilizzo, puoi utilizzare PerformanceObserver e registrarti per osservare le voci di tipo measure
:
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `measure` entries to be dispatched.
po.observe({type: 'measure', buffered: true});
API Long Tasks
L'API Long Tasks è utile per sapere quando il thread principale del browser è bloccato per un periodo di tempo sufficiente a influire sulla frequenza dei fotogrammi o sulla latenza di input. L'API segnalerà tutte le attività eseguite per più di 50 millisecondi.
Ogni volta che devi eseguire codice costoso o caricare ed eseguire script di grandi dimensioni, è utile monitorare se il codice blocca il thread principale. Infatti, molte metriche di livello superiore sono basate sull'API Long Tasks stessa (ad esempio Time to Interactive (TTI) e Total Blocking Time (TBT)).
Per determinare quando si verificano attività lunghe, puoi utilizzare PerformanceObserver e registrarti per osservare le voci di tipo longtask
:
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `longtask` entries to be dispatched.
po.observe({type: 'longtask', buffered: true});
API Long Animation Frames
L'API Long Animation Frames è una nuova versione dell'API Long Tasks che esamina i frame lunghi, anziché le attività lunghe, di oltre 50 millisecondi. In questo modo vengono risolti alcuni mancamenti dell'API Long Tasks, tra cui un'attribuzione migliore e un ambito più ampio di ritardi potenzialmente problematici.
Per determinare quando si verificano frame lunghi, puoi utilizzare PerformanceObserver e registrarti per osservare le voci di tipo long-animation-frame
:
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `long-animation-frame` entries to be dispatched.
po.observe({type: 'long-animation-frame', buffered: true});
API Element Timing
La metrica Largest Contentful Paint (LCP) è utile per sapere quando l'immagine o il blocco di testo più grande è stato visualizzato sullo schermo, ma in alcuni casi è necessario misurare il tempo di rendering di un elemento diverso.
In questi casi, utilizza l'API Element Timing. L'API LCP si basa sull'API Element Timing e aggiunge report automatici sull'elemento con contenuti più grande, ma puoi anche generare report su altri elementi aggiungendo esplicitamente l'attributo elementtiming
e registrando un PerformanceObserver per osservare il tipo di voce element
.
<img elementtiming="hero-image" />
<p elementtiming="important-paragraph">This is text I care about.</p>
<!-- ... -->
<script>
const po = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `element` entries to be dispatched.
po.observe({type: 'element', buffered: true});
</script>
API Event Timing
La metrica Interaction to Next Paint (INP) valuta l'adattabilità complessiva della pagina osservando tutti i clic, i tocchi e le interazioni da tastiera durante la vita di una pagina. L'INP di una pagina è spesso l'interazione che ha richiesto più tempo per essere completata, dal momento in cui l'utente ha avviato l'interazione al momento in cui il browser ha visualizzato il frame successivo che mostra il risultato visivo dell'input dell'utente.
La metrica INP è resa possibile dall'API Event Timing. Questa API espone una serie di timestamp che si verificano durante il ciclo di vita degli eventi, tra cui:
startTime
: l'ora in cui il browser riceve l'evento.processingStart
: l'ora in cui il browser è in grado di iniziare a elaborare i gestori di eventi per l'evento.processingEnd
: il momento in cui il browser termina l'esecuzione di tutto il codice sincrono avviato dai gestori eventi per questo evento.duration
: il tempo (arrotondato a 8 millisecondi per motivi di sicurezza) che intercorre tra il momento in cui il browser riceve l'evento e il momento in cui è in grado di disegnare il frame successivo dopo aver completato l'esecuzione di tutto il codice sincrono avviato dai gestori di eventi.
L'esempio seguente mostra come utilizzare questi valori per creare misurazioni personalizzate:
const po = new PerformanceObserver((entryList) => {
// Get the last interaction observed:
const entries = Array.from(entryList.getEntries()).forEach((entry) => {
// Get various bits of interaction data:
const inputDelay = entry.processingStart - entry.startTime;
const processingTime = entry.processingEnd - entry.processingStart;
const presentationDelay = entry.startTime + entry.duration - entry.processingEnd;
const duration = entry.duration;
const eventType = entry.name;
const target = entry.target || "(not set)"
console.log("----- INTERACTION -----");
console.log(`Input delay (ms): ${inputDelay}`);
console.log(`Event handler processing time (ms): ${processingTime}`);
console.log(`Presentation delay (ms): ${presentationDelay}`);
console.log(`Total event duration (ms): ${duration}`);
console.log(`Event type: ${eventType}`);
console.log(target);
});
});
// A durationThreshold of 16ms is necessary to include more
// interactions, since the default is 104ms. The minimum
// durationThreshold is 16ms.
po.observe({type: 'event', buffered: true, durationThreshold: 16});
API Resource Timing
L'API Resource Timing offre agli sviluppatori informazioni dettagliate su come sono state caricate le risorse per una determinata pagina. Nonostante il nome dell'API, le informazioni che fornisce non si limitano ai dati sulle tempistiche (anche se ce ne sono innumerevoli). Altri dati a cui puoi accedere includono:
initiatorType
: la modalità di recupero della risorsa, ad esempio da un tag<script>
o<link>
o da una chiamatafetch()
.nextHopProtocol
: il protocollo utilizzato per recuperare la risorsa, ad esempioh2
oquic
.encodedBodySize
/decodedBodySize]: la dimensione della risorsa nel formato codificato o decodificato (rispettivamente)transferSize
: le dimensioni della risorsa che è stata effettivamente trasferita sulla rete. Quando le risorse vengono soddisfatte dalla cache, questo valore può essere molto inferiore aencodedBodySize
e, in alcuni casi, può essere pari a zero (se non è richiesta la convalida della cache).
Puoi utilizzare la proprietà transferSize
delle voci di temporizzazione delle risorse per misurare una metrica tasso di hit della cache o una metrica dimensioni totali delle risorse memorizzate nella cache, che può essere utile per capire in che modo la tua strategia di memorizzazione nella cache delle risorse influisce sul rendimento per i visitatori abituali.
L'esempio seguente registra tutte le risorse richieste dalla pagina e indica se ogni risorsa è stata soddisfatta dalla cache o meno.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log(entry.name, entry.transferSize === 0);
}
});
// Start listening for `resource` entries to be dispatched.
po.observe({type: 'resource', buffered: true});
API Navigation Timing
L'API Navigation Timing è simile all'API Resource Timing, ma segnala solo le richieste di navigazione. Anche il tipo di voce navigation
è simile al tipo di voce resource
, ma contiene alcune informazioni aggiuntive specifiche solo per le richieste di navigazione (ad esempio quando vengono attivati gli eventi DOMContentLoaded
e load
).
Una metrica monitorata da molti sviluppatori per comprendere il tempo di risposta del server (tempo per primo byte (TTFB)) è disponibile utilizzando l'API Navigation Timing, in particolare il timestamp responseStart
della voce.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log('Time to first byte', entry.responseStart);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});
Un'altra metrica che potrebbe interessare agli sviluppatori che utilizzano i service worker è il tempo di avvio del service worker per le richieste di navigazione. Si tratta del tempo necessario al browser per avviare il thread del servizio worker prima di poter iniziare a intercettare gli eventi di recupero.
Il tempo di avvio del service worker per una determinata richiesta di navigazione può essere determinato dal delta tra entry.responseStart
e entry.workerStart
.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Service Worker startup time:',
entry.responseStart - entry.workerStart);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});
API Server Timing
L'API Server Timing ti consente di passare i dati relativi ai tempi specifici della richiesta dal server al browser tramite le intestazioni di risposta. Ad esempio, puoi indicare il tempo necessario per cercare i dati in un database per una determinata richiesta, il che può essere utile per il debug dei problemi di prestazioni causati dalla lentezza del server.
Per gli sviluppatori che utilizzano provider di analisi di terze parti, l'API Server Timing è l'unico modo per correlare i dati sulle prestazioni del server con altre metriche aziendali che questi strumenti di analisi potrebbero misurare.
Per specificare i dati relativi ai tempi del server nelle risposte, puoi utilizzare l'intestazione di risposta Server-Timing
. Ecco un esempio.
HTTP/1.1 200 OK
Server-Timing: miss, db;dur=53, app;dur=47.2
Poi, dalle tue pagine, puoi leggere questi dati sia nelle voci resource
che navigation
delle API Resource Timing e Navigation Timing.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Logs all server timing data for this response
console.log('Server Timing', entry.serverTiming);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});