Métricas personalizadas

Ter métricas universais e centradas no usuário que podem ser avaliadas em qualquer site pode ser muito útil para entender a experiência dos usuários na Web e comparar seu site com o dos concorrentes. No entanto, em muitos casos, você precisa medir mais do que apenas as métricas universais para capturar a experiência completa do seu site específico.

Com as métricas personalizadas, você pode avaliar aspectos da experiência que talvez se apliquem somente a seu site, como:

  • Quanto tempo leva para um app de página única (SPA) fazer a transição de uma "página" para outra.
  • Quanto tempo leva para uma página exibir dados buscados em um banco de dados para usuários conectados.
  • Quanto tempo leva para um app renderizado do lado do servidor (SSR, na sigla em inglês) se hidratar.
  • 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 o desempenho e, como resultado, precisaram recorrer a invasões para avaliar se um site teve um bom desempenho. Por exemplo, você pode determinar se a linha de execução principal está bloqueada por tarefas JavaScript de longa duração executando uma repetição requestAnimationFrame e calculando o delta entre cada frame. Se o delta for significativamente maior que o frame rate da tela, você poderá relatar isso como uma tarefa longa.

No entanto, hacks como essa podem afetar o desempenho do site, por exemplo, drenando a bateria do dispositivo. Se as suas técnicas de medição de performance causarem problemas de performance, os dados que você receber não serão precisos. Portanto, recomendamos o uso de uma das seguintes APIs para criar métricas personalizadas.

API Performance Observer

Compatibilidade com navegadores

  • 52
  • 79
  • 57
  • 11

Origem

A API Performance Observer é o mecanismo que coleta e mostra dados de todas as outras APIs de desempenho discutidas nesta página. Compreender isso é fundamental para ter bons dados.

Você pode usar PerformanceObserver para se inscrever passivamente em eventos relacionados ao desempenho. 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.

Ao criar um PerformanceObserver, transmita um callback que é executado sempre que novas entradas de desempenho forem enviadas. Em seguida, use o método observe() para informar ao observador quais tipos de entradas detectar, da seguinte maneira:

// Catch errors that some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  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'});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

As seções abaixo listam todos os tipos de entrada que você pode observar. Em navegadores mais recentes, também é possível inspecionar os tipos de entrada 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 ocorrem. Isso poderá causar problemas se você quiser carregar lentamente o código de análise de desempenho para que ele não bloqueie recursos de prioridade mais alta.

Para receber entradas históricas, chame observe com a sinalização buffered definida como true. O navegador vai incluir entradas históricas do buffer de entrada de desempenho na primeira vez que o callback PerformanceObserver for chamado.

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 entradas de desempenho usando os métodos abaixo, definidos no objeto performance. Não recomendamos o uso deles, porque eles não permitem que você ouça novas entradas.

Além disso, muitas APIs novas, como tarefas longas, não são expostas pelo objeto performance, apenas por PerformanceObserver. Portanto, a menos que você precise especificamente de compatibilidade com o Internet Explorer, é melhor evitar esses métodos no 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 com base em tempo. Ele permite marcar arbitrariamente pontos no tempo e, depois, medir a duração entre essas marcas.

// 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, a API User Timing é melhor integrada às ferramentas de performance. Por exemplo, o Chrome DevTools visualiza as medições de velocidade do usuário no painel "Desempenho", e muitos provedores de análise rastreiam automaticamente qualquer medição feita e enviam os dados de duração para o back-end de análise.

