Métricas personalizadas

Las métricas centradas en el usuario que puedes medir de forma universal en cualquier sitio web son muy valiosas. Estas métricas te permiten hacer lo siguiente:

  • Comprender cómo los usuarios reales experimentan la Web en su totalidad
  • Compara tu sitio con el de un competidor.
  • Realiza un seguimiento de los datos útiles y prácticos en tus herramientas de análisis sin necesidad de escribir código personalizado.

Las métricas universales ofrecen un buen valor de referencia, pero, en muchos casos, debes medir más que estas métricas para captar la experiencia completa de tu sitio en particular.

Las métricas personalizadas te permiten medir aspectos de la experiencia de tu sitio que solo se aplican a él, como los siguientes:

  • Es el tiempo que tarda una aplicación de una sola página (SPA) en pasar de una "página" a otra.
  • Es el tiempo que tarda una página en mostrar los datos recuperados de una base de datos para los usuarios que accedieron a su cuenta.
  • Cuánto tiempo tarda una app renderizada del servidor (SSR) en hidratarse.
  • Es la tasa de aciertos de caché para los recursos que cargan los visitantes recurrentes.
  • Es la latencia de los eventos de clic o teclado en un juego.

APIs para medir métricas personalizadas

Históricamente, los desarrolladores web no han tenido muchas APIs de bajo nivel para medir el rendimiento y, como resultado, han tenido que recurrir a soluciones alternativas para medir si un sitio funcionaba bien.

Por ejemplo, es posible determinar si el subproceso principal está bloqueado debido a tareas de JavaScript de larga duración ejecutando un bucle requestAnimationFrame y calculando el delta entre cada fotograma. Si el delta es significativamente más largo que la velocidad de fotogramas de la pantalla, puedes informarlo como una tarea larga. Sin embargo, no se recomiendan estos trucos, ya que afectan el rendimiento (por ejemplo, agotan la batería).

La primera regla de la medición del rendimiento eficaz es asegurarse de que las técnicas de medición del rendimiento no causen problemas de rendimiento por sí mismas. Por lo tanto, para cualquier métrica personalizada que midas en tu sitio, es mejor usar una de las siguientes APIs, si es posible.

API de Performance Observer

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 57.
  • Safari: 11.

Source

La API de Performance Observer es el mecanismo que recopila y muestra datos de todas las demás APIs de rendimiento que se describen en esta página. Comprenderlo es fundamental para obtener datos de calidad.

Puedes usar PerformanceObserver para suscribirte de forma pasiva a eventos relacionados con el rendimiento. Esto permite que las devoluciones de llamada de la API se activen durante los períodos de inactividad, lo que significa que, por lo general, no interferirán en el rendimiento de la página.

Para crear un objeto PerformanceObserver, pásale una devolución de llamada que se ejecute cada vez que se envíen entradas de rendimiento nuevas. Luego, le indicas al observador qué tipos de entradas debe escuchar con el método observe():

const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Log the entry and all associated details.
    console.log(entry.toJSON());
  }
});

po.observe({type: 'some-entry-type'});

En las siguientes secciones, se enumeran todos los tipos de entrada disponibles para la observación, pero, en los navegadores más nuevos, puedes inspeccionar qué tipos de entrada están disponibles a través de la propiedad estática PerformanceObserver.supportedEntryTypes.

Observa las entradas que ya sucedieron

De forma predeterminada, los objetos PerformanceObserver solo pueden observar las entradas a medida que ocurren. Esto puede causar problemas si deseas cargar de forma diferida tu código de análisis de rendimiento para que no bloquee los recursos de mayor prioridad.

Para obtener entradas históricas (después de que se hayan producido), establece la marca buffered en true cuando llames a observe(). El navegador incluirá entradas históricas de su búfer de entradas de rendimiento la primera vez que se llame a tu devolución de llamada PerformanceObserver, hasta el tamaño máximo del búfer para ese tipo.

