Como avaliar o desempenho de carregamento no campo com a Navigation Timing e a Resource Timing

Aprenda os conceitos básicos de uso das APIs Navigation Timing e Resource Timing para avaliar o desempenho de carregamento no campo.

Publicado em 8 de outubro de 2021

Se você usou a limitação de conexão no painel de rede das ferramentas para desenvolvedores de um navegador (ou o Lighthouse no Chrome) para avaliar o desempenho de carregamento, sabe como essas ferramentas são úteis para o ajuste de desempenho. É possível medir rapidamente o impacto das otimizações de desempenho com uma velocidade de conexão de referência consistente e estável. O único problema é que este é um teste sintético, que gera dados de laboratório, não dados de campo.

O teste sintético não é ruim por natureza, mas não representa a velocidade de carregamento do site para usuários reais. Isso requer dados de campo, que podem ser coletados nas APIs Navigation Timing e Resource Timing.

APIs para ajudar você a avaliar o desempenho de carregamento em campo

Navigation Timing e Resource Timing são duas APIs semelhantes com sobreposição significativa que medem duas coisas distintas:

  • O Navigation Timing mede a velocidade das solicitações de documentos HTML (ou seja, solicitações de navegação).
  • O cronômetro de recursos mede a velocidade das solicitações de recursos dependentes de documentos, como CSS, JavaScript, imagens e outros tipos de recursos.

Essas APIs expõem os dados em um buffer de entrada de performance, que pode ser acessado no navegador com JavaScript. Há várias maneiras de consultar um buffer de desempenho, mas uma maneira comum é usar performance.getEntriesByType:

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

performance.getEntriesByType aceita uma string que descreve o tipo de entradas que você quer recuperar do buffer de entrada de performance. 'navigation' e 'resource' recuperam os tempos das APIs Navigation Timing e Resource Timing, respectivamente.

A quantidade de informações que essas APIs fornecem pode ser desgastante, mas elas são a chave para medir o desempenho de carregamento em campo, já que você pode coletar esses tempos dos usuários à medida que eles visitam seu site.

A vida útil e os horários de uma solicitação de rede

Reunir e analisar os tempos de navegação e recursos é como a arqueologia, em que você está reconstruindo a vida efêmera de uma solicitação de rede após o fato. Às vezes, é útil visualizar conceitos, e, quando se trata de solicitações de rede, as ferramentas para desenvolvedores do navegador podem ajudar.

Tempos de rede, conforme mostrado no Chrome DevTools. Os tempos mostrados são para enfileirar solicitações, negociar conexões, a própria solicitação e a resposta em barras codificadas por cores.
Visualização de uma solicitação de rede no painel de rede do Chrome DevTools

A vida útil de uma solicitação de rede tem fases distintas, como pesquisa DNS, estabelecimento de conexão, negociação TLS e outras fontes de latência. Esses tempos são representados como um DOMHighResTimestamp. Dependendo do navegador, a granularidade dos tempos pode ser de microssegundos ou arredondados para milissegundos. Você deve analisar essas fases em detalhes e como elas se relacionam com Navigation Timing e Resource Timing.

busca DNS

Quando um usuário acessa um URL, o Sistema de Nomes de Domínio (DNS) é consultado para traduzir um domínio em um endereço IP. Esse processo pode levar muito tempo, e você vai querer medir o tempo no campo. A Navigation Timing e a Resource Timing expõem dois tempos relacionados ao DNS:

  • domainLookupStart é quando a pesquisa DNS começa.
  • domainLookupEnd é quando a pesquisa DNS termina.

Para calcular o tempo total de pesquisa DNS, subtraia a métrica inicial da métrica final:

// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;

Negociação de conexão

Outro fator que contribui para o desempenho de carregamento é a negociação de conexão, que é a latência gerada ao se conectar a um servidor da Web. Se o HTTPS estiver envolvido, esse processo também incluirá o tempo de negociação do TLS. A fase de conexão consiste em três tempos:

  • connectStart é quando o navegador começa a abrir uma conexão com um servidor da Web.
  • secureConnectionStart marca quando o cliente inicia a negociação TLS.
  • connectEnd é quando a conexão com o servidor da Web foi estabelecida.

A medição do tempo total de conexão é semelhante à medição do tempo total de busca DNS: você subtrai o horário de início do horário de término. No entanto, há uma propriedade secureConnectionStart extra que pode ser 0 se HTTPS não for usado ou se a conexão for persistente. Se você quiser medir o tempo de negociação do TLS, considere o seguinte:

// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}

Quando a pesquisa DNS e a negociação de conexão terminam, os tempos relacionados à busca de documentos e dos recursos dependentes deles entram em ação.

Solicitações e respostas

O desempenho de carregamento é afetado por dois tipos de fatores:

  • Fatores extrínsecos:são coisas como latência e largura de banda. Além de escolher uma empresa de hospedagem e possivelmente um CDN, eles estão (na maioria das vezes) fora do nosso controle, já que os usuários podem acessar a Web de qualquer lugar.
  • Fatores intrínsecos:são itens como arquiteturas do servidor e do cliente, bem como o tamanho dos recursos e nossa capacidade de otimizar para esses elementos, que estão sob nosso controle.

