Uma introdução à API de desempenho

Publicados: 2022-08-10

A Performance API mede a capacidade de resposta de seu aplicativo da Web ao vivo em dispositivos de usuários reais e conexões de rede. Ele pode ajudar a identificar gargalos no código do lado do cliente e do lado do servidor com:

  • tempo do usuário: medição personalizada do desempenho da função JavaScript do lado do cliente
  • tempo de pintura: métricas de renderização do navegador
  • tempo de recurso: desempenho de carregamento de ativos e chamadas Ajax
  • tempo de navegação: métricas de carregamento de página, incluindo redirecionamentos, pesquisas de DNS, prontidão para DOM e muito mais

A API aborda vários problemas associados à avaliação de desempenho típica:

  1. Os desenvolvedores geralmente testam aplicativos em PCs de última geração conectados a uma rede rápida. O DevTools pode emular dispositivos mais lentos, mas nem sempre destaca problemas do mundo real quando a maioria dos clientes está executando um celular de dois anos conectado ao WiFi do aeroporto.
  2. As opções de terceiros, como o Google Analytics, geralmente são bloqueadas, levando a resultados e suposições distorcidos. Você também pode encontrar implicações de privacidade em alguns países.
  3. A Performance API pode avaliar com precisão várias métricas melhor do que métodos como Date() .

Quer saber mais sobre como usar a API de desempenho? Comece aqui... Clique para Tweetar
As seções a seguir descrevem maneiras de usar a API de desempenho. Alguns conhecimentos de JavaScript e métricas de carregamento de página são recomendados.

Disponibilidade da API de desempenho

A maioria dos navegadores modernos suporta a Performance API – incluindo IE10 e IE11 (até mesmo o IE9 tem suporte limitado). Você pode detectar a presença da API usando:

 if ('performance' in window) { // use Performance API }

Não é possível preencher totalmente a API com Polyfill, portanto, tenha cuidado com navegadores ausentes. Se 90% de seus usuários navegam com satisfação com o Internet Explorer 8, você medirá apenas 10% dos clientes com aplicativos mais capazes.

A API pode ser usada em Web Workers, que fornecem uma maneira de executar cálculos complexos em um thread em segundo plano sem interromper as operações do navegador.

A maioria dos métodos de API pode ser usada no Node.js do lado do servidor com o módulo padrão perf_hooks:

 // Node.js performance import { performance } from 'node:perf_hooks'; // or in Common JS: const { performance } = require('node:perf_hooks'); console.log( performance.now() );

Deno fornece a API de desempenho padrão:

 // Deno performance console.log( performance.now() );

Você precisará executar scripts com a permissão --allow-hrtime para habilitar a medição de tempo de alta resolução:

 deno run --allow-hrtime index.js

O desempenho do lado do servidor geralmente é mais fácil de avaliar e gerenciar porque depende da carga, CPUs, RAM, discos rígidos e limites de serviço de nuvem. Atualizações de hardware ou opções de gerenciamento de processos, como PM2, clustering e Kubernetes, podem ser mais eficazes do que refatorar código.

As seções a seguir concentram-se no desempenho do lado do cliente por esse motivo.

Medição de desempenho personalizada

A Performance API pode ser usada para cronometrar a velocidade de execução das funções do seu aplicativo. Você pode ter usado ou encontrado funções de tempo usando Date() :

 const timeStart = new Date(); runMyCode(); const timeTaken = new Date() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);

A Performance API oferece dois benefícios principais:

  1. Melhor precisão: Date() mede até o milissegundo mais próximo, mas a Performance API pode medir frações de milissegundo (dependendo do navegador).
  2. Melhor confiabilidade: o usuário ou o sistema operacional podem alterar a hora do sistema para que as métricas baseadas em Date() nem sempre sejam precisas. Isso significa que suas funções podem parecer particularmente lentas quando os relógios avançam!

O equivalente a Date() é performance.now() que retorna um timestamp de alta resolução que é definido como zero quando o processo responsável pela criação do documento é iniciado (a página foi carregada):

 const timeStart = performance.now(); runMyCode(); const timeTaken = performance.now() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);

Uma propriedade performance.timeOrigin não padrão também pode retornar um carimbo de data/hora de 1º de janeiro de 1970, embora isso não esteja disponível no IE e no Deno.

