Отслеживание общего использования памяти веб-страницей с помощью функции measureUserAgentSpecificMemory()
Узнайте, как измерять использование памяти веб-страницей в рабочей среде, чтобы обнаруживать регрессии.
Браузеры автоматически управляют памятью веб-страниц. Когда веб-страница создает какой-либо объект, браузер выделяет фрагмент памяти для хранения этого объекта. Так как память — ограниченный ресурс, браузер выполняет сборку мусора. Это позволяет определить, что тот или иной объект больше не нужен, и можно освободить занимаемый им фрагмент памяти. Тем не менее функция обнаружения работает неидеально, и было доказано, что идеальное обнаружение — невыполнимая задача. Поэтому браузеры аппроксимируют понятие «объект необходим» понятием «объект достижим». Если веб-странице не удается обратиться к объекту через его переменные и поля других достижимых объектов, браузер может безопасно «утилизировать» объект. Разница между этими двумя понятиями приводит к утечкам памяти, как показано в приведенном ниже примере.
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, а новый оценивает объем памяти, используемый веб-страницей. Это различие важно, когда Chrome использует одну и ту же кучу для нескольких веб-страниц (или нескольких экземпляров одной и той же веб-страницы). В таких случаях результат старого API может быть случайным образом смещен. Так как старый API определяется терминами, зависящими от реализации, например «куча», попытки стандартизировать его обречены на провал.
Еще одно отличие состоит в том, что новый API выполняет измерение памяти во время сборки мусора. Это уменьшает «шум» в результатах, но для получения результатов может потребоваться некоторое время. Обратите внимание, что разработчики других браузеров могут реализовать новый API, не используя сборку мусора.
Предлагаемые сценарии использования #
То, каким образом веб-страница использует память, зависит от временных параметров событий, действий пользователя и операций сборки мусора. Вот почему API измерения памяти предназначен для агрегирования данных об использовании памяти в рабочей среде. Результаты индивидуальных вызовов менее полезны. Ниже приведены возможные сценарии использования такого API.
- Обнаружение регрессий во время развертывания новой версии веб-страницы, что позволяет обнаруживать новые утечки памяти.
- A/B-тестирование новой функции для оценки ее влияния на память и обнаружения утечек памяти.
- Корреляция использования памяти с продолжительностью сеанса для проверки наличия или отсутствия утечек памяти.
- Корреляция использования памяти с пользовательскими метриками, что позволяет понять, какое общее влияние оказывает использование памяти.
Совместимость с браузерами #
В настоящее время этот API поддерживается только в Chrome 83 в виде версии Origin Trials. Результаты работы API сильно зависят от его реализации, так как в браузерах применяются разные способы представления объектов в памяти и разные способы оценки использования памяти. Браузеры могут не учитывать некоторые области памяти, если надлежащий учет обходится слишком дорого либо невозможен. Таким образом, нельзя сравнивать результаты, получаемые в разных браузерах. Имеет смысл сравнивать только результаты для одного и того же браузера.
Текущий статус #
Этап | Статус |
---|---|
1. Создание эксплейнера | Выполнен |
2. Создание первоначального проекта спецификации | Выполнен |
3. Сбор отзывов и доработка дизайна | Выполняется |
4. Версия Origin Trials | Выполнен |
5. Запуск | Включено по умолчанию в Chrome 89 |
Использование функции performance.measureUserAgentSpecificMemory()
#
Включение на странице about://flags #
Чтобы поэкспериментировать с функцией performance.measureUserAgentSpecificMemory()
без токена Origin Trials, включите флаг #experimental-web-platform-features
на странице about://flags
.
Обнаружение функций #
Если среда выполнения не соответствует требованиям безопасности в части предотвращения утечки информации между источниками, работа функции performance.measureUserAgentSpecificMemory()
может завершиться ошибкой SecurityError. На этапе Origin Trials в Chrome для API требуется, чтобы была включена функция Site Isolation (Изоляция сайтов). В окончательной версии API будет использоваться функция изоляции между источниками. Веб-страница может включить функцию изоляции между источниками, настроив заголовки COOP и COEP.
if (performance.measureUserAgentSpecificMemory) {
let result;
try {
result = await performance.measureUserAgentSpecificMemory();
} catch (error) {
if (error instanceof DOMException && error.name === 'SecurityError') {
console.log('Этот контекст небезопасен.');
} else {
throw error;
}
}
console.log(result);
}
Тестирование в локальной среде #
Chrome выполняет измерение памяти во время сборки мусора. Это означает, что API не сопоставляет обещание результата немедленно, а вместо этого ожидает следующей сборки мусора. API принудительно выполняет сборку мусора по истечении некоторого времени ожидания, которое на сегодняшний день равно 20 секундам. Запустив Chrome с флагом командной строки --enable-blink-features='ForceEagerMeasureMemory'
, можно уменьшить время ожидания до нуля, что удобно при отладке и тестировании в локальной среде.
Пример #
Рекомендуемое использование этого API — создание глобального средства мониторинга памяти, которое делает выборки данных об использовании памяти всей веб-страницей и отправляет результаты на сервер для агрегирования и анализа. Самый простой способ — периодически (например, каждые M
минут) делать выборку. Однако это приводит к появлению ошибок оценки, так как между выборками могут возникать пиковые показатели использования памяти. В приведенном ниже примере показано, как выполнять измерения памяти без ошибок с использованием пуассоновского процесса. Это гарантирует, что вероятность создания выборок будет одинакова для любых моментов времени (демонстрация, источник).
Прежде всего определите функцию, которая планирует следующее измерение памяти, используя функцию setTimeout()
со случайными интервалами. Функцию следует вызывать после загрузки страницы в главном окне.
function scheduleMeasurement() {
if (!performance.measureUserAgentSpecificMemory) {
console.log(
'Функция performance.measureUserAgentSpecificMemory() недоступна.',
);
return;
}
const interval = measurementInterval();
console.log(
'Scheduling memory measurement in ' +
Math.round(interval / 1000) +
' с.',
);
setTimeout(performMeasurement, interval);
}
// Запуск измерений после загрузки страницы в главном окне.
window.onload = function () {
scheduleMeasurement();
};
Функция 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. Вызов функции performance.measureUserAgentSpecificMemory().
let result;
try {
result = await performance.measureUserAgentSpecificMemory();
} catch (error) {
if (error instanceof DOMException && error.name === 'SecurityError') {
console.log('Этот контекст небезопасен.');
return;
}
// Повторное создание других ошибок.
throw error;
}
// 2. Запись результата.
console.log('Memory usage:', result);
// 3. Планирование следующего измерения.
scheduleMeasurement();
}
Результат может выглядеть следующим образом:
// Вывод в консоль:
{
bytes: 60_000_000,
breakdown: [
{
bytes: 40_000_000,
attribution: [
{
url: "https://foo.com",
scope: "Window",
},
]
types: ["JS"]
},
{
bytes: 0,
attribution: [],
types: []
},
{
bytes: 20_000_000,
attribution: [
{
url: "https://foo.com/iframe",
container: {
id: "iframe-id-attribute",
src: "redirect.html?target=iframe.html",
},
},
],
types: ["JS"]
},
]
}
Для возврата значения оценки общего использования памяти используется поле bytes
. Для значения количества байтов используется синтаксис чисел с разделителями. Это значение сильно зависит от реализации, и его нельзя сравнивать для разных браузеров. Оно может быть разным даже для различных версий одного и того же браузера. На этапе Origin Trials это значение включает показатель использования памяти JavaScript в главном окне, а также во всех элементах iframe одного и того же сайта и в связанных окнах. В окончательной версии API в этом значении будет учитываться память JavaScript и DOM для всех элементов iframe, связанных окон и рабочих веб-процессов в текущем процессе. Обратите внимание, что если включена функция Site Isolation (Изоляция сайтов), API не измеряет память внепроцессных элементов iframe между сайтами.
В списке breakdown
содержится дополнительная информация об используемой памяти. Каждая запись описывает определенную часть памяти и связывает ее с набором окон, элементов iframe и рабочих процессов, идентифицируемых с помощью URL-адресов. В поле types
указаны типы памяти, зависящие от реализации и связанные с памятью.
Важно обрабатывать все списки общим способом, а не жестко задавать допущения, основанные на конкретном браузере. Например, одни браузеры могут возвращать пустое поле breakdown
или attribution
. Другие браузеры могут возвращать несколько записей в поле attribution
, тем самым сообщая, что им не удается определить, какой из этих записей принадлежит память.
Обратная связь #
Группа Web Performance Community Group и команда разработчиков Chrome будут рады, если вы поделитесь своими мыслями и расскажете об опыте использования функции performance.measureUserAgentSpecificMemory()
.
Расскажите, что вы думаете о конструкции API #
Есть ли в API элементы, не работающие должным образом? Может в нем нет свойств, необходимых для реализации вашей идеи? Сообщите о проблеме, связанной с техническими характеристиками, в репозитории performance.measureUserAgentSpecificMemory() на GitHub или изложите свои мысли о какой-либо существующей проблеме.
Сообщите о проблеме, связанной с реализацией #
Нашли ошибку в реализации для Chrome? Или реализация отличается от спецификации? Сообщите об ошибке на сайте new.crbug.com. Обязательно укажите как можно больше сведений, предоставьте простые инструкции по воспроизведению ошибки и задайте для параметра Components значение Blink>PerformanceAPIs
. Для быстрого и простого обмена инструкциями по воспроизведению ошибок отлично подходит ресурс Glitch.
Продемонстрируйте поддержку #
Планируете ли вы использовать функцию performance.measureUserAgentSpecificMemory()
? Благодаря вашей публичной поддержке команда разработчиков Chrome сможет правильно расставлять приоритеты при работе над функциями и продемонстрирует другим поставщикам браузеров, насколько важна их поддержка. Отправьте твит в аккаунт @ChromiumDev и сообщите нам, где и как вы используете эту функцию.
Полезные ссылки #
- Эксплейнер
- Демонстрация | Исходный код демонстрации
- Испытания по схеме Origin Trials
- Отслеживание ошибок
- Запись на сайте ChromeStatus.com
Благодарности #
Большое спасибо Доменику Деникола (Domenic Denicola), Йоаву Вайссу (Yoav Weiss) и Матиасу Биненсу (Mathias Bynens) за обзоры структуры API, а также Доминику Инфюру (Dominik Inführ), Ханнесу Пайеру (Hannes Payer), Кентаро Хара (Kentaro Hara) и Майклу Липпауцу (Michael Lippautz) за обзоры кода в Chrome. Я также благодарю Пера Паркера (Per Parker), Филиппа Вайса (Philipp Weis), Ольгу Беломестных, Мэтью Болохана (Matthew Bolohan) и Нила Маккея (Neil Mckay) за ценные отзывы пользователей, которые позволили значительно улучшить этот API.
В качестве главного изображения использовано фото Харрисона Бродбента (Harrison Broadbent) с Unsplash