Métricas personalizadas

Tener métricas universales centradas en el usuario que puedas medir en cualquier sitio web puede ser muy útil para comprender la experiencia de los usuarios en la Web y comparar tu sitio con el de la competencia. Sin embargo, en muchos casos, deberás medir más que solo las métricas universales para capturar la experiencia completa de tu sitio específico.

Las métricas personalizadas te permiten medir aspectos de la experiencia de tu sitio que podrían solo aplicarse a tu sitio, como los siguientes:

  • Cuánto tarda una app de una sola página (SPA) en pasar de una "página" a otra.
  • El tiempo que tarda una página en mostrar los datos recuperados de una base de datos para los usuarios que accedieron.
  • El tiempo que tarda una app renderizada del servidor (SSR) en hidratarse.
  • La tasa de aciertos de caché de 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, tuvieron que recurrir a hackeos para medir si un sitio tuvo un buen rendimiento. Por ejemplo, puedes determinar si el subproceso principal está bloqueado por tareas de JavaScript de larga duración ejecutando un bucle requestAnimationFrame y calculando el delta entre cada fotograma. Si el delta es mucho más largo que la velocidad de fotogramas de la pantalla, puedes informarlo como una tarea larga.

Sin embargo, este tipo de hackeos pueden afectar el rendimiento de tu sitio, por ejemplo, agotando la batería del dispositivo. Si tus técnicas de medición del rendimiento causan problemas de rendimiento por sí mismas, los datos que obtengas de ellas no serán precisos. Por lo tanto, recomendamos usar una de las siguientes APIs para crear métricas personalizadas.

API de Performance Observer

Navegadores compatibles

  • 52.
  • 79
  • 57
  • 11

Origen

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

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 con el rendimiento de la página.

Cuando crees un PerformanceObserver, pásale una devolución de llamada que se ejecute cada vez que se envíen entradas de rendimiento nuevas. Luego, usa el método observe() para indicarle al observador qué tipos de entradas detectar, de la siguiente manera:

// Catch errors that some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  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'});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

En las siguientes secciones, se enumeran todos los tipos de entradas que puedes observar. En los navegadores más nuevos, también puedes inspeccionar qué tipos de entradas están disponibles mediante la propiedad estática PerformanceObserver.supportedEntryTypes.

Observa las entradas que ya ocurrieron

De forma predeterminada, los objetos PerformanceObserver solo pueden observar las entradas a medida que ocurren. Esto puede causar problemas si deseas realizar una carga diferida de tu código de estadísticas de rendimiento para que no bloquee los recursos de mayor prioridad.

Para obtener entradas históricas, llama a observe con la marca buffered establecida en true. Luego, el navegador incluye entradas históricas de su búfer de entrada de rendimiento la primera vez que se llama a la devolución de llamada a PerformanceObserver.

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

APIs de rendimiento heredadas que se deben evitar

Antes de la API de Performance Observer, los desarrolladores podían acceder a las entradas de rendimiento con los siguientes métodos definidos en el objeto performance. No recomendamos su uso porque no te permiten escuchar entradas nuevas.

Además, muchas APIs nuevas (como las tareas largas) no se exponen a través del objeto performance, solo por PerformanceObserver. Por lo tanto, a menos que necesites compatibilidad con Internet Explorer específicamente, se recomienda evitar estos métodos en tu código y usar PerformanceObserver de ahora en adelante.

API de User Timing

La API de User Timing es una API de medición de uso general para métricas basadas en el tiempo. Te permite marcar de forma arbitraria puntos en el tiempo 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, la API de User Timing se integra mejor en las herramientas de rendimiento. Por ejemplo, las Herramientas para desarrolladores de Chrome visualizan las mediciones de la sincronización del usuario en el panel de rendimiento, y muchos proveedores de estadísticas realizan un seguimiento automático de todas las mediciones que realizas y envían los datos de duración a su backend de estadísticas.