performance.now() torna-se impraticável ao fazer mais do que algumas medições. A Performance API fornece um buffer onde você pode gravar eventos para análise posterior passando um nome de rótulo para performance.mark() :

 performance.mark('start:app'); performance.mark('start:init'); init(); // run initialization functions performance.mark('end:init'); performance.mark('start:funcX'); funcX(); // run another function performance.mark('end:funcX'); performance.mark('end:app');

Uma matriz de todos os objetos de marca no buffer de desempenho pode ser extraída usando:

 const mark = performance.getEntriesByType('mark');

Exemplo de resultado:

 [ { detail: null duration: 0 entryType: "mark" name: "start:app" startTime: 1000 }, { detail: null duration: 0 entryType: "mark" name: "start:init" startTime: 1001 }, { detail: null duration: 0 entryType: "mark" name: "end:init" startTime: 1100 }, ... ]

O método performance.measure() calcula o tempo entre duas marcas e também o armazena no buffer Performance. Você passa um novo nome de medida, o nome da marca inicial (ou null para medir a partir do carregamento da página) e o nome da marca final (ou null para medir a hora atual):

 performance.measure('init', 'start:init', 'end:init');

Um objeto PerformanceMeasure é anexado ao buffer com a duração de tempo calculada. Para obter esse valor, você pode solicitar uma matriz de todas as medidas:

 const measure = performance.getEntriesByType('measure');

ou solicite uma medida pelo seu nome:

 performance.getEntriesByName('init');

Exemplo de resultado:

 [ { detail: null duration: 99 entryType: "measure" name: "init" startTime: 1001 } ]

Usando o buffer de desempenho

Além de marcas e medidas, o buffer de desempenho é usado para registrar automaticamente o tempo de navegação, tempo de recurso e tempo de pintura (que discutiremos mais tarde). Você pode obter uma matriz de todas as entradas no buffer:

 performance.getEntries();

Por padrão, a maioria dos navegadores fornece um buffer que armazena até 150 métricas de recursos. Isso deve ser suficiente para a maioria das avaliações, mas você pode aumentar ou diminuir o limite de buffer, se necessário:

 // record 500 metrics performance.setResourceTimingBufferSize(500);

As marcas podem ser limpas pelo nome ou você pode especificar um valor vazio para limpar todas as marcas:

 performance.clearMarks('start:init');

Da mesma forma, as medidas podem ser limpas por nome ou um valor vazio para limpar tudo:

 performance.clearMeasures();

Monitorando atualizações do buffer de desempenho

Um PerformanceObserver pode monitorar alterações no buffer de desempenho e executar uma função quando ocorrerem eventos específicos. A sintaxe será familiar se você tiver usado o MutationObserver para responder às atualizações do DOM ou o IntersectionObserver para detectar quando os elementos são rolados na janela de visualização.

Você deve definir uma função de observador com dois parâmetros:

  1. uma matriz de entradas de observadores que foram detectadas, e
  2. o objeto observador. Se necessário, seu método disconnect() pode ser chamado para parar o observador.
 function performanceCallback(list, observer) { list.getEntries().forEach(entry => { console.log(`name : ${ entry.name }`); console.log(`type : ${ entry.type }`); console.log(`start : ${ entry.startTime }`); console.log(`duration: ${ entry.duration }`); }); }

A função é passada para um novo objeto PerformanceObserver. Seu método observe() recebe uma matriz de inputTypes do buffer de desempenho para observar:

 let observer = new PerformanceObserver( performanceCallback ); observer.observe({ entryTypes: ['mark', 'measure'] });

Neste exemplo, adicionar uma nova marca ou medida executa a função performanceCallback() . Embora apenas registre mensagens aqui, ele pode ser usado para acionar um upload de dados ou fazer cálculos adicionais.

Medindo o desempenho da pintura

A API Paint Timing está disponível apenas em JavaScript do lado do cliente e registra automaticamente duas métricas importantes para o Core Web Vitals:

  1. first-paint: O navegador começou a desenhar a página.
  2. first-contentful-paint: O navegador pintou o primeiro item significativo do conteúdo DOM, como um título ou uma imagem.

