API de velocidade do usuário

Noções básicas sobre o app da Web

Alex Danilo

Aplicativos da Web de alto desempenho são cruciais para uma ótima experiência do usuário. À medida que os aplicativos da Web ficam cada vez mais complexos, entender o impacto no desempenho é vital para criar uma experiência envolvente. Nos últimos anos, várias APIs diferentes apareceram no navegador para ajudar a analisar o desempenho da rede, os tempos de carregamento etc., mas elas não necessariamente fornecem detalhes precisos com flexibilidade suficiente para encontrar o que está deixando seu aplicativo mais lento. Use a API User Timing, que fornece um mecanismo para você instrumentar seu aplicativo da Web e identificar onde ele passa o tempo. Neste artigo, vamos abordar a API e exemplos de como usá-la.

Não é possível otimizar o que não pode ser medido

A primeira etapa para acelerar um aplicativo da Web lento é descobrir onde o tempo está sendo gasto. Medir o impacto do tempo em áreas do código JavaScript é a maneira ideal de identificar pontos de acesso, que é a primeira etapa para descobrir como melhorar o desempenho. Felizmente, a API User Timing oferece uma maneira de inserir chamadas de API em diferentes partes do JavaScript e extrair dados de tempo detalhados que podem ser usados para ajudar na otimização.

Tempo de alta resolução e now()

A precisão é uma parte fundamental da medição de tempo precisa. Antigamente, tínhamos um tempo baseado na medição de milissegundos, o que é bom, mas criar um site sem engasgos a 60 QPS significa que cada frame precisa ser renderizado em 16 ms. Portanto, quando você tem apenas a precisão de milissegundos, falta a precisão necessária para uma boa análise. Insira High Resolution Time, um novo tipo de temporização integrado aos navegadores modernos. O tempo de alta resolução fornece carimbos de data/hora de ponto flutuante que podem ser precisos até a resolução de microssegundos, mil vezes melhor do que antes.

Para saber a hora atual no seu aplicativo da Web, chame o método now(), que forma uma extensão da interface Performance. O código abaixo mostra como fazer isso:

var myTime = window.performance.now();

Há outra interface chamada PerformanceTiming, que fornece vários tempos diferentes relacionados à forma como o aplicativo da Web é carregado. O método now() retorna o tempo decorrido desde o momento em que o tempo navigationStart em PerformanceTiming ocorreu.

O tipo DOMHighResTimeStamp

Ao tentar cronometrar aplicativos da Web no passado, você usava algo como Date.now(), que retorna um DOMTimeStamp. DOMTimeStamp retorna um número inteiro de milissegundos como valor. Para fornecer a maior precisão necessária para o tempo de alta resolução, um novo tipo chamado DOMHighResTimeStamp foi introduzido. Esse tipo é um valor de ponto flutuante que também retorna o tempo em milissegundos. No entanto, como é um ponto flutuante, o valor pode representar milissegundos fracionários e, portanto, pode gerar uma precisão de milésimo de milissegundo.

Interface de tempo do usuário

Agora que temos carimbos de data/hora de alta resolução, vamos usar a interface Tempo do usuário para extrair informações de tempo.

A interface de tempo do usuário oferece funções que permitem chamar métodos em diferentes lugares do aplicativo, que podem fornecer um rastro de migas no estilo Hansel e Gretel para acompanharmos o tempo gasto.

Como usar o mark()

O método mark() é a ferramenta principal do nosso kit de análise de tempo. O mark() armazena um carimbo de data/hora para nós. O que é muito útil em mark() é que podemos nomear o carimbo de data/hora, e a API vai lembrar o nome e o carimbo de data/hora como uma única unidade.

Chamar mark() em vários lugares do aplicativo permite que você descubra quanto tempo levou para atingir essa "marca" no aplicativo da Web.

A especificação chama a atenção para vários nomes sugeridos para marcas que podem ser interessantes e bastante autoexplicativos, como mark_fully_loaded, mark_fully_visible, mark_above_the_fold etc.

Por exemplo, podemos definir uma marca para quando o aplicativo estiver totalmente carregado usando o seguinte código:

window.performance.mark('mark_fully_loaded');

Ao definir marcas nomeadas em todo o aplicativo da Web, podemos coletar muitos dados de tempo e analisá-los no nosso tempo livre para descobrir o que o aplicativo está fazendo e quando.

Como calcular medidas com measure()

Depois de definir várias marcas de tempo, você vai querer saber o tempo decorrido entre elas. Use o método measure() para fazer isso.

O método measure() calcula o tempo decorrido entre as marcas e também pode medir o tempo entre a marca e qualquer um dos nomes de eventos conhecidos na interface PerformanceTiming.

Por exemplo, é possível calcular o tempo desde a conclusão do DOM até o estado do aplicativo ser totalmente carregado usando um código como este:

window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');

