Monitorar o uso total da memória da sua página da Web com medidaUserAgentSpecificMemory()

Saiba como medir o uso da memória da sua página da Web na produção para detectar regressões.

Brendan kenny
Brendan Kenny
Ulan degenbaev
Ulan Degenbaev

Os navegadores gerenciam a memória das páginas da Web automaticamente. Sempre que uma página da Web cria um objeto, o navegador aloca uma parte da memória "em segundo plano" para armazenar o objeto. Como a memória é um recurso finito, o navegador executa a coleta de lixo para detectar quando um objeto não é mais necessário e liberar o bloco de memória subjacente.

No entanto, a detecção não é perfeita, e foi comprovado que a detecção perfeita é uma tarefa impossível. Portanto, os navegadores aproximam a noção de "um objeto é necessário" com a noção de "um objeto pode ser alcançado". Se a página da Web não puder alcançar um objeto por meio de suas variáveis e dos campos de outros objetos acessíveis, o navegador poderá recuperar o objeto com segurança. A diferença entre essas duas noções leva a vazamentos de memória, conforme ilustrado pelo exemplo abaixo.

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

Aqui, a matriz maior b não é mais necessária, mas o navegador não a recupera porque ainda pode ser acessada por object.b no callback. Assim, a memória da matriz maior vaza.

Vazamentos de memória são prevalentes na Web. É fácil introduzir um listener esquecendo de cancelar o registro de um listener de eventos, capturando acidentalmente objetos de um iframe, não fechando um worker, acumulando objetos em matrizes e assim por diante. Se uma página da Web tiver vazamentos de memória, o uso dela aumentará com o tempo e a página da Web aparecerá lenta e sobrecarregada para os usuários.

A primeira etapa para resolver esse problema é medi-lo. A nova API performance.measureUserAgentSpecificMemory() permite que os desenvolvedores meçam o uso da memória das páginas da Web durante a produção e, assim, detectem vazamentos de memória que passam nos testes locais.

Qual é a diferença entre o performance.measureUserAgentSpecificMemory() e a API performance.memory legada?

Se você já conhece a API performance.memory não padrão, talvez esteja se perguntando como a nova API é diferente dela. A principal diferença é que a API antiga retorna o tamanho do heap JavaScript, enquanto a nova API estima a memória usada pela página da Web. Essa diferença é importante quando o Chrome compartilha o mesmo heap com várias páginas da Web ou várias instâncias da mesma página. Nesses casos, o resultado da API antiga pode ser arbitrariamente desativado. Como a API antiga é definida em termos específicos da implementação, como "heap", a padronização é inviável.

Outra diferença é que a nova API realiza a medição de memória durante a coleta de lixo. Isso reduz o ruído nos resultados, mas pode levar algum tempo até que eles sejam produzidos. Outros navegadores podem decidir implementar a nova API sem depender da coleta de lixo.

Casos de uso sugeridos

O uso da memória de uma página da Web depende do tempo dos eventos, das ações do usuário e da coleta de lixo. É por isso que a API de medição de memória serve para agregar os dados de uso da memória da produção. Os resultados de chamadas individuais são menos úteis. Exemplos de casos de uso:

  • Detecção de regressão durante o lançamento de uma nova versão de uma página da Web para detectar novos vazamentos de memória.
  • Testes A/B de um novo recurso para avaliar o impacto na memória e detectar vazamentos.
  • Correlação do uso de memória com a duração da sessão para verificar a presença ou ausência de vazamentos de memória.
  • Correlacionando o uso da memória com métricas do usuário para entender o impacto geral do uso da memória.

Compatibilidade com navegadores

Compatibilidade com navegadores

  • 89
  • 89
  • x
  • x

Origem

Atualmente, a API é compatível apenas com navegadores baseados no Chromium, a partir do Chrome 89. O resultado da API depende muito da implementação, porque os navegadores têm maneiras diferentes de representar objetos na memória e maneiras diferentes de estimar o uso da memória. Os navegadores podem excluir algumas regiões de memória da contabilização se a contabilidade adequada for muito cara ou inviável. Assim, os resultados não podem ser comparados entre navegadores. Só é importante comparar os resultados do mesmo navegador.

Como usar o performance.measureUserAgentSpecificMemory()

Detecção de recursos

