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 y detectar regresiones.

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

Los navegadores administran la memoria de las páginas web de forma automática. Cada vez que una página web crea un objeto, el navegador asigna un fragmento de memoria "en segundo plano" para almacenarlo. Dado que la memoria es un recurso finito, el navegador realiza la recolección de elementos no utilizados para detectar cuándo ya no se necesita un objeto y liberar el fragmento de memoria subyacente.

Sin embargo, la detección no es perfecta, y el problema de la detención de Alan Turing demostró que la detección perfecta es una tarea imposible. Por lo tanto, los navegadores aproximan 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 provoca 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í, el array más grande b ya no es necesario, pero el navegador no lo reclama porque aún se puede acceder a él a través de object.b en la devolución de llamada. Por lo tanto, se filtra la memoria del array más grande.

Las fugas de memoria son frecuentes en la Web, como se puede ver en este estudio. Es fácil introducir una si olvidas anular el registro de un objeto de escucha de eventos, si capturas objetos de un iframe por accidente, si no cierras un trabajador, si acumulas 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 aparece lenta y sobrecargada para los usuarios.

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

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

Si conoces la API performance.memory no estándar existente, es posible que te preguntes en qué se diferencia la nueva API. La diferencia principal es que la API antigua muestra el tamaño del montón de JavaScript, mientras que la nueva API 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 web). En esos casos, el resultado de la API antigua puede ser arbitrariamente incorrecto. Dado que la API antigua se define en términos específicos de la implementación, como "montón", es imposible estandarizarla.

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 hasta que se produzcan los resultados. Ten en cuenta que otros navegadores pueden decidir 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. Por eso, la API de medición de memoria está diseñada para agregar datos de uso de memoria de la producción. Los resultados de las llamadas individuales son menos útiles. Ejemplos de casos de uso:

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

Compatibilidad del navegador

Browser Support

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

Source

Actualmente, la API solo es compatible con 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 memoria de la contabilidad si la contabilidad adecuada es demasiado costosa o inviable. Por lo tanto, los resultados no se pueden comparar entre navegadores. Solo es significativo comparar los resultados del 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 los requisitos de seguridad para evitar fugas de información de origen cruzado. Se basa en el aislamiento de origen cruzado, que una página web puede activar configurando encabezados COOP+COEP.

La compatibilidad se puede detectar en el tiempo 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 una 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 --enable-blink-features='ForceEagerMeasureMemory' marca de línea de comandos reduce el tiempo de espera a cero y es útil para las pruebas y la depuración locales.

Ejemplo

El uso recomendado de la API es definir un monitor de memoria global que muestre el uso de memoria de toda la página web y envíe los resultados a un servidor para su agregación y análisis. La forma más sencilla es realizar un muestreo de forma periódica, por ejemplo, cada M minutos. Sin embargo, eso introduce sesgos en los datos porque los picos de memoria pueden ocurrir entre las muestras.

En el siguiente ejemplo, se muestra cómo realizar mediciones de memoria imparciales con un proceso de 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 con setTimeout() con un intervalo aleatorio.

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 interesa la matemática 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, comienza 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.

La lista breakdown proporciona más información sobre la memoria utilizada. 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 muestra los tipos de memoria específicos de la implementación asociados con la memoria.

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

Comentarios

Al Web Performance Community Group 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

¿Hay algo en la API que no funciona como se espera? ¿O faltan propiedades que necesitas para implementar tu idea? Informa un problema de especificación en el repositorio de GitHub performance.measureUserAgentSpecificMemory() o agrega tus ideas a un problema existente.

Informa un problema con la implementación

¿Encontraste un error con 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 sencillas para reproducir el error y configurar Components en Blink>PerformanceAPIs.

Expresar apoyo

¿Planeas usar performance.measureUserAgentSpecificMemory()? Tu apoyo público ayuda al equipo de Chrome a priorizar funciones y muestra a otros proveedores de navegadores lo fundamental que es 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 y Mathias Bynens por las revisiones del diseño de la API, 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 valiosos comentarios de los usuarios que mejoraron en gran medida la API.