Quando você chama measure(), o resultado é armazenado independentemente das marcas definidas, para que você possa recuperá-las mais tarde. Ao armazenar os tempos à medida que o aplicativo é executado, ele continua respondendo e você pode transferir todos os dados depois que o aplicativo terminar o trabalho para que possam ser analisados mais tarde.

Como descartar marcas com clearMarks()

Às vezes, é útil se livrar de várias marcas que você configurou. Por exemplo, você pode fazer execuções em lote no seu aplicativo da Web e, portanto, quer começar do zero em cada execução.

É fácil se livrar de todas as marcas que você configurou chamando clearMarks().

O exemplo de código abaixo vai apagar todas as marcas que você tem, para que você possa configurar uma execução de tempo novamente, se quiser.

window.performance.clearMarks();

É claro, há alguns cenários em que você talvez não queira limpar todas as suas marcas. Portanto, se você quiser se livrar de marcas específicas, basta transmitir o nome da marca que você quer remover. Por exemplo, o código abaixo:

window.performance.clearMarks('mark_fully_loaded');

se livra da marca que definimos no primeiro exemplo, deixando as outras marcas inalteradas.

Você também pode se livrar de todas as medidas que criou. Para isso, há um método correspondente chamado clearMeasures(). Ele funciona exatamente como o clearMarks(), mas trabalha em qualquer medição que você fez. Por exemplo, o código:

window.performance.clearMeasures('measure_load_from_dom');

vai remover a medida que fizemos no exemplo measure() acima. Se você quiser remover todas as medidas, o funcionamento será o mesmo que o de clearMarks(), em que você chama clearMeasures() sem argumentos.

Como extrair os dados de tempo

É bom definir marcas e medir intervalos, mas em algum momento você vai querer acessar esses dados de tempo para realizar algumas análises. Isso também é muito simples. Basta usar a interface PerformanceTimeline.

Por exemplo, o método getEntriesByType() permite que você receba todos os tempos de marcação ou todos os tempos de medição em uma lista para iterar e processar os dados. A lista é retornada em ordem cronológica, para que você possa conferir as marcas na ordem em que foram atingidas no seu aplicativo da Web.

O código abaixo:

var items = window.performance.getEntriesByType('mark');

retorna uma lista de todas as marcas que foram atingidas no nosso aplicativo da Web, enquanto o código:

var items = window.performance.getEntriesByType('measure');

retorna uma lista de todas as medidas que fizemos.

Também é possível receber uma lista de entradas usando o nome específico que você atribuiu a elas. Por exemplo, o código:

var items = window.performance.getEntriesByName('mark_fully_loaded');

retornaria uma lista com um item que contém o carimbo de data/hora "mark_fully_loaded" na propriedade startTime.

Temporização de uma solicitação XHR (exemplo)

Agora que temos uma imagem decente da API User Timing, podemos usá-la para analisar quanto tempo todos os nossos XMLHttpRequests levam no nosso aplicativo da Web.

Primeiro, vamos modificar todas as solicitações send() para emitir uma chamada de função que defina as marcas e, ao mesmo tempo, mudar nossos callbacks de sucesso com uma chamada de função que defina outra marca e gere uma medida de quanto tempo a solicitação levou.

Normalmente, nosso XMLHttpRequest ficaria assim:

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  do_something(e.responseText);
}
myReq.send();

No nosso exemplo, vamos adicionar um contador global para acompanhar o número de solicitações e também para armazenar uma medida para cada solicitação feita. O código para fazer isso é parecido com este:

var reqCnt = 0;

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  window.performance.mark('mark_end_xhr');
  reqCnt++;
  window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
  do_something(e.responseText);
}
window.performance.mark('mark_start_xhr');
myReq.send();

O código acima gera uma medida com um valor de nome exclusivo para cada XMLHttpRequest enviado. Estamos presumindo que as solicitações sejam executadas em sequência. O código para solicitações paralelas precisa ser um pouco mais complexo para processar solicitações que retornam fora de ordem. Vamos deixar isso como um exercício para o leitor.

Depois que o aplicativo da Web fez várias solicitações, podemos descarregá-las no console usando o código abaixo:

var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length; ++i) {
  var req = items[i];
  console.log('XHR ' + req.name + ' took ' + req.duration + 'ms');
}

Conclusão

A API User Timing oferece muitas ferramentas excelentes para aplicar a qualquer aspecto do seu aplicativo da Web. É possível reduzir os pontos de acesso no seu aplicativo facilmente, distribuindo chamadas de API em todo o aplicativo da Web e processando os dados de tempo gerados para criar uma imagem clara de onde o tempo está sendo gasto. E se o navegador não for compatível com essa API? Sem problemas. Você pode encontrar um polyfill incrível aqui que emula a API muito bem e funciona bem com o webpagetest.org. Então, o que você está esperando? Teste a API User Timing nos seus aplicativos agora. Você vai descobrir como acelerar a execução deles, e seus usuários vão agradecer por melhorar a experiência deles.