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

Os testes sintéticos não são inerentemente ruins, mas não representam a velocidade de carregamento do site para usuários reais. Isso requer dados de campo, que podem ser coletados das 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 Tempo de navegação mede a velocidade das solicitações de documentos HTML, ou seja, as 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 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 tempos das APIs Navigation Timing e Resource Timing, respectivamente.

A quantidade de informações fornecidas por essas APIs pode ser esmagadora, mas elas são essenciais para medir o desempenho de carregamento no campo, já que você pode coletar esses tempos dos usuários enquanto eles visitam seu site.

A vida útil e os tempos de uma solicitação de rede

Reunir e analisar os tempos de navegação e recursos é meio que a arqueologia, em que você está reconstruindo a vida efêmera de uma solicitação de rede após o fato. Às vezes, é mais fácil visualizar conceitos. No que diz respeito às solicitações de rede, as ferramentas para desenvolvedores do seu navegador podem ajudar.

Tempos de rede, conforme mostrado no Chrome DevTools. Os tempos descritos são para enfileiramento de solicitações, negociação de conexão, a própria solicitação e a resposta em barras coloridas.
Uma 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 converter um domínio em um endereço IP. Esse processo pode levar um tempo significativo — tempo que convém medir em campo, mesmo. O Navigation Timing e o Resource Timing exibem dois tempos relacionados ao DNS:

  • domainLookupStart é quando a pesquisa DNS começa.
  • domainLookupEnd é o encerramento da busca DNS.

É possível calcular o tempo total da busca DNS subtraindo 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 da 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 houver HTTPS 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 do TLS.
  • connectEnd é quando a conexão com o servidor da Web foi estabelecida.

Medir o tempo total de conexão é semelhante a medir o tempo total de pesquisa DNS: subtraia o tempo de início do tempo de término. No entanto, há uma propriedade secureConnectionStart adicional que pode ser 0 se o 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 externos:latência e largura de banda. Além de escolher uma empresa de hospedagem e possivelmente uma CDN, elas estão (na maioria dos casos) 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 o Navigation Timing quanto o 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 recursos) 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 está verificando os caches (por exemplo, instâncias HTTP e Cache).
  • workerStart marca quando uma solicitação começa a ser processada no manipulador de eventos fetch de um service worker. Será 0 quando nenhum service worker estiver controlando a página atual.
  • requestStart é quando o navegador faz a solicitação.
  • responseStart é quando chega o primeiro byte da resposta.
  • responseEnd é quando o último byte da resposta chega.

Esses tempos permitem medir vários aspectos do desempenho do 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ção e resposta:

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 medidas 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 podem valer a pena explorar:

  • 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 de HTTP para HTTPs e redirecionamentos 302 302/não armazenados em cache. Os tempos redirectStart, redirectEnd e redirectCount são úteis para avaliar a latência do redirecionamento.
  • Descarregamento de documentos:em 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 do documento pode não ser consequencial, a menos que seu site envie payloads HTML muito grandes. Se essa for sua situação, os tempos domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd e domComplete podem ser interessantes.

Como receber marcações de tempo 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 leve é necessária. Em outras situações, no entanto, elas podem introduzir trabalho excessivo da linha de execução principal, iterando em um grande número de entradas ou até mesmo pesquisando repetidamente o buffer de desempenho para encontrar novas entradas.

A abordagem recomendada para coletar entradas do buffer de entrada de desempenho é usar um PerformanceObserver. PerformanceObserver detecta entradas de desempenho e as fornece à medida que 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 tempo pode parecer estranho quando comparado ao acesso direto ao buffer de entrada de desempenho, mas é preferível amarrar a linha de execução principal com um trabalho que não sirva a um propósito crítico e voltado para o 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. Duas maneiras de fazer isso são com navigator.sendBeacon ou 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, você decide como analisar os dados desse 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 nenhum usuário e podem ser distorcidas por outliers.
  • 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 dos 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 completo sobre Navigation ou Resource Timing, mas sim 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.