Ambos os tipos de fatores afetam o desempenho de carregamento. Os tempos relacionados a esses fatores são vitais, porque descrevem quanto tempo leva para o download dos recursos. Tanto a Navigation Timing quanto a Resource Timing descrevem o desempenho de carregamento com as seguintes métricas:

  • fetchStart marca quando o navegador começa a buscar um recurso (Tempo de recurso) ou um documento para uma solicitação de navegação (Tempo de navegação). Isso precede a solicitação real e é o ponto em que o navegador verifica os caches (por exemplo, instâncias HTTP e Cache).
  • workerStart marca quando uma solicitação começa a ser processada no gerenciador de eventos fetch de um service worker. Ele será 0 quando nenhum service worker estiver controlando a página atual.
  • requestStart é quando o navegador faz a solicitação.
  • responseStart é quando o primeiro byte da resposta chega.
  • responseEnd é quando o último byte da resposta chega.

Esses tempos permitem medir vários aspectos do desempenho de carregamento, como a pesquisa de cache em um service worker e o tempo de download:

// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}

Também é possível medir outros aspectos da latência de solicitações e respostas:

const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;

Outras medições que você pode fazer

Os tempos de navegação e de recursos são úteis para mais do que os exemplos anteriores descrevem. Confira outras situações com horários relevantes que vale a pena analisar:

  • Redirecionamentos de página:os redirecionamentos são uma fonte negligenciada de latência adicional, especialmente cadeias de redirecionamento. A latência é adicionada de várias maneiras, como saltos HTTP para HTTPs e redirecionamentos 302/301 sem cache. Os tempos redirectStart, redirectEnd e redirectCount são úteis para avaliar a latência do redirecionamento.
  • Desmontagem de documentos:nas páginas que executam o código em um manipulador de eventos unload, o navegador precisa executar esse código antes de navegar para a próxima página. unloadEventStart e unloadEventEnd medem o descarregamento de documentos.
  • Processamento de documentos:o tempo de processamento de documentos pode não ser significativo, a menos que seu site envie payloads HTML muito grandes. Se isso descreve sua situação, os tempos domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd e domComplete podem ser úteis.

Como receber tempos no código

Todos os exemplos mostrados até agora usam performance.getEntriesByType, mas há outras maneiras de consultar o buffer de entrada de desempenho, como performance.getEntriesByName e performance.getEntries. Esses métodos são adequados quando apenas uma análise simples é necessária. No entanto, em outras situações, elas podem introduzir trabalho excessivo na linha de execução principal, iterando sobre um grande número de entradas ou até mesmo consultando repetidamente o buffer de desempenho para encontrar novas entradas.

A abordagem recomendada para coletar entradas do buffer de entrada de desempenho é usar um PerformanceObserver. O PerformanceObserver detecta entradas de performance e as fornece conforme elas são adicionadas ao buffer:

// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});

Esse método de coleta de tempos pode parecer estranho quando comparado ao acesso direto ao buffer de entrada de desempenho, mas é preferível vincular a linha de execução principal a um trabalho que não tenha uma finalidade crítica e voltada ao usuário.

Como ligar para casa

Depois de coletar todos os tempos necessários, você pode enviá-los a um endpoint para análise posterior. Há duas maneiras de fazer isso: com navigator.sendBeacon ou com um fetch com a opção keepalive definida. Ambos os métodos enviam uma solicitação para um endpoint especificado de forma não bloqueante, e a solicitação é enfileirada de modo a sobreviver à sessão de página atual, se necessário:

// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
  // Caution: If you have lots of performance entries, don't
  // do this. This is an example for illustrative purposes.
  const data = JSON.stringify(performance.getEntries());

  // Send the data!
  navigator.sendBeacon('/analytics', data);
}

Neste exemplo, a string JSON chega em um payload POST que pode ser decodificado, processado e armazenado em um back-end de aplicativo conforme necessário.

Conclusão

Depois de coletar as métricas, cabe a você descobrir como analisar esses dados de campo. Ao analisar dados de campo, há algumas regras gerais a seguir para garantir que você esteja tirando conclusões significativas:

  • Evite médias, porque elas não representam a experiência de um usuário e podem ser distorcidas por valores atípicos.
  • Use percentis. Nos conjuntos de dados de métricas de desempenho baseadas no tempo, quanto menor, melhor. Isso significa que, ao priorizar percentis baixos, você só presta atenção às experiências mais rápidas.
  • Priorize a cauda longa de valores. Ao priorizar experiências no 75o percentil ou superior, você está colocando seu foco onde ele pertence: nas experiências mais lentas.

Este guia não é um recurso exaustivo sobre navegação ou tempo de carregamento de recursos, mas um ponto de partida. Confira outros recursos que podem ser úteis:

Com essas APIs e os dados que elas fornecem, você terá mais recursos para entender como a performance de carregamento é percebida por usuários reais, o que vai dar mais confiança para diagnosticar e resolver problemas de performance de carregamento no campo.