Cómo evaluar el rendimiento de carga en el campo con Navigation Timing y Resource Timing

Aprende los aspectos básicos del uso de las APIs de Navigation y Resource Timing para evaluar el rendimiento de carga en el campo.

Fecha de publicación: 8 de octubre de 2021

Si usaste la limitación de la conexión en el panel de red de las herramientas para desarrolladores de un navegador (o Lighthouse en Chrome) para evaluar el rendimiento de carga, sabes lo convenientes que son esas herramientas para ajustar el rendimiento. Puedes medir rápidamente el impacto de las optimizaciones de rendimiento con una velocidad de conexión de referencia coherente y estable. El único problema es que se trata de pruebas sintéticas, que generan datos de laboratorio, no datos de campo.

Las pruebas sintéticas no son malas en sí, pero no son representativas de la velocidad con la que se carga tu sitio web para los usuarios reales. Para ello, se requieren datos de campo, que puedes recopilar de las APIs de Navigation Timing y Resource Timing.

APIs para ayudarte a evaluar el rendimiento de la carga en el campo

Navigation Timing y Resource Timing son dos APIs similares con una superposición significativa que miden dos aspectos distintos:

  • El tiempo de navegación mide la velocidad de las solicitudes de documentos HTML (es decir, las solicitudes de navegación).
  • Resource Timing mide la velocidad de las solicitudes de recursos que dependen de un documento, como CSS, JavaScript, imágenes y otros tipos de recursos.

Estas APIs exponen sus datos en un búfer de entrada de rendimiento, al que se puede acceder en el navegador con JavaScript. Existen varias formas de consultar un búfer de rendimiento, pero una forma común es usar performance.getEntriesByType:

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

performance.getEntriesByType acepta una cadena que describe el tipo de entradas que deseas recuperar del búfer de entrada de rendimiento. 'navigation' y 'resource' recuperan los tiempos de las APIs de Navigation Timing y Resource Timing, respectivamente.

La cantidad de información que proporcionan estas APIs puede ser abrumadora, pero son clave para medir el rendimiento de carga en el campo, ya que puedes recopilar estos tiempos de los usuarios a medida que visitan tu sitio web.

La vida y los tiempos de una solicitud de red

Recopilar y analizar los tiempos de navegación y recursos es algo así como arqueología, ya que reconstruyes la vida efímera de una solicitud de red después de que ocurre. En ocasiones, resulta útil visualizar conceptos y, en lo que respecta a las solicitudes de red, las herramientas para desarrolladores de tu navegador pueden ser de utilidad.

Tiempos de red como se muestran en las herramientas para desarrolladores de Chrome. Los tiempos que se muestran son para la cola de solicitudes, la negociación de conexión, la solicitud en sí y la respuesta en barras con codificación de colores.
Visualización de una solicitud de red en el panel de red de las Herramientas para desarrolladores de Chrome

El ciclo de vida de una solicitud de red tiene fases distintas, como la búsqueda de DNS, el establecimiento de la conexión, la negociación de TLS y otras fuentes de latencia. Estos tiempos se representan como un DOMHighResTimestamp. Según el navegador, el nivel de detalle de los tiempos puede ser hasta microsegundos o redondearse a milisegundos. Te recomendamos que examines estas fases en detalle y cómo se relacionan con los tiempos de navegación y de recursos.

búsqueda de DNS

Cuando un usuario va a una URL, se consulta el sistema de nombres de dominio (DNS) para traducir un dominio a una dirección IP. Este proceso puede llevar bastante tiempo; incluso, tiempo que querrás medir en el campo. Navigation Timing y Resource Timing exponen dos tiempos relacionados con el DNS:

  • domainLookupStart es cuando comienza la búsqueda de DNS.
  • domainLookupEnd es cuando finaliza la búsqueda de DNS.

Para calcular el tiempo total de búsqueda de DNS, resta la métrica de inicio de la métrica de finalización:

// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;

Negociación de conexión

Otro factor que contribuye al rendimiento de la carga es la negociación de conexión, que es la latencia que se genera cuando te conectas a un servidor web. Si se incluye HTTPS, este proceso también incluirá el tiempo de negociación de TLS. La fase de conexión consta de tres tiempos:

  • connectStart es cuando el navegador comienza a abrir una conexión a un servidor web.
  • secureConnectionStart marca cuándo el cliente comienza la negociación de TLS.
  • connectEnd es cuando se estableció la conexión con el servidor web.

Medir el tiempo de conexión total es similar a medir el tiempo de búsqueda de DNS total: se resta el tiempo de inicio del tiempo de finalización. Sin embargo, hay una propiedad secureConnectionStart adicional que puede ser 0 si no se usa HTTPS o si la conexión es persistente. Si desea medir el tiempo de negociación de TLS, deberá tenerlo en cuenta:

// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}

Una vez que finaliza la búsqueda de DNS y la negociación de conexión, entran en juego los tiempos relacionados con la recuperación de documentos y sus recursos dependientes.

Solicitudes y respuestas

El rendimiento de la carga se ve afectado por dos tipos de factores:

  • Factores extrínsecos: Son elementos como la latencia y el ancho de banda. Además de elegir una empresa de hosting y posiblemente una CDN, están (en su mayoría) fuera de nuestro control, ya que los usuarios pueden acceder a la Web desde cualquier lugar.
  • Factores intrínsecos: Son aspectos como las arquitecturas del servidor y del cliente, el tamaño de los recursos y nuestra capacidad de realizar optimizaciones en función de esos elementos que están bajo nuestro control.

