API de velocidade do usuário

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

Alex Danilo

Os aplicativos da Web de alto desempenho são cruciais para oferecer uma ótima experiência do usuário. À medida que os aplicativos da Web se tornam cada vez mais complexos, entender o impacto no desempenho é fundamental para criar uma experiência atraente. 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 fornecem necessariamente detalhes minuciosos com flexibilidade suficiente para descobrir o que está atrasando o aplicativo. Insira a API User Timing, que oferece um mecanismo para instrumentar o aplicativo da Web e identificar onde ele está gastando tempo. Neste artigo, vamos falar sobre a API e conferir exemplos de como usá-la.

Você não pode otimizar o que não pode medir

O primeiro passo para acelerar um aplicativo da Web lento é descobrir onde o tempo está sendo gasto. Medir o impacto do tempo das áreas do código JavaScript é a maneira ideal de identificar pontos de acesso. Essa é a primeira etapa para descobrir como melhorar o desempenho. Felizmente, a API User Timing permite a inserção de chamadas de API em diferentes partes do JavaScript e a extração de dados detalhados de tempo para a otimização.

Tempo de alta resolução e now()

Uma parte fundamental da medição precisa de tempo é a precisão. Antigamente, o tempo era baseado em medidas de milissegundos, o que é aceitável, mas criar um site de 60 QPS sem instabilidade significava que cada frame precisa ser desenhado em 16 ms. Portanto, quando sua precisão é de milissegundos, falta a precisão necessária para uma boa análise. Conheça o Tempo de alta resolução, um novo tipo de marcação de tempo integrado aos navegadores mais recentes. Tempo de alta resolução fornece carimbos de data/hora de ponto flutuante que podem ser precisos na resolução de microssegundos, mil vezes melhor do que antes.

Para ver o horário atual no seu app da Web, chame o método now(), que forma uma extensão da interface Performance (link em inglês). O código abaixo mostra como fazer isso:

var myTime = window.performance.now();

Há outra interface chamada PerformanceTiming, que indica diferentes horários relacionados ao carregamento do seu aplicativo da Web. O método now() retorna o tempo decorrido desde o momento em que o horário navigationStart em PerformanceTiming aconteceu.

O tipo DOMHighResTimeStamp

No passado, para cronometrar aplicativos da Web, você usaria 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 ao 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. Mas, por ser um ponto flutuante, o valor pode representar milissegundos fracionários e, portanto, pode gerar uma precisão de milésimos de milissegundo.

A interface de velocidade do usuário

Agora que temos carimbos de data/hora de alta resolução, usaremos a interface User Timing para extrair informações de tempo.

A interface da User Timing oferece funções que nos permitem chamar métodos em diferentes locais do aplicativo e que podem fornecer uma trilha de navegação em estilo de Hansel e Gretel para que possamos rastrear onde o tempo está sendo gasto.

Como usar o mark()

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

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

A especificação menciona vários nomes sugeridos para marcas que podem ser interessantes e autoexplicativas, 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 nosso aplicativo da Web, podemos reunir um grande número de dados de tempo e analisá-los a nosso tempo para descobrir o que o aplicativo está fazendo e quando.

Calcular medidas com o measure()

Depois de definir várias marcações de tempo, descubra o tempo decorrido entre elas. Use o método measure() para fazer isso.

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

Por exemplo, você pode calcular o tempo decorrido desde a conclusão do DOM até o estado do aplicativo ser totalmente carregado usando códigos como estes:

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

Quando você chama measure(), ele armazena o resultado independente das marcas definidas, para que você possa recuperá-los mais tarde. Ao armazenar tempos de ausência enquanto o aplicativo é executado, ele permanece responsivo e é possível despejar todos os dados depois que o aplicativo termina algum trabalho, para que possam ser analisados mais tarde.

Descartando marcas com clearMarks()

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

É fácil o suficiente para se livrar de quaisquer marcas que você configurou, basta chamar clearMarks().

Portanto, o código de exemplo abaixo eliminaria todas as marcas existentes, para que você possa configurar uma execução de tempo novamente, se quiser.

window.performance.clearMarks();

Claro, há alguns cenários em que você pode não querer apagar todas as suas marcas. Portanto, se você quiser se livrar de marcas específicas, basta passar o nome da marca que deseja remover. Por exemplo, o código abaixo:

window.peformance.clearMarks('mark_fully_loaded');

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

Também é possível descartar as medidas que você fez. Há um método correspondente para fazer isso, chamado clearMeasures(). Ele funciona exatamente da mesma forma que o clearMarks(), mas funciona nas medições que você fez. Por exemplo, o código:

window.performance.clearMeasures('measure_load_from_dom');

vai remover a medida que fizemos no exemplo de measure() acima. Se você quiser remover todas as medidas, o processo vai funcionar da mesma forma que clearMarks(), em que você chama clearMeasures() sem argumentos.

Como extrair os dados de tempo

É muito bom definir marcas e medir intervalos, mas, em algum momento, você quer obter esses dados de tempo para realizar algumas análises. Isso também é muito simples. Tudo o que você precisa fazer é usar a interface PerformanceTimeline.

Por exemplo, o método getEntriesByType() permite receber todos os tempos de marcação ou todas as nossas medidas expirarem como uma lista para que possamos iterar e digerir os dados. O interessante é que a lista é retornada em ordem cronológica, para que você possa ver as marcas na ordem em que foram acessadas no aplicativo da Web.

O código abaixo:

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

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

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

retorna uma lista de todas as medidas que tomamos.

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

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

retornaria uma lista com um item contendo o carimbo de data/hora "mark_full_carregado" na propriedade startTime.

Como programar uma solicitação XHR (exemplo)

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

Primeiro, modificaremos todas as solicitações send() para emitir uma chamada de função que configura as marcas e, ao mesmo tempo, alterar os callbacks de sucesso com uma chamada de função que define outra marca e gera uma medida de quanto tempo a solicitação levou.

Normalmente, nosso XMLHttpRequest seria semelhante a este:

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 rastrear o número de solicitações e também usá-lo para armazenar uma medida para cada solicitação feita. O código para fazer isso tem a seguinte aparência:

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 precisaria ser um pouco mais complexo para lidar com solicitações que retornam fora de ordem, vamos deixar isso como um exercício para o leitor.

Depois que o aplicativo da Web tiver feito várias solicitações, podemos despejar todas elas 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 várias ferramentas excelentes que podem ser aplicadas a qualquer aspecto do seu aplicativo da web. A redução dos pontos de acesso em seu aplicativo pode ser facilmente alcançada distribuindo as chamadas de API por todo o seu aplicativo da Web e pós-processamento dos dados de tempo gerados para criar uma imagem clara de onde o tempo está sendo gasto. Mas e se o navegador não for compatível com essa API? Sem problemas. Você encontra um ótimo polyfill aqui, que emula a API muito bem e funciona muito bem com o webpagetest.org (link em inglês). Então, o que você está esperando? Experimente a API User Timing nos seus aplicativos agora. Você descobrirá como aumentar a velocidade deles e seus usuários agradecerão por tornar a experiência deles muito melhor.