Métricas personalizadas

É 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 com o de um concorrente.
  • Acompanhe dados úteis e acionáveis nas suas ferramentas de análise sem precisar escrever códigos personalizados.

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 medir aspectos da experiência do seu site que se aplicam 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 a taxa de quadros da tela, você poderá informar 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ê medir no seu site, é melhor usar uma das APIs a seguir, se possível.

API Performance Observer

Compatibilidade com navegadores

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 57.
  • Safari: 11.

Origem

A API Performance Observer é o mecanismo que coleta e exibe dados de todas as outras APIs de performance 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, é possível 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 conforme 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 a serem 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 código e usar PerformanceObserver daqui em diante.

API User Timing

Compatibilidade com navegadores

  • Chrome: 28.
  • Edge: 12.
  • Firefox: 38.
  • Safari: 11.

Origem

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 que você faz 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

Compatibilidade com navegadores

  • Chrome: 58.
  • Edge: 79.
  • Firefox: não é compatível.
  • Safari: não é compatível.

Origem

A API Long Tasks é útil para saber quando a linha de execução principal do navegador está 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 mais alto são criadas com base na API Long Tasks, como Tempo para interação (TTI) e Tempo total de bloqueio (TBT).

Para determinar quando as 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

Compatibilidade com navegadores

  • Chrome: 123.
  • Edge: 123.
  • Firefox: não é compatível.
  • Safari: não é compatível.

Origem

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 falhas da API Long Tasks, incluindo uma atribuição melhor 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

Compatibilidade com navegadores

  • Chrome: 77.
  • Edge: 79.
  • Firefox: não é compatível.
  • Safari: não é compatível.

Origem

A métrica Maior exibição de conteúdo (LCP, na sigla em inglês) é útil para saber quando a maior imagem ou bloco de texto foi exibido na tela. No entanto, 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 é 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

Compatibilidade com navegadores

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 89.
  • Safari: não é compatível.

Origem

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. A INP de uma página é, na maioria das vezes, a interação que demorou mais para ser concluída, desde o momento em que o usuário iniciou a interação até o momento em que o navegador pintou 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 os processadores de eventos para o evento.
  • processingEnd: momento em que o navegador termina de executar todo o código síncrono iniciado por 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 pode pintar o próximo frame depois de 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 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

Compatibilidade com navegadores

  • Chrome: 29.
  • Edge: 12.
  • Firefox: 35.
  • Safari: 11.

Origem

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 chamada fetch().
  • nextHopProtocol: o protocolo usado para buscar o recurso, como h2 ou quic.
  • 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 o encodedBodySize 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 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});

Compatibilidade com navegadores

  • Chrome: 57.
  • Edge: 12.
  • Firefox: 58.
  • Safari: 15.

Origem

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 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 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

Compatibilidade com navegadores

  • Chrome: 65.
  • Edge: 79.
  • Firefox: 61.
  • Safari: 16.4.

Origem

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 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});