É muito útil ter métricas centradas no usuário que podem ser medidas universalmente em qualquer site. Com essas métricas, é possível:
- Entenda como os usuários reais vivenciam a Web como um todo.
- Compare seu site com o de um concorrente.
- Acompanhe dados úteis e práticos nas suas ferramentas de análise sem precisar escrever código personalizado.
As métricas universais oferecem um bom valor de 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 específico.
Com as métricas personalizadas, você pode medir aspectos da experiência do seu site que só se aplicam a ele, como:
- Quanto tempo leva para um aplicativo de página única (SPA) fazer a transição de uma "página" para outra.
- Quanto tempo leva para uma página mostrar dados extraídos 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 para recursos carregados por visitantes recorrentes.
- A latência de eventos de clique ou 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. Por isso, eles precisavam recorrer a hacks para medir se um site estava funcionando bem.
Por exemplo, é possível determinar se a linha de execução principal está bloqueada devido a tarefas JavaScript de longa duração executando um loop requestAnimationFrame e calculando o delta entre cada frame. Se o delta for significativamente maior que a taxa de frames da tela, você poderá informar isso como uma tarefa longa. No entanto, não recomendamos esses hacks porque eles afetam o desempenho (por exemplo, descarregando a bateria).
A primeira regra da medição de performance eficaz é garantir que as técnicas usadas não estejam causando problemas de performance. Portanto, para qualquer métrica personalizada que você medir no seu site, é melhor usar uma das seguintes APIs, se possível.
API Performance Observer
A API Performance Observer é o mecanismo que coleta e mostra dados de todas as outras APIs de performance discutidas nesta página. Entender isso é fundamental para ter bons dados.
Você pode usar PerformanceObserver para se inscrever passivamente em eventos relacionados à performance. Isso permite que os callbacks da API sejam disparados durante períodos ociosos, o que significa que eles geralmente não interferem no desempenho da página.
Para criar um PerformanceObserver, transmita um callback a ser executado sempre que novas entradas de performance forem enviadas. Em seguida, informe ao observador quais tipos de entradas ele precisa detectar 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, é possível inspecionar quais tipos de entrada estão disponíveis usando a propriedade estática PerformanceObserver.supportedEntryTypes.
Observar entradas que já aconteceram
Por padrão, os objetos PerformanceObserver só podem observar entradas à medida que elas ocorrem. Isso pode causar problemas se você quiser carregar o código de análise de desempenho de forma lenta 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 entradas 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 performance legadas que devem ser evitadas
Antes da API Performance Observer, os desenvolvedores podiam acessar 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 não permitem que você ouça 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 no tempo de forma arbitrária 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 desempenho. Por exemplo, o Chrome DevTools visualiza medições de tempo do usuário no painel de performance, e muitos provedores de análise também rastreiam automaticamente as medições feitas e enviam os dados de duração para o back-end de análise.
Para gerar relatórios sobre medições de User Timing, use 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 fica bloqueada por tempo suficiente para afetar a taxa de frames 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 superior são criadas com base na API Long Tasks (como Tempo até a interatividade (TTI) e Tempo total de bloqueio (TBT)).
Para determinar quando tarefas longas acontecem, use PerformanceObserver e registre-se 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, de mais de 50 milissegundos. Isso resolve algumas deficiências da API Long Tasks, incluindo uma atribuição melhor e um escopo mais amplo de atrasos potencialmente problemáticos.
Para determinar quando 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 exibido na tela, mas, em alguns casos, você quer medir o tempo de renderização de um elemento diferente.
Para esses casos, use a API Element Timing. A API LCP é criada com base na API Element Timing e adiciona relatórios automáticos do maior elemento de 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, desde o momento em que o usuário iniciou a interação até o momento em que o navegador renderiza o próximo frame 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 manipuladores 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 momento em que o navegador recebe o evento e o momento em que ele consegue renderizar o próximo frame após terminar de executar todo o código síncrono iniciado pelos manipuladores de eventos.
O exemplo a seguir mostra como usar esses valores para criar métricas 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 insights detalhados sobre como os recursos de uma página específica foram carregados. Apesar do nome da API, as informações que ela fornece não se limitam apenas a dados de tempo (embora haja muitos deles). 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, comoh2ouquic.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 do que oencodedBodySizee, em alguns casos, pode ser zero (se nenhuma revalidação de cache for necessária).
Você pode usar a propriedade transferSize das entradas de tempo de recurso para medir uma métrica de taxa de ocorrência em cache ou de tamanho total do recurso em cache, o que pode ser útil para entender como sua estratégia de cache de recursos afeta o desempenho para visitantes recorrentes.
O exemplo a seguir registra todos os recursos solicitados pela página e indica se cada recurso foi atendido 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 resource, mas contém algumas informações adicionais específicas apenas para solicitações de navegação (como quando os eventos DOMContentLoaded e load são acionados).
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 importante para desenvolvedores que usam service worker é o tempo de inicialização do service worker para solicitações de navegação. É o tempo que o navegador leva para iniciar a linha de execução do service worker antes de começar a interceptar eventos de busca.
O tempo de inicialização do service worker para uma solicitação de navegação específica pode ser determinado pelo 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
A API Server Timing permite transmitir dados de tempo específicos da solicitação do servidor para o navegador usando cabeçalhos de resposta. Por exemplo, é possível indicar quanto tempo levou para pesquisar dados em um banco de dados para uma solicitação específica, o que pode ser útil para depurar problemas de desempenho causados por 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 estar medindo.
Para especificar dados de tempo do servidor nas suas 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});