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 pedaço 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 no bloco de memória.

Mas a detecção não é perfeita comprovou que a detecção perfeita é uma tarefa impossível. Assim, os navegadores se aproximam da noção de "um objeto é necessário" com a ideia de que um objeto é acessível. Se a página da Web não puder alcançar um objeto por meio de suas variáveis e os campos de outros objetos acessíveis, o navegador poderá reivindicar o objeto com segurança. A diferença entre duas noções básicas causam vazamentos de memória, conforme ilustrado pelo 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 reivindique-o porque ele ainda pode ser acessado via object.b no callback. Assim, a memória da matriz maior vaza.

Os vazamentos de memória são prevalentes na Web. É fácil introduzir um se esquecer de cancelar o registro de um listener de eventos, capturar acidentalmente objetos de um iframe, sem fechar um worker, acumulando objetos em matrizes e assim por diante. Se uma página da Web tem vazamentos de memória, o uso de memória aumenta com o tempo, e a página fica lenta sobrecarregadas para os usuários.

O primeiro passo para resolver esse problema é medi-lo. A nova A API performance.measureUserAgentSpecificMemory() permite que os desenvolvedores medem o uso de memória das páginas da Web na produção e, assim, detectam a memória que passam por testes locais.

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

Se você já conhece a API performance.memory não padrão, você deve estar se perguntando qual é a diferença entre essa nova API. A principal diferença é que a API antiga retorna o tamanho da pilha JavaScript, enquanto a nova API estima a memória usada pela página da Web. Essa diferença torna-se importante quando o Chrome compartilha a mesma heap com várias páginas da Web (ou várias instâncias da mesma página da Web). Nesses casos, o resultado da fase A API pode ser desativada arbitrariamente. Como a API antiga é definida em termos específicos de implementação, como "heap", e a padronização é irreversível.

Outra diferença é que a nova API realiza a medição de memória durante coleta de lixo. Isso reduz o ruído nos resultados, mas pode demorar enquanto os resultados são 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 coletas de lixo. É por isso que a API de medição de memória se destina a agregar os dados de uso de memória da produção. 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 detectar 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.
  • 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.
  • Correlação do uso de memória com métricas do usuário para entender o impacto geral do uso de memória.

Compatibilidade com navegadores

Compatibilidade com navegadores

  • Chrome: 89.
  • Borda: 89.
  • Firefox: incompatível.
  • Safari: incompatível.

Origem

Atualmente, a API tem suporte apenas em navegadores baseados no Chromium, a partir do Chrome 89. A resultado da API é altamente dependente da implementação porque os navegadores têm maneiras diferentes de representar objetos na memória para estimar o uso da memória. Os navegadores podem excluir algumas regiões de memória contabilização se a contabilidade adequada for muito cara ou inviável. Assim, os resultados não podem ser comparados entre navegadores. Só é significativo comparar resultados para o 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 for atendido os requisitos de segurança para evitar vazamentos de informações de origem cruzada. Ele depende do isolamento de origem cruzada, que uma página da Web pode ativar definindo cabeçalhos COOP+COEP.

O suporte pode ser detectado no ambiente de 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 faz a medição da memória durante a coleta de lixo, ou seja, que a API não resolva a promessa de resultado imediatamente e, em vez disso, aguarde para a próxima coleta de lixo.

Chamar a API força uma coleta de lixo após algum tempo limite, que é está definido para 20 segundos, embora isso possa acontecer antes. Iniciar o Chrome com o a sinalização de linha de comando --enable-blink-features='ForceEagerMeasureMemory' reduz o tempo limite para zero e é útil para depuração e teste locais.

Exemplo

O uso recomendado da API é definir um monitor de memória global que faz uma amostragem do uso da memória de toda a página da Web e envia os resultados a um servidor para agregação e análise. A maneira mais simples é fazer amostras periodicamente, por 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 abaixo 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 (demonstração, fonte).

Primeiro, defina uma função que programa 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, haja uma medição a cada cinco minutos. Consulte Exponencial distribuição se 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 performMeasurement() assíncrona 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 o seguinte:

// 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. Talvez ou até alternar entre diferentes versões do mesmo navegador. O valor inclui Memória JavaScript e DOM de todos os iframes, janelas relacionadas e web workers na o 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 por 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 com base em um navegador específico. Por exemplo, alguns navegadores podem retornar um breakdown vazio ou um attribution vazio. Outros navegadores podem retornar várias entradas em attribution, indicando que elas não puderam distinguir qual dessas entradas é a proprietária da memória.

Feedback

O Grupo da comunidade de desempenho da Web e a equipe do Chrome adoraram ouvir sobre seus pensamentos e experiências com performance.measureUserAgentSpecificMemory()

Fale sobre o design da API

Alguma coisa na API não funciona como esperado? Ou há quais propriedades estão faltando para você implementar sua ideia? Registre um problema de especificação em repositório do GitHub performance.measureUserAgentSpecificMemory() ou adicione o que você pensa sobre um problema.

Informar um problema com a implementação

Você encontrou um bug na implementação do Chrome? Ou a implementação diferente das especificações? Registre um bug em new.crbug.com. Não se esqueça de incluir o máximo de detalhes possível, fornecer instruções simples para reproduzir o bug e ter a opção Componentes definida como Blink>PerformanceAPIs. O Glitch é ótimo para compartilhar repetições rápidas e fáceis.

Mostrar apoio

Você planeja usar performance.measureUserAgentSpecificMemory()? Seu apoio público ajuda a equipe do Chrome a priorizar recursos e mostra a outros fornecedores de navegadores como é crucial apoiá-las. Envie um tweet para @ChromiumDev e informe onde e como você o utiliza.

Links úteis

Agradecimentos

Agradecemos a Domenic Denicola, Yoav Weiss e Mathias Bynens pelas análises de design de APIs, e Dominik Inführ, Hannes Payer, Kentaro Hara e Michael Lippautz para as revisões de código no Chrome. Também agradeço a Per Parker, Philipp Weis, Olga Belomestnykh e Matthew Bolohan e Neil Mckay por fornecerem feedbacks valiosos aos usuários melhorou a API.

Imagem principal de Harrison Broadbent no Unsplash