Отслеживайте общее использование памяти вашей веб-страницы с помощью метода MeasureUserAgentSpecificMemory().

Узнайте, как измерить использование памяти вашей веб-страницы в рабочей среде, чтобы обнаружить регрессии.

Брендан Кенни
Brendan Kenny
Улан Дегенбаев
Ulan Degenbaev

Браузеры автоматически управляют памятью веб-страниц. Всякий раз, когда веб-страница создает объект, браузер выделяет «под капотом» часть памяти для хранения объекта. Поскольку память является ограниченным ресурсом, браузер выполняет сборку мусора, чтобы определить, когда объект больше не нужен, и освободить соответствующий фрагмент памяти.

Однако обнаружение не является идеальным, и было доказано , что идеальное обнаружение — невыполнимая задача. Поэтому браузеры приближают понятие «объект необходим» к понятию «объект достижим». Если веб-страница не может получить доступ к объекту через его переменные и поля других доступных объектов, браузер может безопасно вернуть объект. Разница между этими двумя понятиями приводит к утечкам памяти, как показано в следующем примере.

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

Здесь больший массив b больше не нужен, но браузер не забирает его, поскольку он по-прежнему доступен через object.b в обратном вызове. Таким образом происходит утечка памяти большего массива.

Утечки памяти широко распространены в Интернете . Его легко ввести, забыв отменить регистрацию прослушивателя событий, случайно захватить объекты из iframe, не закрыть рабочий процесс, накопить объекты в массивах и т. д. Если на веб-странице есть утечки памяти, то ее использование памяти со временем увеличивается, и веб-страница кажется пользователям медленной и раздутой.

Первым шагом в решении этой проблемы является ее измерение. Новый API performance.measureUserAgentSpecificMemory() позволяет разработчикам измерять использование памяти их веб-страницами в рабочей среде и, таким образом, обнаруживать утечки памяти, которые не проходят локальное тестирование.

Чем performance.measureUserAgentSpecificMemory() отличается от устаревшего API performance.memory ?

Если вы знакомы с существующим нестандартным API performance.memory , вам может быть интересно, чем отличается от него новый API. Основное отличие состоит в том, что старый API возвращает размер кучи JavaScript, тогда как новый API оценивает объем памяти, используемый веб-страницей. Это различие становится важным, когда Chrome использует одну и ту же кучу с несколькими веб-страницами (или несколькими экземплярами одной и той же веб-страницы). В таких случаях результат старого API может быть произвольно отключен. Поскольку старый API определен в терминах, специфичных для реализации, таких как «куча», его стандартизация безнадежна.

Еще одно отличие состоит в том, что новый API выполняет измерение памяти во время сборки мусора. Это уменьшает шум в результатах, но получение результатов может занять некоторое время. Обратите внимание, что другие браузеры могут решить реализовать новый API, не полагаясь на сбор мусора.

Рекомендуемые варианты использования

Использование памяти веб-страницы зависит от времени событий, действий пользователя и сборки мусора. Вот почему API измерения памяти предназначен для агрегирования данных об использовании памяти в производстве. Результаты отдельных вызовов менее полезны. Примеры использования:

  • Обнаружение регрессии во время развертывания новой версии веб-страницы для обнаружения новых утечек памяти.
  • A/B-тестирование новой функции для оценки ее влияния на память и обнаружения утечек памяти.
  • Сопоставление использования памяти с продолжительностью сеанса для проверки наличия или отсутствия утечек памяти.
  • Сопоставление использования памяти с пользовательскими метриками, чтобы понять общее влияние использования памяти.

Совместимость с браузером

Поддержка браузера

  • Хром: 89.
  • Край: 89.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Источник

В настоящее время API поддерживается только в браузерах на базе Chromium, начиная с Chrome 89. Результат работы API сильно зависит от реализации, поскольку браузеры имеют разные способы представления объектов в памяти и разные способы оценки использования памяти. Браузеры могут исключить некоторые области памяти из учета, если правильный учет слишком дорог или невозможен. Таким образом, результаты нельзя сравнивать в разных браузерах. Имеет смысл сравнивать результаты только для одного и того же браузера.

Использование performance.measureUserAgentSpecificMemory()

Обнаружение функций

Функция performance.measureUserAgentSpecificMemory будет недоступна или может завершиться сбоем с ошибкой SecurityError , если среда выполнения не соответствует требованиям безопасности для предотвращения утечек информации из разных источников. Он опирается на изоляцию между источниками , которую веб-страница может активировать, установив заголовки COOP+COEP .

