É 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" ao outro.
- 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 medir a performance de maneira eficaz é 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
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()
:
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'});
As seções a seguir listam todos os tipos de entrada disponíveis para observação, mas, em navegadores mais recentes, você pode 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 poderá causar problemas se você quiser fazer o carregamento lento do 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 até o tamanho máximo do buffer para esse tipo.
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 largest-contentful-paint
) 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 a API Measurement para métricas baseadas no tempo. Ele permite que você marque arbitrariamente pontos em e depois medir a duração entre essas marcas.
// 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
:
// 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});
API Long Tasks
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
:
// 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});
API Long Animation Frames
A API Long Animation Frames é uma nova iteração da API Long Tasks que analisa frames longos, em vez de tarefas longas, com mais de 50 milissegundos. Isso resolve algumas falhas da API Long Tasks, incluindo uma atribuição aprimorada e um escopo mais amplo de atrasos potencialmente problemáticos.
Para determinar quando frames longos ocorrem, use o PerformanceObserver e faça o registro para observar entradas do tipo long-animation-frame
:
// 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 `long-animation-frame` entries to be dispatched.
po.observe({type: 'long-animation-frame', buffered: true});
API Element Timing
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>
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});
</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:
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 presentationDelay = entry.startTime + entry.duration - entry.processingEnd;
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 processing time (ms): ${processingTime}`);
console.log(`Presentation delay (ms): ${presentationDelay}`);
console.log(`Total event duration (ms): ${duration}`);
console.log(`Event type: ${eventType}`);
console.log(target);
});
});
// A durationThreshold of 16ms is necessary to include more
// interactions, since the default is 104ms. The minimum
// durationThreshold is 16ms.
po.observe({type: 'event', buffered: true, durationThreshold: 16});
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 chamadafetch()
.nextHopProtocol
: o protocolo usado para buscar o recurso, comoh2
ouquic
.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 queencodedBodySize
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 do recurso 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.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log(entry.name, entry.transferSize === 0);
}
});
// Start listening for `resource` entries to be dispatched.
po.observe({type: 'resource', buffered: true});
API Navigation Timing
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.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log('Time to first byte', entry.responseStart);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});
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
.
// 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});
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.
// 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});