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 em 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 um bloco de memória "em segundo plano" para armazenar o objeto. Como a memória é um recurso finito, o navegador realiza 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 o problema da parada de Alan Turing provou 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 é acessível". Se a página da Web não conseguir alcançar um objeto pelas variáveis e pelos 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 no exemplo a seguir.

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 ela ainda pode ser acessada por object.b no callback. Assim, a memória da matriz maior é vazada.

Os vazamentos de memória são comuns na Web, como mostra este estudo. É fácil introduzir um vazamento ao esquecer de cancelar o registro de um listener de eventos, capturar acidentalmente objetos de um iframe, não fechar um worker, acumular objetos em matrizes etc. Se uma página da Web tiver vazamentos de memória, o uso da memória vai aumentar com o tempo, e a página vai parecer 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 em produção e, assim, detectem vazamentos de memória que passam despercebidos nos testes locais.

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

Se você já conhece a API performance.memory não padrão, talvez esteja se perguntando qual é a diferença entre ela e a nova API. A principal diferença é que a API antiga retorna o tamanho do heap do JavaScript, enquanto a nova API estima a memória usada pela página da Web. Essa diferença se torna importante quando o Chrome compartilha o mesmo heap com várias páginas da Web (ou várias instâncias da mesma página da Web). 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", não é possível padronizá-la.

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 momento dos eventos, das ações do usuário e das coletas de lixo. Por isso, a API de medição de memória é destinada à agregação de 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 da página da Web para identificar novos vazamentos de memória.
  • Teste A/B de um novo recurso para avaliar o impacto na memória e detectar vazamentos de memória.
  • Correlacionar o uso da memória com a duração da sessão para verificar a presença ou ausência de vazamentos de memória.
  • Correlacionar o uso da memória com as métricas do usuário para entender o impacto geral do uso da memória.

Compatibilidade com navegadores

Browser Support

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

Source

No momento, 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 de estimar o uso da memória. Os navegadores podem excluir algumas regiões de memória da contabilidade se a contabilidade adequada for muito cara ou inviável. Portanto, os resultados não podem ser comparados entre navegadores. Só faz sentido comparar os resultados do mesmo navegador.

Como usar o performance.measureUserAgentSpecificMemory()

Detecção de recursos

A função performance.measureUserAgentSpecificMemory vai 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 entre origens. Ela depende do isolamento de origem cruzada, que uma página da Web pode ativar definindo cabeçalhos COOP+COEP.

A compatibilidade pode ser detectada no momento da 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 realiza 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, em vez disso, 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 coleta amostras do uso da memória de toda a página da Web e envia os resultados para 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 picos de memória podem ocorrer 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 tenham a mesma probabilidade de ocorrer em qualquer momento.

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, de modo que, em média, há uma medição a cada cinco minutos. Consulte Distribuição exponencial se você tiver interesse 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 assim:

// 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 é altamente dependente da implementação e não pode ser comparado entre navegadores. Ele pode até mudar entre versões diferentes 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 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 maneira genérica e não fixar no código suposições com base em um navegador específico. Por exemplo, alguns navegadores podem retornar um breakdown ou um attribution vazio. Outros navegadores podem retornar várias entradas em attribution, indicando que não foi possível distinguir qual delas é proprietária da memória.

Feedback

O Web Performance Community Group e a equipe do Chrome adorariam saber suas opiniões 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 do GitHub performance.measureUserAgentSpecificMemory() ou adicione suas ideias a um problema existente.

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 Components como Blink>PerformanceAPIs.

Mostrar suporte

Você planeja usar o performance.measureUserAgentSpecificMemory()? Seu apoio público ajuda a equipe do Chrome a priorizar recursos e mostra a outros fornecedores de navegadores como é fundamental oferecer suporte a eles. Envie um tweet para @ChromiumDev e conte onde e como você está usando o recurso.

Links úteis

Agradecimentos

Agradecemos a Domenic Denicola, Yoav Weiss e Mathias Bynens pelas revisões de design da API, e a 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 por fornecerem feedback valioso dos usuários, o que melhorou muito a API.