Supervisa el uso total de memoria de tu página web con MeasureUserAgentSpecificMemory()

Obtén información para medir el uso de memoria de tu página web en producción para detectar regresiones.

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

Los navegadores administran la memoria de las páginas web automáticamente. Cada vez que una página web crea un objeto, el navegador asigna un fragmento de memoria "detrás de escena" para almacenar el objeto. Dado que la memoria es un recurso finito, el navegador realiza la recolección de elementos no utilizados para detectar cuándo un objeto ya no es necesario y liberar el fragmento de memoria subyacente.

Sin embargo, la detección no es perfecta y se comprobó que una detección perfecta es una tarea imposible. Por lo tanto, los navegadores se aproximan a la noción de "se necesita un objeto" con la noción de "se puede acceder a un objeto". Si la página web no puede acceder a un objeto a través de sus variables y los campos de otros objetos accesibles, el navegador puede reclamar el objeto de forma segura. La diferencia entre estas dos nociones genera fugas de memoria, como se ilustra en el siguiente ejemplo.

const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);

Aquí ya no se necesita el array más grande b, pero el navegador no lo recupera porque todavía se puede acceder a él a través de object.b en la devolución de llamada. Por lo tanto, se pierde la memoria del array más grande.

Las fugas de memoria son prevalentes en la Web. Es fácil ingresar uno si olvidas cancelar el registro de un objeto de escucha de eventos, captura objetos de un iframe por accidente, no cierras un trabajador, acumula objetos en arrays, etcétera. Si una página web tiene fugas de memoria, su uso de memoria aumenta con el tiempo, y la página web parece lenta e inflada para los usuarios.

El primer paso para resolver este problema es medirlo. La nueva API de performance.measureUserAgentSpecificMemory() permite a los desarrolladores medir el uso de memoria de sus páginas web en producción y, por lo tanto, detectar las fugas de memoria que pasan por las pruebas locales.

¿En qué se diferencia performance.measureUserAgentSpecificMemory() de la API de performance.memory heredada?

Si conoces la API no estándar de performance.memory existente, es posible que te preguntes en qué se diferencia la API nueva. La principal diferencia es que la API anterior muestra el tamaño del montón de JavaScript, mientras que la API nueva estima la memoria que usa la página web. Esta diferencia se vuelve importante cuando Chrome comparte el mismo montón con varias páginas web (o varias instancias de la misma página). En esos casos, el resultado de la API anterior puede estar desactivado de manera arbitraria. Dado que la API anterior se define en términos específicos de la implementación, como "montón", estandarizarla es una tarea irrecuperable.

Otra diferencia es que la nueva API realiza la medición de la memoria durante la recolección de elementos no utilizados. Esto reduce el ruido en los resultados, pero puede tardar un tiempo en generarse. Ten en cuenta que otros navegadores pueden decidir si implementar la nueva API sin depender de la recolección de elementos no utilizados.

Casos de uso sugeridos

El uso de memoria de una página web depende del tiempo de los eventos, las acciones del usuario y las recolecciones de elementos no utilizados. Es por eso que la API de medición de memoria está diseñada para agregar datos de uso de memoria de la producción. Los resultados de llamadas individuales son menos útiles. Casos prácticos de ejemplo:

  • Detección de regresión durante el lanzamiento de una versión nueva de la página web para detectar nuevas fugas de memoria.
  • Realiza pruebas A/B de una función nueva para evaluar su impacto en la memoria y detectar fugas de memoria.
  • Correlaciona el uso de memoria con la duración de la sesión para verificar la presencia o ausencia de fugas de memoria.
  • Correlaciona el uso de memoria con las métricas del usuario para comprender el impacto general del uso de memoria.

Compatibilidad del navegador

Navegadores compatibles

  • 89
  • 89
  • x
  • x

Origen

Actualmente, la API solo es compatible con los navegadores basados en Chromium a partir de Chrome 89. El resultado de la API depende en gran medida de la implementación, ya que los navegadores tienen diferentes formas de representar objetos en la memoria y diferentes formas de estimar el uso de memoria. Los navegadores pueden excluir algunas regiones de la memoria de la contabilización si la contabilidad adecuada es demasiado costosa o inviable. Por lo tanto, los resultados no se pueden comparar entre navegadores. Solo es importante comparar los resultados para el mismo navegador.

Usa performance.measureUserAgentSpecificMemory()

Detección de funciones

La función performance.measureUserAgentSpecificMemory no estará disponible o puede fallar con un SecurityError si el entorno de ejecución no cumple con los requisitos de seguridad para evitar filtraciones de información de origen cruzado. Se basa en el aislamiento de origen cruzado, que una página web puede activar si se configuran encabezados COOP+COEP.

La compatibilidad se puede detectar en el entorno de ejecución:

if (!window.crossOriginIsolated) {
  console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
} else if (!performance.measureUserAgentSpecificMemory) {
  console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
} else {
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
    } else {
      throw error;
    }
  }
  console.log(result);
}

Pruebas locales

Chrome realiza la medición de la memoria durante la recolección de elementos no utilizados, lo que significa que la API no resuelve la promesa de resultado de inmediato y, en su lugar, espera la siguiente recolección de elementos no utilizados.

