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

Aprende los conceptos 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 carga en el campo

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

  • Navigation Timing mide la velocidad de las solicitudes de documentos HTML (es decir, las solicitudes de navegación).
  • El tiempo de los recursos mide la velocidad de las solicitudes de recursos dependientes del 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 estas APIs proporcionan puede ser abrumadora, pero son la 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 de recursos es similar a la arqueología, ya que se reconstruye la vida fugaz de una solicitud de red después del hecho. A veces, es útil visualizar conceptos y, en el caso de las solicitudes de red, las herramientas para desarrolladores de tu navegador pueden ser de ayuda.

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 mucho tiempo, incluso el que quieras 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 carga es la negociación de conexión, que es la latencia que se genera cuando se conecta 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 ocurre cuando el navegador comienza a abrir una conexión a un servidor web.
  • secureConnectionStart marca el momento en que el cliente comienza la negociación de TLS.
  • connectEnd corresponde al momento en que 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 carga se ve afectado por dos tipos de factores:

  • Factores extremos: Estos son factores 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 elementos como las arquitecturas del servidor y del cliente, así como el tamaño de los recursos y nuestra capacidad de realizar optimizaciones para 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 cuándo el navegador comienza a recuperar un recurso (Resource Timing) o un documento para una solicitud de navegación (Navigation Timing). Esto precede a la solicitud real y es el punto en el que el navegador verifica las cachés (por ejemplo, HTTP y instancias de 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 ocurre cuando llega el primer byte de la respuesta.
  • responseEnd ocurre 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 medidas que puedes tomar

Navigation Timing y Resource Timing es útil para 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 de latencia adicional que se pasa por alto, en especial las cadenas de redireccionamientos. La latencia se agrega de varias maneras, como saltos de HTTP a HTTP, así como redireccionamientos 302/301 no almacenados en caché. Los tiempos redirectStart, redirectEnd y redirectCount son útiles para evaluar la latencia de 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: Es posible que el tiempo de procesamiento de los documentos no sea resultante, a menos que tu sitio web envíe cargas útiles de HTML muy grandes. Si esto describe tu situación, los tiempos domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd y domComplete pueden ser de interés.

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 detecta las 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 trabajo que no sirva para 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 analizas datos de campo, debes seguir algunas reglas generales para asegurarte de sacar conclusiones significativas:

  • Evita los promedios, ya que no representan la experiencia de ningún usuario y pueden estar sesgados por valores atípicos.
  • Confía en los percentiles. En los conjuntos de datos de métricas de rendimiento basadas en el tiempo, cuanto más bajo sea el valor, mejor. Esto significa que cuando priorizas percentiles bajos, solo prestas atención a las experiencias más rápidas.
  • Prioriza la cola larga de valores. Cuando priorizas las experiencias en el percentil 75 o superior, te enfocas en lo que corresponde: en las experiencias más lentas.

Esta guía no pretende ser un recurso exhaustivo sobre navegación o sincronización del recurso, sino un punto de partida. Estos son algunos recursos adicionales que podrían resultarte útiles:

Con estas APIs y los datos que proporcionan, estarás mejor preparado para comprender cómo experimentan el rendimiento de carga los usuarios reales, lo que te dará más confianza para diagnosticar y abordar los problemas de rendimiento de carga en el campo.