É muito importante ter métricas voltadas ao usuário que possam ser medidas de forma universal em qualquer site. Com essas métricas, você pode:
- Entenda como os usuários reais usam a Web como um todo.
- Compare seu site ao de um concorrente.
- Acompanhe dados úteis e acionáveis nas suas ferramentas de análise sem precisar escrever um código personalizado.
As métricas universais oferecem uma boa referência, mas, em muitos casos, é necessário medir mais do que apenas essas métricas para capturar a experiência completa do seu site.
Com as métricas personalizadas, você pode avaliar aspectos da experiência no site que podem ser aplicados apenas a ele, como:
- O tempo que um app de página única (SPA, na sigla em inglês) leva para fazer a transição de uma "página" para outra.
- O tempo que uma página leva para mostrar dados buscados de um banco de dados para usuários conectados.
- Quanto tempo leva para um app renderizado do lado do servidor (SSR) ser hidratado.
- A taxa de ocorrência em cache dos recursos carregados por visitantes recorrentes.
- A latência de eventos de clique ou de teclado em um jogo.
APIs para medir métricas personalizadas
Historicamente, os desenvolvedores da Web não tinham muitas APIs de baixo nível para medir a performance e, como resultado, precisavam recorrer a hacks para avaliar se um site estava com bom desempenho.
Por exemplo, é possível determinar se a linha de execução principal está bloqueada devido a tarefas de JavaScript de longa duração executando um loop requestAnimationFrame
e calculando o delta entre cada frame. Se o delta for significativamente maior que o frame rate da tela, você pode relatar isso como uma tarefa longa. No entanto, esses hacks não são recomendados, porque afetam a performance (por exemplo, esgotando a bateria).
A primeira regra da medição de desempenho eficaz é garantir que as técnicas de medição de desempenho não causem problemas de desempenho. Portanto, para qualquer métrica personalizada que você avaliar no seu site, é melhor usar uma das seguintes APIs, se possível.
API Performance Observer
A API Performance Observer é o mecanismo que coleta e exibe dados de todas as outras APIs de desempenho discutidas nesta página. É fundamental entender isso para conseguir dados bons.
Você pode usar PerformanceObserver
para se inscrever de forma passiva em eventos relacionados à performance. Isso permite que os callbacks da API sejam acionados durante períodos de inatividade, o que significa que eles geralmente não interferem no desempenho da página.
Para criar um PerformanceObserver
, transmita um callback para ser executado sempre que novas entradas de performance forem enviadas. Em seguida, informe ao observador quais tipos de entradas devem ser detectadas usando o método 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'});
As seções a seguir listam todos os tipos de entrada disponíveis para observação, mas, em navegadores mais recentes, você pode inspecionar quais tipos de entrada estão disponíveis usando a propriedade estática PerformanceObserver.supportedEntryTypes
.
Observar as entradas que já aconteceram
Por padrão, os objetos PerformanceObserver
só podem observar as entradas à medida que elas ocorrem. Isso pode causar problemas se você quiser carregar com atraso o código de análise de desempenho para que ele não bloqueie recursos de maior prioridade.
Para receber entradas históricas (depois que elas ocorrerem), defina a flag buffered
como true
ao chamar observe()
. O navegador vai incluir entradas históricas do buffer de entrada de performance na primeira vez que o callback PerformanceObserver
for chamado, até o tamanho máximo do buffer para esse tipo.
po.observe({
type: 'some-entry-type',
buffered: true,
});
APIs de desempenho legadas que devem ser evitadas
Antes da API Performance Observer, os desenvolvedores podiam acessar as entradas de performance usando os três métodos a seguir definidos no objeto performance
:
Embora essas APIs ainda sejam compatíveis, o uso delas não é recomendado, porque elas não permitem que você detecte quando novas entradas são emitidas. Além disso, muitas novas APIs (como largest-contentful-paint
) não são expostas pelo objeto performance
, apenas pelo PerformanceObserver
.
A menos que você precise especificamente de compatibilidade com o Internet Explorer, é melhor evitar esses métodos no seu código e usar PerformanceObserver
daqui para frente.
API User Timing
A API User Timing é uma API de medição de uso geral para métricas baseadas em tempo. Ele permite marcar pontos arbitrariamente no tempo e medir a duração entre essas marcas mais tarde.
// 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');
Embora APIs como Date.now()
ou performance.now()
ofereçam recursos semelhantes, o benefício de usar a API User Timing é que ela se integra bem às ferramentas de performance. Por exemplo, o Chrome DevTools mostra medições de tempo do usuário no painel "Performance". Além disso, muitos provedores de análise também rastreiam automaticamente todas as medições feitas e enviam os dados de duração para o back-end de análise.
Para informar as medições da User Timing, use a PerformanceObserver e registre-se para observar entradas do 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
A API Long Tasks é útil para saber quando a linha de execução principal do navegador está bloqueada por tempo suficiente para afetar o frame rate ou a latência de entrada. A API vai informar todas as tarefas que forem executadas por mais de 50 milissegundos.
Sempre que você precisar executar um código caro ou carregar e executar scripts grandes, é útil acompanhar se esse código bloqueia a linha de execução principal. Na verdade, muitas métricas de nível mais alto são criadas com base na própria API Long Tasks, como Tempo para interação (TTI) e Tempo total de bloqueio (TBT).
Para determinar quando tarefas longas acontecem, você pode usar o PerformanceObserver e fazer o registro para observar entradas do 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
A API Long Animation Frames é uma nova iteração da API Long Tasks que analisa frames longos, em vez de tarefas longas, com mais de 50 milissegundos. Isso resolve algumas falhas da API Long Tasks, incluindo uma atribuição aprimorada e um escopo mais amplo de atrasos potencialmente problemáticos.
Para determinar quando os frames longos acontecem, use PerformanceObserver e registre-se para observar entradas do 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
A métrica Maior exibição de conteúdo (LCP) é útil para saber quando a maior imagem ou bloco de texto foi pintado na tela, mas, em alguns casos, você quer medir o tempo de renderização de um elemento diferente.
Nesses casos, use a API Element Timing. A API LCP é construída com base na API Element Timing e adiciona relatórios automáticos do maior elemento com conteúdo. No entanto, você também pode gerar relatórios sobre outros elementos adicionando explicitamente o atributo elementtiming
a eles e registrando um PerformanceObserver para observar o tipo de entrada 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
A métrica Interaction to Next Paint (INP) avalia a capacidade de resposta geral da página observando todas as interações de clique, toque e teclado durante a vida útil de uma página. O INP de uma página geralmente é a interação que levou mais tempo para ser concluída, do momento em que o usuário iniciou a interação até o momento em que o navegador exibe o frame seguinte, mostrando o resultado visual da entrada do usuário.
A métrica INP é possível graças à API Event Timing. Essa API expõe vários carimbos de data/hora que ocorrem durante o ciclo de vida do evento, incluindo:
startTime
: o momento em que o navegador recebe o evento.processingStart
: o momento em que o navegador pode começar a processar os processadores de eventos para o evento.processingEnd
: momento em que o navegador termina de executar todo o código síncrono iniciado pelos manipuladores de eventos para esse evento.duration
: o tempo (arredondado para 8 milissegundos por motivos de segurança) entre o recebimento do evento pelo navegador e a exibição do próximo frame depois de terminar a execução de todo o código síncrono iniciado nos manipuladores de eventos.
O exemplo a seguir mostra como usar esses valores para criar medições personalizadas:
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
A API Resource Timing oferece aos desenvolvedores informações detalhadas sobre como os recursos de uma página específica foram carregados. Apesar do nome da API, as informações fornecidas não se limitam a dados de tempo, embora haja muitas delas. Outros dados que você pode acessar incluem:
initiatorType
: como o recurso foi buscado, por exemplo, de uma tag<script>
ou<link>
ou de uma chamadafetch()
.nextHopProtocol
: o protocolo usado para buscar o recurso, comoh2
ouquic
.encodedBodySize
/decodedBodySize]: o tamanho do recurso na forma codificada ou decodificada (respectivamente)transferSize
: o tamanho do recurso que foi transferido pela rede. Quando os recursos são atendidos pelo cache, esse valor pode ser muito menor que oencodedBodySize
e, em alguns casos, pode ser zero (se nenhuma revalidação de cache for necessária).
É possível usar a propriedade transferSize
das entradas de tempo de recurso para medir uma métrica de taxa de ocorrências em cache ou de tamanho total de recursos em cache, o que pode ser útil para entender como sua estratégia de armazenamento em cache afeta a performance de visitantes recorrentes.
O exemplo a seguir registra todos os recursos solicitados pela página e indica se cada recurso foi atendido ou não pelo cache.
// 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
A API Navigation Timing é semelhante à API Resource Timing, mas informa apenas solicitações de navegação. O tipo de entrada navigation
também é semelhante ao tipo de entrada resource
, mas contém algumas informações específicas apenas para solicitações de navegação, como quando os eventos DOMContentLoaded
e load
são disparados.
Uma métrica que muitos desenvolvedores acompanham para entender o tempo de resposta do servidor (Tempo até o primeiro byte (TTFB)) está disponível usando a API Navigation Timing, especificamente o carimbo de data/hora responseStart
da entrada.
// 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});
Outra métrica que pode ser importante para os desenvolvedores que usam service workers é o tempo de inicialização do service worker para solicitações de navegação. Esse é o tempo que o navegador leva para iniciar a linha de execução do service worker antes de interceptar eventos de busca.
O tempo de inicialização do service worker para uma solicitação de navegação específica pode ser determinado com base no delta entre 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
Com a API Server Timing, você pode transmitir dados de tempo específicos da solicitação do seu servidor para o navegador usando cabeçalhos de resposta. Por exemplo, é possível indicar quanto tempo levou para consultar dados em um banco de dados para uma solicitação específica, o que pode ser útil na depuração de problemas de desempenho causados pela lentidão no servidor.
Para desenvolvedores que usam provedores de análise de terceiros, a API Server Timing é a única maneira de correlacionar dados de desempenho do servidor com outras métricas de negócios que essas ferramentas de análise podem medir.
Para especificar dados de tempo do servidor nas respostas, use o cabeçalho de resposta Server-Timing
. Veja um exemplo.
HTTP/1.1 200 OK
Server-Timing: miss, db;dur=53, app;dur=47.2
Em seguida, nas suas páginas, é possível ler esses dados nas entradas resource
ou navigation
das APIs 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});