po.observe({
  type: 'some-entry-type',
  buffered: true,
});

APIs de rendimiento heredadas que debes evitar

Antes de la API de Performance Observer, los desarrolladores podían acceder a las entradas de rendimiento con los siguientes tres métodos definidos en el objeto performance:

Si bien estas APIs aún son compatibles, no se recomienda su uso porque no te permiten detectar cuándo se emiten nuevas entradas. Además, muchas APIs nuevas (como largest-contentful-paint) no se exponen a través del objeto performance, sino solo a través de PerformanceObserver.

A menos que necesites específicamente compatibilidad con Internet Explorer, es mejor evitar estos métodos en tu código y usar PerformanceObserver en el futuro.

API de User Timing

Browser Support

  • Chrome: 28.
  • Edge: 12.
  • Firefox: 38.
  • Safari: 11.

Source

La API de User Timing es una API de medición de propósito general para las métricas basadas en el tiempo. Te permite marcar puntos en el tiempo de forma arbitraria y, luego, medir la duración entre esas marcas.

// Record the time immediately before running a task.
performance.mark('myTask:start');
await doMyTask();

// Record the time immediately after running a task.
performance.mark('myTask:end');

// Measure the delta between the start and end of the task
performance.measure('myTask', 'myTask:start', 'myTask:end');

Si bien las APIs como Date.now() o performance.now() te brindan capacidades similares, el beneficio de usar la API de User Timing es que se integra bien con las herramientas de rendimiento. Por ejemplo, las Herramientas para desarrolladores de Chrome visualizan las mediciones de User Timing en el panel Rendimiento, y muchos proveedores de análisis también harán un seguimiento automático de las mediciones que realices y enviarán los datos de duración a su backend de análisis.

Para informar las mediciones de User Timing, puedes usar PerformanceObserver y registrarte para observar las entradas de tipo measure:

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Log the entry and all associated details.
    console.log(entry.toJSON());
  }
});

// Start listening for `measure` entries to be dispatched.
po.observe({type: 'measure', buffered: true});

API de Long Tasks

Browser Support

  • Chrome: 58.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

La API de Long Tasks es útil para saber cuándo el subproceso principal del navegador está bloqueado durante el tiempo suficiente como para afectar la frecuencia de fotogramas o la latencia de entrada. La API informará cualquier tarea que se ejecute durante más de 50 milisegundos.

Cada vez que necesites ejecutar código costoso o cargar y ejecutar secuencias de comandos grandes, es útil hacer un seguimiento para saber si ese código bloquea el subproceso principal. De hecho, muchas métricas de nivel superior se basan en la API de Long Tasks (como Time to Interactive [TTI] y Total Blocking Time [TBT]).

Para determinar cuándo ocurren las tareas largas, puedes usar PerformanceObserver y registrarte para observar las entradas de tipo longtask:

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Log the entry and all associated details.
    console.log(entry.toJSON());
  }
});

// Start listening for `longtask` entries to be dispatched.
po.observe({type: 'longtask', buffered: true});

API de Long Animation Frames

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: not supported.
  • Safari: not supported.

Source

La API de Long Animation Frames es una nueva iteración de la API de Long Tasks que analiza los fotogramas largos, en lugar de las tareas largas, de más de 50 milisegundos. Esto aborda algunas deficiencias de la API de Long Tasks, como una mejor atribución y un alcance más amplio de las demoras potencialmente problemáticas.

Para determinar cuándo se producen los fotogramas largos, puedes usar PerformanceObserver y registrarte para observar las entradas de tipo long-animation-frame:

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Log the entry and all associated details.
    console.log(entry.toJSON());
  }
});

// Start listening for `long-animation-frame` entries to be dispatched.
po.observe({type: 'long-animation-frame', buffered: true});

API de Element Timing

Browser Support

  • Chrome: 77.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