Para informar medições da velocidade do usuário, registre um PerformanceObserver para observar entradas do tipo measure:

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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.
  po.observe({type: 'measure', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

API Long Tasks

Compatibilidade com navegadores

  • 58
  • 79
  • x
  • x

Origem

A API Long Tasks é útil para determinar 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 informa todas as tarefas executadas por mais de 50 milissegundos (ms).

Sempre que você precisa executar códigos caros 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 para interação da página (TTI, na sigla em inglês) e Tempo total de bloqueio (TBT, na sigla em inglês).

Para determinar quando tarefas longas acontecem, registre um PerformanceObserver para observar entradas do tipo longtask:

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

API Element Timing

Compatibilidade com navegadores

  • 77
  • 79
  • x
  • x

Origem

A métrica de Maior exibição de conteúdo (LCP, na sigla em inglês) é útil para saber quando a maior imagem ou bloco de texto da página é mostrado na tela. No entanto, em alguns casos, você quer medir o tempo de renderização de um elemento diferente.

Nesses casos, use a API Element Timing. Na verdade, a API LCP foi criada com base na API Element Timing e adiciona relatórios automáticos do maior elemento de conteúdo. Também é possível 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>
// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}
</script>

API Event Timing

A métrica Latência na primeira entrada (FID, na sigla em inglês) mede o tempo entre o momento em que um usuário interage com a página pela primeira vez e o momento em que o navegador pode começar a processar manipuladores de eventos em resposta a essa interação. No entanto, em alguns casos, também pode ser útil medir o próprio tempo de processamento do evento.

Isso é possível usando a API Event Timing, que, além de medir a FID, também expõe vários carimbos de data/hora no ciclo de vida do evento, incluindo:

  • startTime: o horário 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: o 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 ms por motivos de segurança) entre o momento em que o navegador recebe o evento e até conseguir exibir o próximo frame depois de concluir a execução de todo o código síncrono iniciado pelos manipuladores de eventos.

O exemplo a seguir mostra como usar esses valores para criar medidas personalizadas:

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  const po = new PerformanceObserver((entryList) => {
    const firstInput = entryList.getEntries()[0];

    // Measure First Input Delay (FID).
    const firstInputDelay = firstInput.processingStart - firstInput.startTime;

    // Measure the time it takes to run all event handlers
    // Doesn't include work scheduled asynchronously using methods like
    // `requestAnimationFrame()` or `setTimeout()`.
    const firstInputProcessingTime = firstInput.processingEnd - firstInput.processingStart;

    // Measure the entire duration of the event, from when input is received by
    // the browser until the next frame can be painted after processing all
    // event handlers.
    // Doesn't include work scheduled asynchronously using
    // `requestAnimationFrame()` or `setTimeout()`.
    // For security reasons, this value is rounded to the nearest 8 ms.
    const firstInputDuration = firstInput.duration;

    // Log these values to the console.
    console.log({
      firstInputDelay,
      firstInputProcessingTime,
      firstInputDuration,
    });
  });

  po.observe({type: 'first-input', buffered: true});
} catch (error) {
  // Do nothing if the browser doesn't support this API.
}

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 fornecidas não se limitam apenas a dados de tempo, embora haja muitas dessas. Outros dados que você pode acessar incluem:

  • initiatorType: como o recurso foi buscado, por exemplo, de uma tag <script> ou <link> ou de fetch().
  • nextHopProtocol: o protocolo usado para buscar o recurso, como h2 ou quic.
  • encodedBodySize e decodedBodySize]: o tamanho do recurso na forma codificada ou decodificada (respectivamente).
  • transferSize: o tamanho do recurso que foi realmente transferido pela rede. Quando os recursos são preenchidos usando o cache, esse valor pode ser muito menor que o encodedBodySize e, em alguns casos, pode ser zero, se nenhuma revalidação do cache for necessária.

Use a propriedade transferSize das entradas de velocidade de recursos para medir uma métrica de taxa de ocorrência em cache ou uma métrica de tamanho total de recurso em cache, o que pode ser útil para entender como sua estratégia de armazenamento em 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 preenchido ou não usando o cache:

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // If transferSize is 0, the resource was fulfilled via the cache.
      console.log(entry.name, entry.transferSize === 0);
    }
  });
  // Start listening for `resource` entries to be dispatched.
  po.observe({type: 'resource', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Compatibilidade com navegadores

  • 57
  • 12
  • 58
  • 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 de entrada 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 disparados.

Uma métrica que muitos desenvolvedores rastreiam para entender o tempo de resposta do servidor, Tempo para primeiro byte (TTFB, na sigla em inglês), está disponível no carimbo de data/hora responseStart na API Navigation Timing.

// Catch errors since  browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Outro aspecto importante para os desenvolvedores de métricas 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 começar a interceptar eventos de busca.

O tempo de inicialização do service worker para uma solicitação de navegação especificada pode ser determinado pelo delta entre entry.responseStart e entry.workerStart da seguinte maneira:

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

API Server Timing

A API Server Timing permite a transmissão de 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 pela lentidão no servidor.

Para desenvolvedores que usam provedores de análise terceirizados, a API Server Timing é a única maneira de correlacionar os dados de desempenho do servidor com outras métricas de negócios que essas ferramentas de análise avaliam.

Para especificar os dados de velocidade 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, leia esses dados nas entradas resource ou navigation das APIs Resource Timing e Navigation Timing.

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}