Eles podem ser extraídos do buffer de desempenho para um array:

 const paintTimes = performance.getEntriesByType('paint');

Tenha cuidado ao executar isso antes que a página seja totalmente carregada; os valores não estarão prontos. Aguarde o evento window.load ou use um PerformanceObserver para monitorar entryTypes de paint .

Lutando com problemas de tempo de inatividade e WordPress? Kinsta é a solução de hospedagem projetada para economizar seu tempo! Confira nossas funcionalidades

Exemplo de resultado:

 [ { "name": "first-paint", "entryType": "paint", "startTime": 812, "duration": 0 }, { "name": "first-contentful-paint", "entryType": "paint", "startTime": 856, "duration": 0 } ]

Uma primeira pintura lenta geralmente é causada por CSS ou JavaScript de bloqueio de renderização. A lacuna para a primeira pintura de conteúdo pode ser grande se o navegador tiver que baixar uma imagem grande ou renderizar elementos complexos.

Medição de desempenho de recursos

Os tempos de rede para recursos como imagens, folhas de estilo e arquivos JavaScript são registrados automaticamente no buffer de desempenho. Embora haja pouco que você possa fazer para resolver problemas de velocidade de rede (além de reduzir o tamanho dos arquivos), isso pode ajudar a destacar problemas com ativos maiores, respostas lentas do Ajax ou scripts de terceiros com desempenho ruim.

Uma matriz de métricas PerformanceResourceTiming pode ser extraída do buffer usando:

 const resources = performance.getEntriesByType('resource');

Como alternativa, você pode buscar métricas para um recurso passando seu URL completo:

 const resource = performance.getEntriesByName('https://test.com/script.js');

Exemplo de resultado:

 [ { connectEnd: 195, connectStart: 195, decodedBodySize: 0, domainLookupEnd: 195, domainLookupStart: 195, duration: 2, encodedBodySize: 0, entryType: "resource", fetchStart: 195, initiatorType: "script", name: "https://test.com/script.js", nextHopProtocol: "h3", redirectEnd: 0, redirectStart: 0, requestStart: 195, responseEnd: 197, responseStart: 197, secureConnectionStart: 195, serverTiming: [], startTime: 195, transferSize: 0, workerStart: 195 } ]

As seguintes propriedades podem ser examinadas:

  • nome : URL do recurso
  • entryType : “recurso”
  • initiatorType : como o recurso foi iniciado, como “script” ou “link”
  • serverTiming : uma matriz de objetos PerformanceServerTiming passados ​​pelo servidor no cabeçalho HTTP Server-Timing (seu aplicativo do lado do servidor pode enviar métricas ao cliente para análise adicional)
  • startTime : timestamp quando a busca começou
  • nextHopProtocol : protocolo de rede usado
  • workerStart : Timestamp antes de iniciar um Progressive Web App Service Worker (0 se a solicitação não for interceptada por um Service Worker)
  • redirectStart : timestamp quando um redirecionamento foi iniciado
  • redirectEnd : Timestamp após o último byte da última resposta de redirecionamento
  • fetchStart : Timestamp antes da busca do recurso
  • domainLookupStart : Timestamp antes de uma pesquisa de DNS
  • domainLookupEnd : carimbo de data/hora após a pesquisa de DNS
  • connectStart : Timestamp antes de estabelecer uma conexão com o servidor
  • connectEnd : Timestamp após estabelecer uma conexão com o servidor
  • secureConnectionStart : Timestamp antes do handshake SSL
  • requestStart : Timestamp antes do navegador solicitar o recurso
  • responseStart : Timestamp quando o navegador recebe o primeiro byte de dados
  • responseEnd : Timestamp após receber o último byte ou fechar a conexão
  • duração : A diferença entre startTime e responseEnd
  • transferSize : O tamanho do recurso em bytes, incluindo o cabeçalho e o corpo compactado
  • encodedBodySize : O corpo do recurso em bytes antes de descompactar
  • decodedBodySize : O corpo do recurso em bytes após a descompactação