Para informar las mediciones de User Timing, registra un PerformanceObserver para observar las entradas de tipo measure:

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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.
  po.observe({type: 'measure', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

API de Long Tasks

Navegadores compatibles

  • 58
  • 79
  • x
  • x

Origen

La API de Long Tasks es útil para determinar cuándo se bloquea el subproceso principal del navegador el tiempo suficiente para afectar la velocidad de fotogramas o la latencia de entrada. La API informa las tareas que se ejecutan durante más de 50 milisegundos (ms).

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 compilan sobre la API de Long Tasks (como Tiempo de carga (TTI) y Tiempo de bloqueo total (TBT).

Para determinar cuándo ocurren las tareas largas, registra un PerformanceObserver para observar las entradas de tipo longtask:

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

API de Element Timing

Navegadores compatibles

  • 77
  • 79
  • x
  • x

Origen

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

Para estos casos, usa la API de Element Timing. La API de LCP se basa en la API de Element Timing y agrega informes automáticos del elemento con contenido más grande, pero también puedes generar informes sobre otros elementos agregando 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>
// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}
</script>

API de Event Timing

La métrica Retraso de primera entrada (FID) mide el tiempo que transcurre desde que un usuario interactúa por primera vez con una página hasta que el navegador puede comenzar a procesar los controladores de eventos en respuesta a esa interacción. Sin embargo, en algunos casos, también puede ser útil medir el tiempo de procesamiento del evento en sí.

Esto es posible con la API de Event Timing, que, además de medir el FID, también expone distintas marcas de tiempo en el ciclo de vida del evento, incluidas las siguientes:

  • startTime: Es la hora en que el navegador recibe el evento.
  • processingStart: Es el momento en que el navegador puede comenzar a procesar los controladores de eventos para el evento.
  • processingEnd: Es la hora en 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 ms por motivos de seguridad) que transcurre entre el momento en que el navegador recibe el evento y el momento en 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:

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  const po = new PerformanceObserver((entryList) => {
    const firstInput = entryList.getEntries()[0];

    // Measure First Input Delay (FID).
    const firstInputDelay = firstInput.processingStart - firstInput.startTime;

    // Measure the time it takes to run all event handlers
    // Doesn't include work scheduled asynchronously using methods like
    // `requestAnimationFrame()` or `setTimeout()`.
    const firstInputProcessingTime = firstInput.processingEnd - firstInput.processingStart;

    // Measure the entire duration of the event, from when input is received by
    // the browser until the next frame can be painted after processing all
    // event handlers.
    // Doesn't include work scheduled asynchronously using
    // `requestAnimationFrame()` or `setTimeout()`.
    // For security reasons, this value is rounded to the nearest 8 ms.
    const firstInputDuration = firstInput.duration;

    // Log these values to the console.
    console.log({
      firstInputDelay,
      firstInputProcessingTime,
      firstInputDuration,
    });
  });

  po.observe({type: 'first-input', buffered: true});
} catch (error) {
  // Do nothing if the browser doesn't support this API.
}

API de Resource Timing

La API de Resource Timing proporciona a los desarrolladores estadísticas detalladas 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 datos de tiempo (aunque hay mucho de eso). Entre otros datos a los que puedes acceder, se incluyen los siguientes:

  • initiatorType: Indica cómo se recuperó el recurso, por ejemplo, desde una etiqueta <script> o <link>, o desde fetch().
  • nextHopProtocol: el protocolo que se usa para recuperar el recurso, como h2 o quic
  • encodedBodySize y 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 se entregan los recursos con la caché, este valor puede ser mucho menor que encodedBodySize y, en algunos casos, puede ser cero si no se requiere una revalidación de caché.

Puedes usar la propiedad transferSize de las entradas de tiempo de los recursos 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 cual puede ser útil para comprender cómo tu estrategia de almacenamiento en caché de recursos afecta el rendimiento de visitantes recurrentes.

En el siguiente ejemplo, se registran todos los recursos que solicita la página y se indica si cada recurso se entregó mediante la caché:

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // If transferSize is 0, the resource was fulfilled via the cache.
      console.log(entry.name, entry.transferSize === 0);
    }
  });
  // Start listening for `resource` entries to be dispatched.
  po.observe({type: 'resource', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Navegadores compatibles

  • 57
  • 12
  • 58
  • 15

Origen

La API de Navigation Timing es similar a la API de Resource Timing, pero solo informa 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 a la que muchos desarrolladores hacen seguimiento para comprender el tiempo de respuesta del servidor, el tiempo hasta el primer byte (TTFB), está disponible a través de la marca de tiempo responseStart en la API de Navigation Timing.

// Catch errors since  browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

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

El tiempo de inicio del service worker para una solicitud de navegación específica se puede determinar a partir del delta entre entry.responseStart y entry.workerStart de la siguiente manera:

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

API de Server Timing

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

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

Para especificar los datos de sincronización del servidor en tus respuestas, usa el encabezado de respuesta Server-Timing. Por 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.

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}