A função performance.measureUserAgentSpecificMemory ficará indisponível ou poderá falhar com um SecurityError se o ambiente de execução não atender aos requisitos de segurança para evitar vazamentos de informações de origem cruzada. Ela depende do isolamento de origem cruzada, que uma página da Web pode ativar configurando cabeçalhos COOP+COEP.

A compatibilidade pode ser detectada durante a execução:

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);
}

Teste local

O Chrome executa a medição de memória durante a coleta de lixo, o que significa que a API não resolve a promessa de resultado imediatamente e aguarda a próxima coleta de lixo.

Chamar a API força uma coleta de lixo após um tempo limite, que atualmente está definido como 20 segundos, mas pode acontecer antes. Iniciar o Chrome com a flag de linha de comando --enable-blink-features='ForceEagerMeasureMemory' reduz o tempo limite para zero e é útil para depuração e testes locais.

Exemplo

O uso recomendado da API é definir um monitor de memória global que faça uma amostragem do uso de memória de toda a página da Web e envie os resultados a um servidor para agregação e análise. A maneira mais simples é fazer amostras periodicamente, por exemplo, a cada M minutos. No entanto, isso introduz um viés nos dados porque podem ocorrer picos de memória entre as amostras.

O exemplo a seguir mostra como fazer medições de memória imparciais usando um processo de Poisson, que garante que as amostras têm a mesma probabilidade de ocorrer a qualquer momento (demo, fonte).

Primeiro, defina uma função que programe a próxima medição de memória usando setTimeout() com um intervalo aleatório.

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);
}

A função measurementInterval() calcula um intervalo aleatório em milissegundos para que, em média, haja uma medição a cada cinco minutos. Consulte Distribuição exponencial se você estiver interessado na matemática por trás da função.

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

Por fim, a função assíncrona performMeasurement() invoca a API, registra o resultado e programa a próxima medição.

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 fim, comece a medir.

// Start measurements.
scheduleMeasurement();

O resultado pode ser semelhante a este:

// 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']
    },
  ],
}

A estimativa de uso total da memória é retornada no campo bytes. Esse valor depende muito da implementação e não pode ser comparado entre navegadores. Ele pode até variar entre diferentes versões do mesmo navegador. O valor inclui a memória JavaScript e DOM de todos os iframes, janelas relacionadas e workers da Web no processo atual.

A lista de breakdown fornece mais informações sobre a memória usada. Cada entrada descreve uma parte da memória e a atribui a um conjunto de janelas, iframes e workers identificados pelo URL. O campo types lista os tipos de memória específicos da implementação associados à memória.

É importante tratar todas as listas de forma genérica e não fixar suposições baseadas em um navegador específico. Por exemplo, alguns navegadores podem retornar uma breakdown ou uma attribution vazias. Outros navegadores podem retornar várias entradas em attribution, indicando que não conseguiram distinguir qual dessas entradas é proprietária da memória.

Feedback

O Grupo da comunidade de desempenho na Web e a equipe do Chrome gostariam de saber o que você acha e experiências com o performance.measureUserAgentSpecificMemory().

Fale sobre o design da API

Há algo na API que não funciona como esperado? Ou há propriedades ausentes que você precisa para implementar sua ideia? Registre um problema de especificação no repositório performance.measureUserAgentSpecificMemory() do GitHub (link em inglês) ou adicione suas ideias a um problema atual.

Informar um problema com a implementação

Você encontrou um bug na implementação do Chrome? Ou a implementação é diferente da especificação? Registre um bug em new.crbug.com. Inclua o máximo de detalhes possível, forneça instruções simples para reproduzir o bug e defina Componentes como Blink>PerformanceAPIs. O Glitch funciona muito bem para compartilhar repetições rápidas e fáceis.

Mostrar apoio

Você planeja usar o performance.measureUserAgentSpecificMemory()? Seu suporte público ajuda a equipe do Chrome a priorizar recursos e mostra a outros fornecedores de navegador como é importante oferecer suporte a eles. Envie um tweet para @ChromiumDev e informe onde e como ele está sendo usado.

Links úteis

Agradecimentos

Agradecemos a Domenic Denicola, Yoav Weiss, Mathias Bynens pelas revisões de design de APIs e Dominik Inführ, Hannes Payer, Kentaro Hara e Michael Lippautz pelas revisões de código no Chrome. Também agradeço a Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan e Neil Mckay pelo feedback valioso dos usuários que melhoraram a API.

Imagem principal por Harrison Broadbent no Unsplash