Métricas personalizadas

É muito vantajoso ter métricas centradas no usuário que você possa medir de maneira universal em qualquer site. Com essas métricas, é possível:

  • Compreenda a experiência de usuários reais na Web como um todo.
  • Compare seu site ao de um concorrente.
  • Acompanhe dados úteis e acionáveis nas suas ferramentas de análise sem precisar escrever um código personalizado.

As métricas universais oferecem uma boa linha de base, mas, em muitos casos, você precisa avaliar mais do que apenas essas métricas para capturar a experiência completa no seu site específico.

Com as métricas personalizadas, você pode avaliar aspectos da experiência no site que podem ser aplicados apenas 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 exibir dados buscados em um banco de dados para usuários conectados.
  • Quanto tempo leva para um app renderizado pelo servidor (SSR, na sigla em inglês) hidratar.
  • A taxa de ocorrência em cache para 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 Web não tinham muitas APIs de baixo nível para avaliar o desempenho e, por isso, tiveram que recorrer a invasões para avaliar se um site estava com boa performance.

Por exemplo, é possível determinar se a linha de execução principal está bloqueada devido a 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ê pode relatar isso como uma tarefa longa. No entanto, essas invasões não são recomendadas, porque elas afetam o próprio desempenho (por exemplo, descarregando a bateria).

A primeira regra para uma medição eficaz da performance é garantir que suas técnicas de medição não estejam causando problemas de performance. Portanto, para qualquer métrica personalizada que você avaliar no seu site, é melhor usar uma das seguintes APIs, se possível.

API Performance Observer

Compatibilidade com navegadores

  • 52
  • 79
  • 57
  • 11

Origem

A API Performance Observer é o mecanismo que coleta e exibe dados de todas as outras APIs de desempenho discutidas nesta página. Compreendê-los é fundamental para obter bons dados.

Use PerformanceObserver para se inscrever passivamente em eventos relacionados ao desempenho. Isso permite que os callbacks de 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 que será executado sempre que novas entradas de desempenho forem enviadas. Em seguida, você informa ao observador quais tipos de entradas precisam ser detectados usando o método observe():

// Catch errors since 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 a seguir listam todos os tipos de entrada disponíveis para observação. No entanto, 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 as entradas à medida que elas ocorrem. Isso pode causar problemas se você quiser carregar lentamente 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 ocorreram), defina a flag buffered como true ao chamar observe(). 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 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 APIs novas, como tarefas longas, não são expostas pelo objeto performance. Elas são expostas 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 com base no tempo. Ele permite que você marque arbitrariamente pontos no tempo e meça a duração entre essas marcações 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, a vantagem de usar a API User Timing é que ela se integra bem a ferramentas de performance. Por exemplo, o Chrome DevTools exibe as medições de velocidade do usuário no painel "Desempenho", 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 relatar medições da velocidade do usuário, use o PerformanceObserver e faça o registro para observar entradas do tipo measure:

// 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 `measure` entries to be dispatched.
  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 saber 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 informará todas as tarefas 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 alto nível são criadas com base na própria API Long Tasks (como Tempo para interação da página (TTI) e Tempo total de bloqueio (TBT)).

Para determinar quando tarefas longas acontecem, você pode usar o PerformanceObserver e fazer o registro 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 Maior exibição de conteúdo (LCP) é útil para saber quando a maior imagem ou bloco de texto foi pintado na tela, mas, 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 é criada 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>
  // 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 Interação com a próxima pintura (INP, na sigla em inglês) 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, do momento em que o usuário iniciou a interação até o momento em que o navegador exibe o próximo frame, mostrando o resultado visual da entrada do usuário.

A métrica INP é possibilitada pela API Event Timing. Essa API expõe vários carimbos de data/hora que ocorrem durante o ciclo de vida do evento, incluindo:

  • startTime: a hora em que o navegador recebe o evento.
  • processingStart: a hora em que o navegador pode começar a processar os manipuladores de eventos do evento.
  • processingEnd: hora em que o navegador termina de executar todo o código síncrono iniciado a partir de manipuladores de eventos para esse evento.
  • duration: o tempo (arredondado para 8 milissegundos por motivos de segurança) entre o recebimento do evento pelo navegador e a exibição do próximo frame depois de terminar a execução de todo o código síncrono iniciado nos manipuladores de eventos.

O exemplo a seguir mostra como usar esses valores para criar medições 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) => {
    // 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 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 time (ms): ${processingTime}`);
      console.log(`Total event duration (ms): ${duration}`);
      console.log(`Event type: ${eventType}`);
      console.log(target);
    });
  });

  // A durationThreshold of 16ms is necessary to surface more
  // interactions, since the default is 104ms. The minimum
  // durationThreshold is 16ms.
  po.observe({type: 'event', buffered: true, durationThreshold: 16});
} catch (error) {
  // Do nothing if the browser doesn't support this API.
}

API Resource Timing

A API Resource Timing (em inglês) 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 aos dados de tempo (embora haja muito disso). Estes são outros dados que você pode acessar:

  • initiatorType: como o recurso foi buscado, por exemplo, em uma tag <script> ou <link> ou 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 realmente transferido pela rede. Quando os recursos são preenchidos pelo cache, esse valor pode ser muito menor que 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 recursos 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 de recursos afeta o desempenho de visitantes recorrentes.

O exemplo a seguir registra todos os recursos solicitados pela página e indica se cada recurso foi atendido ou não pelo cache.

// 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()) {
      // 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 à Resource Timing API, mas informa apenas as solicitações de navegação. O tipo de entrada navigation também é semelhante ao tipo de entrada resource, mas contém algumas informações 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 o primeiro byte (TTFB)) está disponível usando a API Navigation Timing, especificamente o carimbo de data/hora responseStart da entrada.

// 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()) {
      // If transferSize is 0, the resource was fulfilled via 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.
}

O tempo de inicialização do service worker para solicitações de navegação pode ser algo importante para os desenvolvedores de métricas que usam o service worker. 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 específica pode ser determinado com base no delta entre entry.responseStart e entry.workerStart.

// 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()) {
      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

Com a API Server Timing, você pode transmitir dados de tempo específicos da solicitação do seu servidor para o navegador usando cabeçalhos de resposta. Por exemplo, você pode 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 os 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.

// 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()) {
      // 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.
}