API de User Timing

Conceptos básicos sobre tu aplicación web

Alex Danilo

Las aplicaciones web de alto rendimiento son fundamentales para una excelente experiencia del usuario. A medida que las aplicaciones web se vuelven cada vez más complejas, es fundamental comprender el impacto en el rendimiento para crear una experiencia atractiva. En los últimos años, aparecieron varias APIs en el navegador para ayudar a analizar el rendimiento de la red, los tiempos de carga, etc., pero no necesariamente brindan detalles precisos ni suficiente flexibilidad para detectar qué está ralentizando tu aplicación. Ingresa la API de User Timing, que te proporciona un mecanismo que puedes utilizar para instrumentar tu aplicación web para identificar en qué parte de su tiempo tu aplicación dedica su tiempo. En este artículo, abordaremos la API y presentaremos ejemplos de cómo usarla.

No se puede optimizar lo que no se puede medir

El primer paso para acelerar una aplicación web lenta es averiguar en qué se invierte el tiempo. La medición del impacto en el tiempo de las áreas de código JavaScript es la forma ideal de identificar los hotspots, que constituye el primer paso para encontrar cómo mejorar el rendimiento. Afortunadamente, la API de User Timing te permite insertar llamadas a la API en diferentes partes de tu JavaScript y, luego, extraer datos detallados sobre el tiempo que se pueden usar para optimizar la campaña.

Tiempo de alta resolución y now()

Una parte fundamental de la medición exacta del tiempo es la precisión. En el pasado, los tiempos se basan en mediciones de milisegundos, lo cual está bien, pero crear un sitio de 60 FPS sin bloqueos implica que cada fotograma se debe dibujar en 16 ms. Cuando solo tienes milisegundos de precisión, carecen de la precisión necesaria para un buen análisis. Ingresa High Resolution Time, un nuevo tipo de tiempo integrado en los navegadores modernos. El tiempo de alta resolución nos proporciona marcas de tiempo de punto flotante que pueden ser precisas con una resolución de microsegundos, mil veces mejor que antes.

Para obtener la hora actual en tu aplicación web, llama al método now(), que forma una extensión de la interfaz Performance. En el siguiente código, se muestra cómo hacerlo:

var myTime = window.performance.now();

Hay otra interfaz llamada PerformanceTiming que proporciona varias veces diferentes relacionadas con la forma en que se carga tu aplicación web. El método now() muestra el tiempo transcurrido desde que ocurrió la hora navigationStart en PerformanceTiming.

El tipo DOMHighResTimeStamp

Si intentas cronometrar aplicaciones web en el pasado, deberías usar algo como Date.now(), que muestra una DOMTimeStamp. DOMTimeStamp devuelve un número entero de milisegundos como su valor. Para proporcionar la mayor precisión necesaria para el tiempo de alta resolución, se introdujo un nuevo tipo llamado DOMHighResTimeStamp. Este tipo es un valor de punto flotante que también muestra la hora en milisegundos. Sin embargo, como se trata de un punto flotante, el valor puede representar milisegundos fraccionarios, por lo que puede obtener una precisión de milésima de milisegundo.

La interfaz de User Timing

Ahora que tenemos marcas de tiempo de alta resolución, vamos a usar la interfaz User Timing para extraer información sobre el tiempo.

La interfaz de User Timing proporciona funciones que nos permiten llamar a métodos en diferentes lugares de nuestra aplicación que pueden proporcionar un recorrido de ruta de navegación al estilo Hansel y Gretel para permitirnos hacer un seguimiento del tiempo que se invierte.

Usa mark()

El método mark() es la herramienta principal de nuestro kit de herramientas de análisis de tiempo. Lo que mark() hace es almacenar una marca de tiempo para nosotros. Lo que es muy útil de mark() es que podemos asignar un nombre a la marca de tiempo, y la API recordará el nombre y la marca de tiempo como una sola unidad.

Llamar a mark() en varios lugares de tu aplicación te permite averiguar cuánto tiempo tardaste en alcanzar esa "marca" en la aplicación web.

En la especificación, se mencionan varios nombres sugeridos para las marcas que pueden ser interesantes y que requieren bastante explicación, como mark_fully_loaded, mark_fully_visible, mark_above_the_fold, etc.

Por ejemplo, podríamos establecer una marca para cuando la aplicación esté completamente cargada con el siguiente código:

window.performance.mark('mark_fully_loaded');

Al configurar marcas con nombre en toda nuestra aplicación web, podemos recopilar una gran cantidad de datos de tiempo y analizarlos cuando lo desees para averiguar qué hace la aplicación y cuándo.

Calcula las mediciones con measure()

Una vez que hayas establecido muchas marcas de tiempo, querrás averiguar el tiempo transcurrido entre ellas. Para ello, usa el método measure().

El método measure() calcula el tiempo transcurrido entre las marcas y también puede medir el tiempo entre la marca y cualquiera de los nombres de eventos conocidos en la interfaz de PerformanceTiming.