Este script de exemplo recupera todas as solicitações Ajax iniciadas pela API Fetch e retorna o tamanho e a duração total da transferência:

 const fetchAll = performance.getEntriesByType('resource') .filter( r => r.initiatorType === 'fetch') .reduce( (sum, current) => { return { transferSize: sum.transferSize += current.transferSize, duration: sum.duration += current.duration } }, { transferSize: 0, duration: 0 } );

Medição de desempenho de navegação

Os tempos de rede para descarregar a página anterior e carregar a página atual são registrados automaticamente no buffer de desempenho como um único objeto PerformanceNavigationTiming .

Extraia-o para um array usando:

 const pageTime = performance.getEntriesByType('navigation');

…ou passando o URL da página para .getEntriesByName() :

 const pageTiming = performance.getEntriesByName(window.location);

As métricas são idênticas às dos recursos, mas também incluem valores específicos da página:

  • entryType : Por exemplo, “navegação”
  • type : “navigate”, “reload”, “back_forward” ou “prerender”
  • redirectCount : o número de redirecionamentos
  • unloadEventStart : Timestamp antes do evento unload do documento anterior
  • unloadEventEnd : Timestamp após o evento unload do documento anterior
  • domInteractive : Timestamp quando o navegador analisou o HTML e construiu o DOM
  • domContentLoadedEventStart : Timestamp antes do evento DOMContentLoaded do documento ser acionado
  • domContentLoadedEventEnd : Timestamp após a conclusão do evento DOMContentLoaded do documento
  • domComplete : Timestamp após a construção do DOM e os eventos DOMContentLoaded terem sido concluídos
  • loadEventStart : carimbo de data/hora antes do evento de carregamento da página ser acionado
  • loadEventEnd : Timestamp após o evento de carregamento da página e todos os ativos estão disponíveis

Problemas típicos incluem:

  • Um longo atraso entre unloadEventEnd e domInteractive . Isso pode indicar uma resposta lenta do servidor.
  • Um longo atraso entre domContentLoadedEventStart e domComplete . Isso pode indicar que os scripts de inicialização da página estão muito lentos.
  • Um longo atraso entre domComplete e loadEventEnd . Isso pode indicar que a página tem muitos recursos ou vários estão demorando muito para carregar.

Registro e análise de desempenho

A Performance API permite reunir dados de uso do mundo real e carregá-los em um servidor para análise posterior. Você pode usar um serviço de terceiros, como o Google Analytics, para armazenar os dados, mas existe o risco de o script de terceiros ser bloqueado ou apresentar novos problemas de desempenho. Sua própria solução pode ser personalizada de acordo com seus requisitos para garantir que o monitoramento não afete outras funcionalidades.

Desconfie de situações em que as estatísticas não podem ser determinadas - talvez porque os usuários estejam em navegadores antigos, bloqueando o JavaScript ou por trás de um proxy corporativo. Compreender quais dados estão faltando pode ser mais proveitoso do que fazer suposições com base em informações incompletas.

Idealmente, seus scripts de análise não afetarão negativamente o desempenho executando cálculos complexos ou carregando grandes quantidades de dados. Considere utilizar web workers e minimizar o uso de chamadas localStorage síncronas. É sempre possível processar dados brutos em lote posteriormente.

Por fim, tenha cuidado com exceções, como dispositivos e conexões muito rápidos ou muito lentos que afetam negativamente as estatísticas. Por exemplo, se nove usuários carregam uma página em dois segundos, mas o décimo experimenta um download de 60 segundos, a latência média chega a quase 8 segundos. Uma métrica mais realista é o valor médio (2 segundos) ou o percentil 90 (9 em cada 10 usuários experimentam um tempo de carregamento de 2 segundos ou menos).

Resumo

O desempenho da Web continua sendo um fator crítico para os desenvolvedores. Os usuários esperam que os sites e aplicativos sejam responsivos na maioria dos dispositivos. O Search Engine Optimization também pode ser afetado, pois sites mais lentos são rebaixados no Google.
Tudo o que você precisa saber para começar a usar a Performance API está aqui Click to Tweet
Existem muitas ferramentas de monitoramento de desempenho por aí, mas a maioria avalia as velocidades de execução do lado do servidor ou usa um número limitado de clientes capazes para avaliar a renderização do navegador. A Performance API fornece uma maneira de reunir métricas de usuários reais que não seriam possíveis de calcular de outra forma.