Поддержка может быть обнаружена во время выполнения:

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

Локальное тестирование

Chrome выполняет измерение памяти во время сборки мусора. Это означает, что API не выполняет обещание результата немедленно, а вместо этого ждет следующей сборки мусора.

Вызов API приводит к принудительной сборке мусора по истечении некоторого времени ожидания, которое в настоящее время установлено на 20 секунд, но может произойти и раньше. Запуск Chrome с флагом командной строки --enable-blink-features='ForceEagerMeasureMemory' сокращает время ожидания до нуля и полезен для локальной отладки и тестирования.

Пример

Рекомендуемое использование API — определить глобальный монитор памяти, который производит выборку использования памяти всей веб-страницы и отправляет результаты на сервер для агрегирования и анализа. Самый простой способ — периодически производить выборку, например, каждые M минут. Однако это приводит к смещению данных, поскольку между выборками могут возникать пики памяти.

В следующем примере показано, как выполнить несмещенные измерения памяти с помощью процесса Пуассона , который гарантирует, что выборки имеют одинаковую вероятность появления в любой момент времени ( демо , исходный код ).

Сначала определите функцию, которая планирует следующее измерение памяти с помощью setTimeout() со случайным интервалом.

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

Функция measurementInterval() вычисляет случайный интервал в миллисекундах, так что в среднем каждые пять минут выполняется одно измерение. См. Экспоненциальное распределение , если вас интересует математика этой функции.

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

Наконец, асинхронная функция performMeasurement() вызывает API, записывает результат и планирует следующее измерение.

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

Наконец, приступайте к измерениям.

// Start measurements.
scheduleMeasurement();

Результат может выглядеть следующим образом:

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

Общая оценка использования памяти возвращается в поле bytes . Это значение сильно зависит от реализации и не может сравниваться между браузерами. Он может даже меняться в разных версиях одного и того же браузера. Значение включает память JavaScript и DOM для всех iframe, связанных окон и веб-воркеров в текущем процессе.

Список breakdown предоставляет дополнительную информацию об используемой памяти. Каждая запись описывает некоторую часть памяти и приписывает ее набору окон, iframe и воркеров, идентифицированных по URL-адресу. В поле types перечислены типы памяти, связанные с конкретной реализацией.

Важно относиться ко всем спискам в общем виде и не жестко запрограммировать предположения, основанные на конкретном браузере. Например, некоторые браузеры могут возвращать пустую breakdown или пустую attribution . Другие браузеры могут возвращать несколько записей attribution указывая, что они не могут определить, какой из этих записей принадлежит память.

Обратная связь

Группа сообщества Web Performance и команда Chrome хотели бы услышать ваши мысли и опыт использования performance.measureUserAgentSpecificMemory() .

Расскажите нам о дизайне API

Что-то в API работает не так, как ожидалось? Или вам не хватает свойств, необходимых для реализации вашей идеи? Сообщите о проблеме спецификации в репозитории Performance.measureUserAgentSpecificMemory() на GitHub или добавьте свои мысли к существующей проблеме.

Сообщить о проблеме с реализацией

Вы нашли ошибку в реализации Chrome? Или реализация отличается от спецификации? Сообщите об ошибке на сайте new.crbug.com . Обязательно укажите как можно больше подробностей, предоставьте простые инструкции по воспроизведению ошибки и установите для параметра «Компоненты» значение Blink>PerformanceAPIs . Glitch отлично подходит для быстрого и простого обмена репродукциями.

Показать поддержку

Планируете ли вы использовать performance.measureUserAgentSpecificMemory() ? Ваша публичная поддержка помогает команде Chrome расставлять приоритеты в функциях и показывает другим поставщикам браузеров, насколько важно их поддерживать. Отправьте твит @ChromiumDev и сообщите нам, где и как вы его используете.

Полезные ссылки

Благодарности

Большое спасибо Доменику Дениколе, Йоаву Вайсу, Матиасу Биненсу за обзоры дизайна API, а также Доминику Инфюру, Ханнесу Пайеру, Кентаро Хара, Михаэлю Липпаутцу за обзоры кода в Chrome. Я также благодарю Пера Паркера, Филиппа Вайса, Ольгу Беломестных, Мэтью Болохана и Нила Маккея за ценные отзывы пользователей, которые значительно улучшили API.