La métrica Largest Contentful Paint (LCP) es útil para saber cuándo se pintó la imagen o el bloque de texto más grande en la pantalla, pero, en algunos casos, deseas medir el tiempo de renderización de un elemento diferente.

En estos casos, usa la API de Element Timing. En realidad, la API de LCP se basa en la API de Element Timing y agrega informes automáticos del elemento más grande con contenido, pero también puedes generar informes sobre otros elementos agregándoles explícitamente el atributo elementtiming y registrando un PerformanceObserver para observar el tipo de entrada element.

<img elementtiming="hero-image" />
<p elementtiming="important-paragraph">This is text I care about.</p>
<!-- ... -->

<script>
  const po = new PerformanceObserver((entryList) => {
    for (const entry of entryList.getEntries()) {
      // Log the entry and all associated details.
      console.log(entry.toJSON());
    }
  });

  // Start listening for `element` entries to be dispatched.
  po.observe({type: 'element', buffered: true});
</script>

API de Event Timing

Browser Support

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 89.
  • Safari: 26.2.

Source

La métrica Interaction to Next Paint (INP) evalúa la capacidad de respuesta general de la página observando todas las interacciones de clic, presión y teclado durante la vida útil de una página. El INP de una página suele ser la interacción que tardó más en completarse, desde el momento en que el usuario inició la interacción hasta el momento en que el navegador pinta el siguiente fotograma que muestra el resultado visual de la entrada del usuario.

La métrica del INP es posible gracias a la API de Event Timing. Esta API expone varias marcas de tiempo que ocurren durante el ciclo de vida del evento, incluidas las siguientes:

  • startTime: Es el momento en que el navegador recibe el evento.
  • processingStart: Es la hora en la que el navegador puede comenzar a procesar los controladores de eventos para el evento.
  • processingEnd: Es la hora en la que el navegador termina de ejecutar todo el código síncrono iniciado desde los controladores de eventos para este evento.
  • duration: Es el tiempo (redondeado a 8 milisegundos por motivos de seguridad) que transcurre desde que el navegador recibe el evento hasta que puede pintar el siguiente fotograma después de terminar de ejecutar todo el código síncrono iniciado desde los controladores de eventos.

En el siguiente ejemplo, se muestra cómo usar estos valores para crear mediciones personalizadas:

const po = new PerformanceObserver((entryList) => {
  // Get the last interaction observed:
  const entries = Array.from(entryList.getEntries()).forEach((entry) => {
    // Get various bits of interaction data:
    const inputDelay = entry.processingStart - entry.startTime;
    const processingTime = entry.processingEnd - entry.processingStart;
    const presentationDelay = entry.startTime + entry.duration - entry.processingEnd;
    const duration = entry.duration;
    const eventType = entry.name;
    const target = entry.target || "(not set)"

    console.log("----- INTERACTION -----");
    console.log(`Input delay (ms): ${inputDelay}`);
    console.log(`Event handler processing time (ms): ${processingTime}`);
    console.log(`Presentation delay (ms): ${presentationDelay}`);
    console.log(`Total event duration (ms): ${duration}`);
    console.log(`Event type: ${eventType}`);
    console.log(target);
  });
});

// A durationThreshold of 16ms is necessary to include more
// interactions, since the default is 104ms. The minimum
// durationThreshold is 16ms.
po.observe({type: 'event', buffered: true, durationThreshold: 16});

API de Resource Timing

Browser Support

  • Chrome: 29.
  • Edge: 12.
  • Firefox: 35.
  • Safari: 11.

Source

