A API de desempenho mede a capacidade de resposta da sua aplicação web ao vivo em dispositivos reais de usuário e conexões de rede. Ela pode ajudar a identificar gargalos no seu código do lado do cliente e do lado do servidor:
- user timing: Medição personalizada do desempenho da função JavaScript do lado do cliente
- paint timing: Métricas de renderização do navegador
- resource timing: Carregamento do desempenho dos ativos e chamadas Ajax
- navigation timing: Métricas de carregamento de página, incluindo redirecionamentos, consultas DNS, prontidão DOM, e mais
A API aborda vários problemas associados com a avaliação de desempenho típica:
- Os desenvolvedores frequentemente testam aplicativos em PCs high-end conectados a uma rede rápida. O DevTools pode emular dispositivos mais lentos, mas nem sempre destacará problemas do mundo real quando a maioria dos clientes está rodando um celular com dois anos de idade conectado ao WiFi do aeroporto.
- Opções de terceiros, como o Google Analytics, são frequentemente bloqueadas, levando a resultados e suposições distorcidas. Você também pode encontrar implicações de privacidade em alguns países.
- A API de desempenho pode medir com precisão várias métricas melhor do que métodos tais como
Date()
.
As seções seguintes descrevem como você pode 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 o IE10 e IE11 (até 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 efetuar um Polyfill completo do API, portanto, tenha cuidado com a falta de navegadores. Se 90% dos seus usuários estão navegando com o Internet Explorer 8, você estaria medindo apenas 10% dos clientes com aplicativos mais capazes.
A API pode ser usada em Web Worker, que fornecem uma maneira de executar cálculos complexos em uma linha de fundo sem interromper as operações do navegador.
A maioria dos métodos API podem ser usados no server-side Node.js com o módulo perf_hooks padrão:
// Node.js performance
import { performance } from 'node:perf_hooks';
// or in Common JS: const { performance } = require('node:perf_hooks');
console.log( performance.now() );
A 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 permitir a medição de tempo em 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 dos serviços em nuvem. Atualizações de hardware ou opções de gerenciamento de processos como PM2, clustering e Kubernetes podem ser mais eficazes do que o código de refatoração.
As seções seguintes se concentram no desempenho do lado do cliente por esta razão.
Medição de desempenho personalizado
A API de desempenho pode ser usada para cronometrar a velocidade de execução das funções da sua aplicação. Você pode ter usado ou encontrado funções de cronometragem usando Date()
:
const timeStart = new Date();
runMyCode();
const timeTaken = new Date() - timeStart;
console.log(`runMyCode() executed in ${ timeTaken }ms`);
O API de desempenho oferece dois benefícios principais:
- Melhor precisão:
Date()
mede até o milissegundo mais próximo, mas o API de desempenho pode medir frações de um milissegundo (dependendo do navegador). - Melhor confiabilidade: O usuário ou sistema operacional pode mudar o tempo do sistema de forma que as métricas baseadas em
Date()
nem sempre serão precisas. Isto significa que suas funções podem parecer particularmente lentas quando os relógios avançam!
O equivalente em Date()
é performance.now()
que retorna um carimbo de tempo de alta resolução que é colocado em zero quando o processo responsável pela criação do documento começa (a página foi carregada):
const timeStart = performance.now();
runMyCode();
const timeTaken = performance.now() - timeStart;
console.log(`runMyCode() executed in ${ timeTaken }ms`);
Um não-padrão performance.timeOrigin
a propriedade também pode devolver um carimbo de data/hora a partir de 1 de janeiro de 1970, embora isso não esteja disponível no IE e Deno.
performance.now()
torna-se impraticável quando se faz mais do que algumas medidas. A API de desempenho fornece um buffer onde você pode gravar o evento para análise posterior, passando um nome de etiqueta 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');
Um conjunto de todos os objetos de marcação no buffer de performance pode ser extraído 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 performance.measure()
calcula o tempo entre duas marcas e também o armazena no buffer de desempenho. Você passa um novo nome de medida, o nome da marca inicial (ou nulo para medir a partir do carregamento da página), e o nome da marca final (ou nulo 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 este valor, você pode solicitar um array de todas as medidas:
const measure = performance.getEntriesByType('measure');
ou solicitar 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 navigation timing, o resource timing e o paint timing (que discutiremos mais tarde). Você pode obter um array 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. Isto 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 apagadas pelo nome ou você pode especificar um valor vazio para apagar todas as marcas:
performance.clearMarks('start:init');
Da mesma forma, as medidas podem ser limpas pelo nome ou por um valor vazio para limpar tudo:
performance.clearMeasures();
Monitoramento de atualizações de buffer de desempenho
A PerformanceObserver pode monitorar mudanças no buffer de desempenho e executar uma função quando eventos específicos ocorrem. A sintaxe será familiar se você tiver usado MutationObserver para responder a atualizações do DOM ou IntersectionObserver para detectar quando os elementos são rolados para o viewport.
Você deve definir uma função de observador com dois parâmetros:
- uma série de entradas de observadores que foram detectadas, e
- o objeto observador. Se necessário, seu
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 observe()
é passado um conjunto de tipos de entrada de buffer de desempenho a serem observados:
let observer = new PerformanceObserver( performanceCallback );
observer.observe({ entryTypes: ['mark', 'measure'] });
Neste exemplo, a adição de uma nova marca ou medida executa a função performanceCallback()
. Enquanto ela apenas registra mensagens aqui, ela poderia ser usada para acionar um upload de dados ou fazer cálculos adicionais.
Medindo o desempenho Paint
O Paint Timing API está disponível apenas em JavaScript do lado do cliente e grava automaticamente duas métricas que são importantes para o Core Web Vitals:
- first-paint: O navegador começou a renderizar a página.
- first-contentful-paint: O navegador tornou o primeiro elemento significativo do conteúdo DOM, como um título ou uma imagem.
Estes podem ser extraídos do buffer de desempenho para um array:
const paintTimes = performance.getEntriesByType('paint');
Tenha cuidado ao executar isto antes que a página tenha sido totalmente carregada; os valores não estarão prontos. Ou espere pelo window.load
evento ou usar um PerformanceObserver
para monitorar paint
entryTypes.
Exemplo de resultado:
[
{
"name": "first-paint",
"entryType": "paint",
"startTime": 812,
"duration": 0
},
{
"name": "first-contentful-paint",
"entryType": "paint",
"startTime": 856,
"duration": 0
}
]
Um first-paint lenta é frequentemente causada por CSS ou JavaScript. A lacuna para first-paint pode ser grande se o navegador tiver que baixar uma imagem grande ou renderizar elementos complexos.
Medindo o Resource Performance
Os tempos de rede para recursos como imagens, folhas de estilo e arquivos JavaScript são automaticamente gravados no buffer de desempenho. Embora haja pouco que você possa fazer para resolver problemas de velocidade da 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 mau desempenho.
Um conjunto de métricas PerformanceResourceTiming pode ser extraído do buffer usando:
const resources = performance.getEntriesByType('resource');
Alternativamente, você pode buscar métricas para um ativo passando sua URL completa:
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:
- name: URL do recurso
- entryType: “recurso”
- initiatorType: Como o recurso foi iniciado, tal como “script” ou “link”
- serverTiming: Um conjunto de
PerformanceServerTiming
objetos passados pelo servidor no cabeçalho HTTP Server-Timing (sua aplicação do lado do servidor poderia enviar métricas para o cliente para análise posterior) - startTime: Timestamp quando o fetch começou
- nextHopProtocolo: Protocolo de rede utilizado
- workerStart: Timestamp antes de iniciar um Trabalhador de Serviço Progressivo de Aplicação Web (0 se a solicitação não for interceptada por um Trabalhador de Serviço)
- redirectStart: Carimbo de tempo quando um redirecionamento começou
- redirectEnd: Timestamp após o último byte da última resposta de redirecionamento
- fetchStart: Timestamp antes do recebimento dos recursos
- domínioLookupStart: Carimbo da hora antes de uma consulta DNS
- domainLookupEnd: Carimbo de tempo após a pesquisa DNS
- connectStart: Timestamp antes de estabelecer uma conexão de servidor
- connectEnd: Timestamp depois de estabelecer uma conexão de servidor
- secureConnectionStart: Timestamp antes do aperto de mão SSL
- requestStart: Timestamp antes que o navegador solicite o recurso
- responseStart: Timestamp quando o navegador recebe o primeiro byte de dados
- responseEnd: Timestamp depois de receber o último byte ou fechar a conexão
- duration: A diferença entre startTime e responseEnd
- transferSize: O tamanho do recurso em bytes incluindo o cabeçalho e o corpo comprimido
- encodedBodySize: O corpo do recurso em bytes antes de descomprimir
- decodedBodySize: O corpo do recurso em bytes após descompressão
Este script de exemplo recupera todos os pedidos Ajax iniciados pelo Fetch API e retorna o tamanho total e a duração 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 }
);
Medindo o Navigation Performance
Os tempos de rede para baixar a página anterior e carregar a página atual são automaticamente registrados no buffer de desempenho como um único objeto PerformanceNavigationTiming
.
Extraia-o para um array usando:
const pageTime = performance.getEntriesByType('navigation');
…ou passando a 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 de página:
- entryType: Por exemplo: “navegação”
- tipo: Ou “navigate”, “reload”, “back_forward”, ou “prerender”
- redirectCount: O número de redirecionamentos
- unloadEventStart: Timestamp antes do evento de descarregamento do documento anterior
- unloadEventEnd: Timestamp após o evento de descarregamento do documento anterior
- domInteractive: Timestamp quando o navegador tiver analisado o HTML e construído o DOM
- domContentLoadedEventStart: Carimbo da hora antes do evento DOMContentLoadedDomContentLoaded do documento
- domContentLoadedEventEnd: Timestamp após a conclusão do evento DOMContentLoadedDomContentLoaded do documento
- domComplete: Timestamp após a construção do DOM e eventos DOMContentLoaded
- loadEventStart: Timestamp antes que o evento de carga de página tenha disparado
- loadEventEnd: Timestamp após o evento de carregamento da página e todos os ativos estão disponíveis
As questões típicas incluem:
- Um longo atraso entre unloadEventEnd e domInteractive. Isto pode indicar uma resposta lenta do servidor.
- Um longo atraso entre domContentLoadedEventStart e domComplete. Isto poderia indicar que os scripts de inicialização da página são muito lentos.
- Um longo atraso entre domComplete e loadEventEnd. Isso pode indicar que a página tem muitos ativos ou vários estão demorando muito para carregar.
Gravação e análise de desempenho
A API de desempenho permite que você colete dados de uso no mundo real e os carregue em um servidor para análise posterior. Você poderia usar um serviço de terceiros como o Google Analytics para armazenar os dados, mas há um risco de o script de terceiros ser bloqueado ou introduzir novos problemas de desempenho. Sua própria solução pode ser personalizada de acordo com suas necessidades para garantir que o monitoramento não tenha impacto em outras funcionalidades.
Tenha cuidado com situações nas quais as estatísticas não podem ser determinadas – talvez porque os usuários estão em navegadores antigos, bloqueando o JavaScript, ou atrás de um proxy corporativo. Entender quais dados estão faltando pode ser mais proveitoso do que fazer suposições baseadas em informações incompletas.
Idealmente, seus scripts de análise não afetarão negativamente o desempenho executando cálculos complexos ou fazendo upload de grandes quantidades de dados. Considere utilizar trabalhadores da web e minimizar o uso de chamadas sincronizadas do localStorage. É sempre possível processar os dados brutos em lote mais tarde.
Finalmente, desconfie de dispositivos e conexões anômalas, tais como dispositivos muito rápidos ou muito lentos que afetam adversamente as estatísticas. Por exemplo, se nove usuários carregarem uma página em dois segundos, mas o décimo experimentar um download de 60 segundos, a média de latência sai para quase 8 segundos. Uma métrica mais realista é o valor mediano (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. A Otimização de mecanismos de busca também pode ser afetada à medida que sites mais lentos são rebaixados no Google.
Existem muitas ferramentas de monitoramento de desempenho por aí, mas a maioria avalia a velocidade de execução do lado do servidor ou usa um número limitado de clientes capazes para julgar a renderização do navegador. A API de desempenho fornece uma forma de comparar métricas reais do usuário que não seria possível calcular de outra forma.
Deixe um comentário