Героическое изображение Харрисона Бродбента на Unsplash

,

Узнайте, как измерить использование памяти вашей веб-страницы в рабочей среде, чтобы обнаружить регрессии.

Брендан Кенни
Brendan Kenny
Улан Дегенбаев
Ulan Degenbaev

Браузеры автоматически управляют памятью веб-страниц. Всякий раз, когда веб-страница создает объект, браузер выделяет «под капотом» часть памяти для хранения объекта. Поскольку память является ограниченным ресурсом, браузер выполняет сборку мусора, чтобы определить, когда объект больше не нужен, и освободить соответствующий фрагмент памяти.

Однако обнаружение не является идеальным, и было доказано , что идеальное обнаружение — невыполнимая задача. Поэтому браузеры приближают понятие «объект необходим» к понятию «объект достижим». Если веб-страница не может получить доступ к объекту через его переменные и поля других доступных объектов, браузер может безопасно вернуть объект. Разница между этими двумя понятиями приводит к утечкам памяти, как показано в следующем примере.

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

Здесь больший массив b больше не нужен, но браузер не забирает его, поскольку он по-прежнему доступен через object.b в обратном вызове. Таким образом происходит утечка памяти большего массива.

Утечки памяти широко распространены в Интернете . Его легко ввести, забыв отменить регистрацию прослушивателя событий, случайно захватить объекты из iframe, не закрыть рабочий процесс, накопить объекты в массивах и т. д. Если на веб-странице есть утечки памяти, то ее использование памяти со временем увеличивается, и веб-страница кажется пользователям медленной и раздутой.

Первым шагом в решении этой проблемы является ее измерение. Новый API performance.measureUserAgentSpecificMemory() позволяет разработчикам измерять использование памяти их веб-страницами в рабочей среде и таким образом обнаруживать утечки памяти, которые не проходят локальное тестирование.

Чем performance.measureUserAgentSpecificMemory() отличается от устаревшего API performance.memory ?

Если вы знакомы с существующим нестандартным API performance.memory , вам может быть интересно, чем отличается от него новый API. Основное отличие состоит в том, что старый API возвращает размер кучи JavaScript, тогда как новый API оценивает объем памяти, используемый веб-страницей. Это различие становится важным, когда Chrome использует одну и ту же кучу с несколькими веб-страницами (или несколькими экземплярами одной и той же веб-страницы). В таких случаях результат старого API может быть произвольно отключен. Поскольку старый API определен в терминах, специфичных для реализации, таких как «куча», его стандартизация безнадежна.

Еще одно отличие состоит в том, что новый API выполняет измерение памяти во время сборки мусора. Это уменьшает шум в результатах, но получение результатов может занять некоторое время. Обратите внимание, что другие браузеры могут решить реализовать новый API, не полагаясь на сбор мусора.

Рекомендуемые варианты использования

Использование памяти веб-страницы зависит от времени событий, действий пользователя и сборки мусора. Вот почему API измерения памяти предназначен для агрегирования данных об использовании памяти в производстве. Результаты отдельных вызовов менее полезны. Примеры использования:

  • Обнаружение регрессии во время развертывания новой версии веб-страницы для обнаружения новых утечек памяти.
  • A/B-тестирование новой функции для оценки ее влияния на память и обнаружения утечек памяти.
  • Сопоставление использования памяти с продолжительностью сеанса для проверки наличия или отсутствия утечек памяти.
  • Сопоставление использования памяти с пользовательскими метриками, чтобы понять общее влияние использования памяти.

Совместимость с браузером

Поддержка браузера

  • Хром: 89.
  • Край: 89.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Источник

В настоящее время API поддерживается только в браузерах на базе Chromium, начиная с Chrome 89. Результат работы API сильно зависит от реализации, поскольку браузеры имеют разные способы представления объектов в памяти и разные способы оценки использования памяти. Браузеры могут исключить некоторые области памяти из учета, если правильный учет слишком дорог или невозможен. Таким образом, результаты нельзя сравнивать в разных браузерах. Имеет смысл сравнивать результаты только для одного и того же браузера.

Использование performance.measureUserAgentSpecificMemory()

Обнаружение функций

Функция performance.measureUserAgentSpecificMemory будет недоступна или может завершиться сбоем с ошибкой SecurityError , если среда выполнения не соответствует требованиям безопасности для предотвращения утечек информации из разных источников. Он опирается на изоляцию между источниками , которую веб-страница может активировать, установив заголовки COOP+COEP .