Llamar a la API fuerza la recolección de elementos no utilizados después de un tiempo de espera, que actualmente está configurado en 20 segundos, aunque puede ocurrir antes. Iniciar Chrome con la marca de línea de comandos --enable-blink-features='ForceEagerMeasureMemory' reduce el tiempo de espera a cero y es útil para la depuración y las pruebas locales.

Ejemplo

Se recomienda usar la API para definir un monitor de memoria global que muestrea el uso de memoria de toda la página web y envíe los resultados a un servidor para la agregación y el análisis. La forma más sencilla es generar muestras de manera periódica, por ejemplo, cada M minutos. Sin embargo, eso genera sesgo en los datos porque pueden ocurrir picos de memoria entre las muestras.

En el siguiente ejemplo, se muestra cómo realizar mediciones de memoria no sesgadas mediante un proceso Poisson, que garantiza que las muestras tengan la misma probabilidad de ocurrir en cualquier momento (demostración, fuente).

Primero, define una función que programe la siguiente medición de memoria mediante setTimeout() con un intervalo aleatorizado.

function scheduleMeasurement() {
  // Check measurement API is available.
  if (!window.crossOriginIsolated) {
    console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
    console.log('See https://web.dev/coop-coep/ to learn more')
    return;
  }
  if (!performance.measureUserAgentSpecificMemory) {
    console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
    return;
  }
  const interval = measurementInterval();
  console.log(`Running next memory measurement in ${Math.round(interval / 1000)} seconds`);
  setTimeout(performMeasurement, interval);
}

La función measurementInterval() calcula un intervalo aleatorio en milisegundos de modo que, en promedio, haya una medición cada cinco minutos. Consulta Distribución exponencial si te interesan los cálculos detrás de la función.

function measurementInterval() {
  const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
  return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}

Por último, la función performMeasurement() asíncrona invoca la API, registra el resultado y programa la siguiente medición.

async function performMeasurement() {
  // 1. Invoke performance.measureUserAgentSpecificMemory().
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
      return;
    }
    // Rethrow other errors.
    throw error;
  }
  // 2. Record the result.
  console.log('Memory usage:', result);
  // 3. Schedule the next measurement.
  scheduleMeasurement();
}

Por último, empieza a medir.

// Start measurements.
scheduleMeasurement();

El resultado puede verse de la siguiente manera:

// Console output:
{
  bytes: 60_100_000,
  breakdown: [
    {
      bytes: 40_000_000,
      attribution: [{
        url: 'https://example.com/',
        scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 20_000_000,
      attribution: [{
          url: 'https://example.com/iframe',
          container: {
            id: 'iframe-id-attribute',
            src: '/iframe',
          },
          scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 100_000,
      attribution: [],
      types: ['DOM']
    },
  ],
}

La estimación del uso total de memoria se muestra en el campo bytes. Este valor depende en gran medida de la implementación y no se puede comparar entre navegadores. Incluso puede cambiar entre diferentes versiones del mismo navegador. El valor incluye la memoria de JavaScript y DOM de todos los iframes, las ventanas relacionadas y los trabajadores web en el proceso actual.

En la lista breakdown, se proporciona más información sobre la memoria usada. Cada entrada describe una parte de la memoria y la atribuye a un conjunto de ventanas, iframes y trabajadores identificados por URL. El campo types enumera los tipos de memoria específicos de la implementación asociados con la memoria.

Es importante tratar todas las listas de una manera genérica y no codificar suposiciones en función de un navegador en particular. Por ejemplo, algunos navegadores pueden mostrar un breakdown o un attribution vacíos. Otros navegadores pueden mostrar varias entradas en attribution, lo que indica que no pudieron distinguir cuál de estas entradas es propietaria de la memoria.

Comentarios

Al grupo de la comunidad de rendimiento web y al equipo de Chrome les encantaría conocer tus opiniones y experiencias con performance.measureUserAgentSpecificMemory().

Cuéntanos sobre el diseño de la API

¿Existe algún aspecto de la API que no funcione según lo esperado? ¿O faltan propiedades que necesites para implementar tu idea? Informa un problema de especificaciones en el repositorio de GitHub performance.measureUserAgentSpecificMemory() o agrega tus ideas sobre un problema existente.

Informar un problema con la implementación

¿Encontraste un error en la implementación de Chrome? ¿O la implementación es diferente de la especificación? Informa un error en new.crbug.com. Asegúrate de incluir la mayor cantidad de detalles posible, proporcionar instrucciones simples para reproducir el error y establecer Components como Blink>PerformanceAPIs. Glitch funciona muy bien para compartir repros rápidos y fáciles.

Demostrar apoyo

¿Planeas usar performance.measureUserAgentSpecificMemory()? Tu asistencia pública ayuda al equipo de Chrome a priorizar funciones y les muestra a otros proveedores de navegadores qué tan importantes son para admitirlas. Envía un tweet a @ChromiumDev y cuéntanos dónde y cómo lo usas.

Vínculos útiles

Agradecimientos

Muchas gracias a Domenic Denicola, Yoav Weiss, Mathias Bynens por las revisiones de diseño de APIs y a Dominik Inführ, Hannes Payer, Kentaro Hara y Michael Lippautz por las revisiones de código en Chrome. También agradezco a Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan y Neil Mckay por proporcionar comentarios valiosos de los usuarios que mejoraron mucho la API.

Hero image de Harrison Broadbent en Unsplash