Ambos tipos de factores afectan el rendimiento de carga. Los tiempos relacionados con estos factores son vitales, ya que describen cuánto tardan los recursos en descargarse. Tanto Navigation Timing como Resource Timing describen el rendimiento de carga con las siguientes métricas:

  • fetchStart marca el momento en que el navegador comienza a recuperar un recurso (tiempos de los recursos) o un documento para una solicitud de navegación (tiempos de navegación). Esto precede a la solicitud real y es el punto en el que el navegador verifica las cachés (por ejemplo, instancias HTTP y Cache).
  • workerStart marca cuándo se comienza a controlar una solicitud en el controlador de eventos fetch de un service worker. Este será 0 cuando ningún service worker controle la página actual.
  • requestStart es cuando el navegador realiza la solicitud.
  • responseStart es cuando llega el primer byte de la respuesta.
  • responseEnd es cuando llega el último byte de la respuesta.

Estos tiempos te permiten medir varios aspectos del rendimiento de carga, como la búsqueda en caché dentro de un trabajador de servicio y el tiempo de descarga:

// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}

También puedes medir otros aspectos de la latencia de solicitud y respuesta:

const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;

Otras mediciones que puedes hacer

Los tiempos de navegación y de recursos son útiles para mucho más de lo que se describe en los ejemplos anteriores. Estas son otras situaciones con horarios relevantes que podrían ser útiles explorar:

  • Redireccionamientos de página: Los redireccionamientos son una fuente ignorada de latencia adicional, especialmente las cadenas de redireccionamiento. La latencia se agrega de varias maneras, como los saltos de HTTP a HTTPs, así como los redireccionamientos 302 o 301 no almacenados en caché. Los tiempos de redirectStart, redirectEnd y redirectCount son útiles para evaluar la latencia del redireccionamiento.
  • Descarga de documentos: En las páginas que ejecutan código en un controlador de eventos unload, el navegador debe ejecutar ese código antes de poder navegar a la página siguiente. unloadEventStart y unloadEventEnd miden la descarga de documentos.
  • Procesamiento de documentos: El tiempo de procesamiento de documentos puede no ser significativo, a menos que tu sitio web envíe cargas útiles HTML muy grandes. Si esta es tu situación, es posible que te interesen los tiempos de domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd y domComplete.

Cómo obtener tiempos en tu código

En todos los ejemplos que se mostraron hasta ahora, se usa performance.getEntriesByType, pero hay otras formas de consultar el búfer de entrada de rendimiento, como performance.getEntriesByName y performance.getEntries. Estos métodos son adecuados cuando solo se necesita un análisis ligero. Sin embargo, en otras situaciones, pueden generar un trabajo excesivo del subproceso principal iterando sobre una gran cantidad de entradas o incluso sondeando repetidamente el búfer de rendimiento para encontrar entradas nuevas.

El enfoque recomendado para recopilar entradas del búfer de entrada de rendimiento es usar un PerformanceObserver. PerformanceObserver escucha entradas de rendimiento y las proporciona a medida que se agregan al búfer:

// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});

Este método de recopilación de tiempos puede resultar incómodo en comparación con el acceso directo al búfer de entrada de rendimiento, pero es preferible vincular el subproceso principal con un trabajo que no tenga un propósito crítico y orientado al usuario.

Cómo realizar una llamada de retorno

Una vez que hayas recopilado todos los tiempos que necesitas, puedes enviarlos a un extremo para un análisis más detallado. Existen dos maneras de hacerlo: con navigator.sendBeacon o un fetch con la opción keepalive establecida. Ambos métodos enviarán una solicitud a un extremo especificado de forma no bloqueante, y la solicitud se pondrá en cola de manera que sobreviva a la sesión de la página actual si es necesario:

// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
  // Caution: If you have lots of performance entries, don't
  // do this. This is an example for illustrative purposes.
  const data = JSON.stringify(performance.getEntries());

  // Send the data!
  navigator.sendBeacon('/analytics', data);
}

En este ejemplo, la cadena JSON llegará en una carga útil POST que puedes decodificar, procesar y almacenar en un backend de la aplicación según sea necesario.

Conclusión

Una vez que hayas recopilado las métricas, depende de ti descubrir cómo analizar esos datos de campo. Cuando analices datos de campo, hay algunas reglas generales que debes seguir para asegurarte de llegar a conclusiones significativas:

  • Evita los promedios, ya que no son representativos de la experiencia de ningún usuario y pueden estar sesgados por valores atípicos.
  • Usa percentiles. En los conjuntos de datos de métricas de rendimiento basadas en el tiempo, cuanto menor es mejor. Esto significa que, cuando priorizas los percentiles bajos, solo prestas atención a las experiencias más rápidas.
  • Prioriza la cola larga de valores. Cuando priorizas experiencias en el percentil 75 o más alto, pones tu enfoque en lo que corresponde: en las experiencias más lentas.

El objetivo de esta guía no es ser un recurso exhaustivo sobre Navigation o Resource Timing, sino un punto de partida. Estos son algunos recursos adicionales que pueden resultarte útiles:

Con estas APIs y los datos que proporcionan, estarás mejor equipado para comprender cómo experimentan los usuarios reales el rendimiento de carga, lo que te dará más confianza en el diagnóstico y la resolución de problemas de rendimiento de carga en el campo.