Por ejemplo, puedes averiguar el tiempo que transcurre desde que se completa el DOM hasta que el estado de tu aplicación se carga por completo usando código como el siguiente:

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

Cuando llamas a measure(), este almacena el resultado independientemente de las marcas que estableces para que puedas recuperarlos más tarde. Cuando se almacenan los tiempos de ausencia mientras se ejecuta la aplicación, esta permanece responsiva y puedes volcar todos los datos una vez que la aplicación haya terminado algún trabajo para poder analizarlos más adelante.

Descartando marcas con clearMarks()

A veces, es útil deshacerse de muchas marcas que configuraste. Por ejemplo, puedes realizar ejecuciones por lotes en tu aplicación web y, por lo tanto, quieres comenzar de nuevo en cada ejecución.

Llama a clearMarks() para deshacerte de las marcas que hayas configurado con facilidad.

Por lo tanto, el siguiente código de ejemplo borraría todas las marcas existentes, de modo que puedes volver a configurar una ejecución de tiempo si lo deseas.

window.performance.clearMarks();

Por supuesto, hay algunas situaciones en las que quizás no quieras borrar todas tus marcas. Así que si quieres deshacerte de marcas específicas, simplemente puedes pasar el nombre de la marca que deseas eliminar. Por ejemplo, el siguiente código:

window.peformance.clearMarks('mark_fully_loaded');

se deshace de la marca que establecimos en el primer ejemplo y deja cualquier otra marca sin cambios.

Es posible que también quieras deshacerte de las medidas que tomaste y hay un método correspondiente para hacerlo llamado clearMeasures(). Funciona exactamente igual que clearMarks(), pero funciona en las mediciones que hayas realizado. Por ejemplo, el siguiente código:

window.performance.clearMeasures('measure_load_from_dom');

Se quitará la medida que tomamos en el ejemplo de measure() anterior. Si deseas quitar todas las mediciones, funciona igual que clearMarks(), ya que solo debes llamar a clearMeasures() sin argumentos.

Cómo obtener los datos de tiempo

Está bien establecer marcas y medir intervalos, pero en algún momento querrás tener esos datos de tiempo para realizar algún análisis. Esto también es muy simple: solo debes usar la interfaz PerformanceTimeline.

Por ejemplo, el método getEntriesByType() nos permite obtener todos los tiempos de marca, o todas nuestras mediciones agotan el tiempo de espera como una lista, para que podamos iterar sobre ella y digerir los datos. Lo bueno es que la lista se devuelve en orden cronológico, de modo que puedas ver las marcas en el orden en que se alcanzaron en tu aplicación web.

Este código es el siguiente:

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

nos devuelve una lista de todas las marcas que se han alcanzado en nuestra aplicación web, mientras que el código:

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

nos muestra una lista de todas las medidas que tomamos.

También puedes obtener una lista de entradas con el nombre específico que les asignaste. Por ejemplo, el código:

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

nos muestra una lista con un elemento que contiene la marca de tiempo "mark_fully_charge" en la propiedad startTime.

Cuándo se realiza una solicitud XHR (ejemplo)

Ahora que tenemos una imagen adecuada de la API de User Timing, podemos usarla para analizar cuánto tiempo tardan todas las XMLHttpRequests en nuestra aplicación web.

Primero, modificaremos todas nuestras solicitudes send() para emitir una llamada a función que establezca las marcas y, al mismo tiempo, cambiaremos nuestras devoluciones de llamada de éxito con una llamada a función que establezca otra marca y, luego, genere una medición de cuánto tiempo tardó la solicitud.

Por lo general, nuestra XMLHttpRequest debería verse de la siguiente manera:

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

En nuestro ejemplo, agregaremos un contador global para hacer un seguimiento de la cantidad de solicitudes y también lo usaremos para almacenar una medida por cada solicitud realizada. El código para hacer esto se ve así:

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();

El código anterior genera una medida con un valor de nombre único para cada XMLHttpRequest que enviamos. Suponemos que las solicitudes se ejecutan en secuencia; el código de las solicitudes paralelas tendría que ser un poco más complejo para manejar las solicitudes que se devuelven desordenadas. Dejaremos eso como un ejercicio para el lector.

Una vez que la aplicación web haya realizado muchas solicitudes, podríamos volcarlas todas en la consola con el siguiente código:

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');
}

Conclusión

La API de User Timing te ofrece muchísimas herramientas útiles para aplicar en cualquier aspecto de tu aplicación web. Se puede reducir fácilmente los puntos problemáticos en tu aplicación si dispersas las llamadas a la API en toda tu aplicación web y realizas un procesamiento posterior de los datos de latencia generados para crear una imagen clara de en qué se invierte el tiempo. Pero ¿qué sucede si tu navegador no admite esta API? No hay problema. Aquí puedes encontrar un polyfill excelente que emula muy bien la API y también funciona bien con webpagetest.org. ¿Qué está esperando? Prueba la API de User Timing en tus aplicaciones ahora. Descubrirás cómo acelerarlas y los usuarios te agradecerán por hacer que su experiencia sea mucho mejor.