Поддержка может быть обнаружена во время выполнения:

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

Локальное тестирование

Chrome выполняет измерение памяти во время сборки мусора. Это означает, что API не выполняет обещание результата немедленно, а вместо этого ждет следующей сборки мусора.

Вызов API приводит к принудительной сборке мусора по истечении некоторого времени ожидания, которое в настоящее время установлено на 20 секунд, но может произойти и раньше. Запуск Chrome с флагом командной строки --enable-blink-features='ForceEagerMeasureMemory' сокращает время ожидания до нуля и полезно для локальной отладки и тестирования.

Пример

Рекомендуемое использование API — определить глобальный монитор памяти, который измеряет использование памяти всей веб-страницы и отправляет результаты на сервер для агрегирования и анализа. Самый простой способ — периодически производить выборку, например, каждые M минут. Однако это приводит к смещению данных, поскольку между выборками могут возникать пики памяти.

В следующем примере показано, как выполнить несмещенные измерения памяти с помощью процесса Пуассона , который гарантирует, что выборки с равной вероятностью возникнут в любой момент времени ( демо , исходный код ).

Сначала определите функцию, которая планирует следующее измерение памяти с помощью setTimeout() со случайным интервалом.

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

Функция measurementInterval() вычисляет случайный интервал в миллисекундах, так что в среднем каждые пять минут выполняется одно измерение. См. Экспоненциальное распределение , если вас интересует математика этой функции.

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

Наконец, асинхронная функция performMeasurement() вызывает API, записывает результат и планирует следующее измерение.

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

Наконец, приступайте к измерениям.

// Start measurements.
scheduleMeasurement();

Результат может выглядеть следующим образом:

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

Общая оценка использования памяти возвращается в поле bytes . Это значение сильно зависит от реализации и не может сравниваться между браузерами. Он может даже меняться в разных версиях одного и того же браузера. Значение включает память JavaScript и DOM для всех iframe, связанных окон и веб-воркеров в текущем процессе.

Список breakdown предоставляет дополнительную информацию об используемой памяти. Каждая запись описывает некоторую часть памяти и приписывает ее набору окон, iframe и воркеров, идентифицированных по URL-адресу. В поле types перечислены типы памяти, связанные с конкретной реализацией.

Важно относиться ко всем спискам в общем виде и не жестко запрограммировать предположения, основанные на конкретном браузере. Например, некоторые браузеры могут возвращать пустую breakdown или пустую attribution . Другие браузеры могут возвращать несколько записей attribution указывая, что они не могут определить, какой из этих записей принадлежит память.

Обратная связь

Группа сообщества Web Performance и команда Chrome будут рады узнать ваши мысли и опыт работы с performance.measureUserAgentSpecificMemory() .

Расскажите нам о дизайне API

Что-то в API работает не так, как ожидалось? Или вам не хватает свойств, необходимых для реализации вашей идеи? Сообщите о проблеме спецификации в репозитории Performance.measureUserAgentSpecificMemory() на GitHub или добавьте свои мысли к существующей проблеме.

Сообщить о проблеме с реализацией

Вы нашли ошибку в реализации Chrome? Или реализация отличается от спецификации? Сообщите об ошибке на сайте new.crbug.com . Обязательно укажите как можно больше подробностей, предоставьте простые инструкции по воспроизведению ошибки и установите для параметра «Компоненты» значение Blink>PerformanceAPIs . Glitch отлично подходит для быстрого и простого обмена репродукциями.

Показать поддержку

Планируете ли вы использовать performance.measureUserAgentSpecificMemory() ? Ваша публичная поддержка помогает команде Chrome расставлять приоритеты в функциях и показывает другим поставщикам браузеров, насколько важно их поддерживать. Отправьте твит @ChromiumDev и сообщите нам, где и как вы его используете.

Полезные ссылки

Благодарности

Большое спасибо Доменику Дениколе, Йоаву Вайсу, Матиасу Биненсу за обзоры дизайна API, а также Доминику Инфюру, Ханнесу Пайеру, Кентаро Хара, Михаэлю Липпаутцу за обзоры кода в Chrome. Я также благодарю Пера Паркера, Филиппа Вайса, Ольгу Беломестных, Мэтью Болохана и Нила Маккея за ценные отзывы пользователей, которые значительно улучшили API.

Героическое изображение Харрисона Бродбента на Unsplash