La API de Resource Timing brinda a los desarrolladores información detallada sobre cómo se cargaron los recursos de una página en particular. A pesar del nombre de la API, la información que proporciona no se limita solo a los datos de sincronización (aunque hay muchos de esos datos). Otros datos a los que puedes acceder incluyen los siguientes:

  • initiatorType: Indica cómo se recuperó el recurso, por ejemplo, desde una etiqueta <script> o <link>, o desde una llamada fetch().
  • nextHopProtocol: Es el protocolo que se usa para recuperar el recurso, como h2 o quic.
  • encodedBodySize/decodedBodySize]: Tamaño del recurso en su forma codificada o decodificada (respectivamente)
  • transferSize: Es el tamaño del recurso que se transfirió realmente a través de la red. Cuando la caché proporciona los recursos, este valor puede ser mucho menor que el de encodedBodySize y, en algunos casos, puede ser cero (si no se requiere una revalidación de la caché).

Puedes usar la propiedad transferSize de las entradas de la API de Resource Timing para medir una métrica de tasa de aciertos de caché o una métrica de tamaño total de recursos almacenados en caché, lo que puede ser útil para comprender cómo tu estrategia de almacenamiento en caché de recursos afecta el rendimiento para los visitantes recurrentes.

En el siguiente ejemplo, se registran todos los recursos que solicitó la página y se indica si la caché satisfizo cada recurso.

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // If transferSize is 0, the resource was fulfilled using the cache.
    console.log(entry.name, entry.transferSize === 0);
  }
});

// Start listening for `resource` entries to be dispatched.
po.observe({type: 'resource', buffered: true});

Browser Support

  • Chrome: 57.
  • Edge: 12.
  • Firefox: 58.
  • Safari: 15.

Source

La API de Navigation Timing es similar a la API de Resource Timing, pero solo informa las solicitudes de navegación. El tipo de entrada navigation también es similar al tipo de entrada resource, pero contiene información adicional específica solo para las solicitudes de navegación (como cuando se activan los eventos DOMContentLoaded y load).

Una métrica que muchos desarrolladores supervisan para comprender el tiempo de respuesta del servidor (tiempo hasta el primer byte [TTFB]) está disponible a través de la API de Navigation Timing, específicamente, la marca de tiempo responseStart de la entrada.

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // If transferSize is 0, the resource was fulfilled using the cache.
    console.log('Time to first byte', entry.responseStart);
  }
});

// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});

Otra métrica que puede interesarles a los desarrolladores que usan service workers es el tiempo de inicio del service worker para las solicitudes de navegación. Es la cantidad de tiempo que tarda el navegador en iniciar el subproceso del service worker antes de que pueda comenzar a interceptar eventos de recuperación.

El tiempo de inicio del service worker para una solicitud de navegación en particular se puede determinar a partir del delta entre entry.responseStart y entry.workerStart.

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('Service Worker startup time:',
        entry.responseStart - entry.workerStart);
  }
});

// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});

API de Server Timing

Browser Support

  • Chrome: 65.
  • Edge: 79.
  • Firefox: 61.
  • Safari: 16.4.

Source

La API de Server Timing te permite pasar datos de tiempo específicos de la solicitud desde tu servidor al navegador a través de los encabezados de respuesta. Por ejemplo, puedes indicar cuánto tiempo tardó en buscar datos en una base de datos para una solicitud en particular, lo que puede ser útil para depurar problemas de rendimiento causados por la lentitud del servidor.

Para los desarrolladores que usan proveedores de estadísticas de terceros, la API de Server Timing es la única forma de correlacionar los datos de rendimiento del servidor con otras métricas comerciales que estas herramientas de estadísticas pueden medir.

Para especificar los datos de tiempo del servidor en tus respuestas, puedes usar el encabezado de respuesta Server-Timing. A continuación, se muestra un ejemplo.

HTTP/1.1 200 OK

Server-Timing: miss, db;dur=53, app;dur=47.2

Luego, desde tus páginas, puedes leer estos datos en las entradas resource o navigation de las APIs de Resource Timing y Navigation Timing.

// Create the performance observer.
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Logs all server timing data for this response
    console.log('Server Timing', entry.serverTiming);
  }
});

// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});