[Перевод] Мониторинг всей памяти, используемой веб-страницей: performance.measureMemory()
Автор статьи, перевод которой мы сегодня публикуем, рассказывает о том, как мониторить память, выделяемую веб-страницам. Внимательное отношение к памяти страниц, работающих в продакшне, помогает поддерживать производительность веб-проектов на высоком уровне.
Браузеры автоматически управляют памятью, выделяемой веб-страницам. Когда страница создаёт объект, браузер, используя свои внутренние механизмы, выделяет память для хранения этого объекта. Так как память — это не бесконечный ресурс, браузер периодически выполняет процедуру сборки мусора, в ходе которой обнаруживаются ненужные объекты и очищается занимаемая ими памяти. Процесс обнаружения таких объектов, правда, не идеален. Было доказано, что абсолютно точное и полное выявление таких объектов — неразрешимая задача. В результате браузеры заменяют идею поиска «ненужных объектов» на идею поиска «недостижимых объектов». Если веб-страница не может обратиться к объекту через имеющиеся у неё переменные и поля других объектов, достижимых для неё, это значит, что браузер может безопасно очистить память, занимаемую таким объектом. Разница между «ненужным» и «недостижимым» приводит к утечкам памяти, что проиллюстрировано в следующем примере:
const object = { a: new Array(1000), b: new Array(2000) };
setInterval(() => console.log(object.a), 1000);
Здесь имеется большой ненужный массив b
, но браузер не освобождает занимаемую им память из-за того, что он достижим через свойство объекта object.b
в коллбэке. В результате память, занимаемая этим массивом, «утекает».
Утечки памяти — это распространённое явление в веб-разработке. Они очень легко появляются в программах, когда, например, разработчики забывают отменить подписку на прослушиватель событий, когда случайно захватывают объекты в элементе iframe
, когда забывают закрыть воркер, когда собирают объекты в массивах. Если на веб-странице есть утечки памяти, это приводит к тому, что со временем растёт потребление памяти страницей. Такая страница кажется пользователям медленной и неповоротливой.
Первый шаг в решении этой проблемы заключается в выполнении измерений. Новый API performance.measureMemory () позволяет разработчикам измерять уровень использования памяти веб-страницами в продакшне и, в результате, выявлять утечки памяти, проскользнувшие через локальные тесты.
Чем новый API performance.measureMemory () отличается от старого performance.memory?
Если вы знакомы с существующим нестандартным API performance.memory
, то вас, возможно, интересует вопрос о том, чем новый API отличается от старого. Главное отличие заключается в том, что старый API возвращает размер JavaScript-кучи, а новый оценивает использование памяти всей веб-страницей. Это различие оказывается важным в том случае, когда Chrome организует совместное использование кучи несколькими веб-страницами (или несколькими экземплярами одной и той же страницы). В таких случаях результаты, возвращаемые старым API, могут быть искажены. Так как старый API определён в терминах, специфичных для реализации, таких, как «куча», его стандартизация — безнадёжное дело.
Ещё одно отличие заключается в том, что в Chrome новый API производит измерения памяти при сборке мусора. Это уменьшает «шум» в результатах измерений, но может потребовать некоторого времени, необходимого для получения результатов. Обратите внимание на то, что создатели других браузеров могут решить реализовать новый API без привязки к сборке мусора.
Рекомендуемые способы использования нового API
Использование памяти веб-страницами зависит от возникновения событий, от действий пользователя, от сборки мусора. Именно поэтому API performance.measureMemory()
предназначен для исследования уровня использования памяти в продакшне. Результаты вызова этого API в тестовом окружении менее полезны. Вот примеры вариантов его использования:
- Выявление случаев замедления работы приложения в ходе развёртывания новой версии веб-страницы при обнаружении новых утечек памяти.
- A/B-тестирование новой возможности, направленное на оценку её воздействия на память и на обнаружение утечек памяти.
- Сопоставление использования памяти и длительности сессии для подтверждения наличия или отсутствия утечек памяти.
- Сопоставление использования памяти с метриками, характеризующими пользователя. Это позволяет понять воздействие уровня использования памяти на работу с приложением.
Браузерная совместимость
Сейчас рассматриваемый API поддерживается только в Chrome 83, по схеме Origin Trial. Результаты, возвращаемые API, сильно зависят от реализации, так как разные браузеры используют разные способы представления объектов в памяти и разные способы оценки уровня использования памяти. Браузеры могут исключать из учёта некоторые области памяти в том случае, если полный учёт всей используемой памяти является неоправданно сложной или невыполнимой задачей. В итоге можно сказать, что результаты, выдаваемые этим API в разных браузерах, не поддаются сравнению. Сравнивать имеет смысл лишь результаты, полученные в одном и том же браузере.
Текущий ход работ
Использование performance.measureMemory ()
▍Включение поддержки на этапе Origin Trial
API performance.measureMemory()
доступен в Chrome 83 по схеме Origin Trial. Ожидается, что эта фаза завершится с выходом Chrome 84.
Программа Origin Trial позволяет разработчикам пользоваться новыми возможностями Chrome и делиться с веб-сообществом отзывами об удобстве, практичности и эффективности этих возможностей. Подробности об этой программе можно почитать здесь. Подписаться на участие в программе можно на странице регистрации.
▍Регистрация в программе Origin Trial
- Запросите токен для интересующей вас возможности.
- Добавьте токен на страницы экспериментального проекта. Существует 2 способа это сделать:
- Добавьте
-тег
origin-trial
в заголовок каждой страницы. Например, это может выглядеть так:.
- Если у вас есть доступ к настройкам сервера — может добавить токен с использованием HTTP-заголовка
Origin-Trial
. В результате в заголовке ответа должно появиться нечто, подобное следующему:Origin-Trial: TOKEN_GOES_HERE
.
- Добавьте
▍Включение новой возможности через флаги Chrome
Для того чтобы поэкспериментировать с performance.measureMemory()
, обойдясь при этом без токена Origin Trial, нужно включить флаг #experimental-web-platform-features
в chrome://flags
.
▍Проверка возможности использования API
Вызов функции performance.measureMemory()
может завершиться неудачно, с выдачей ошибки SecurityError. Это может произойти в том случае, если окружение не удовлетворяет требованиям по безопасности, касающимся утечек информации. В процессе Origin Trial-тестирования в Chrome этот API требует включения возможности Site Isolation. Когда API будет готов к обычному использованию, он будет полагаться на свойство crossOriginIsolated. Веб-страница может работать в таком режиме, установив заголовки COOP и COEP.
Вот пример кода:
if (performance.measureMemory) {
let result;
try {
result = await performance.measureMemory();
} 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 рекомендуется использовать, определяя глобальный монитор памяти, который измеряет уровень использования памяти всей страницы и отправляет результаты на сервер, где они могут быть агрегированы и проанализированы. Самый простой способ организации работы с этим API заключается в проведении периодических измерений. Например, они могут выполняться каждые M
минут. Это, правда, вносит в данные искажения, так как пики в использовании памяти могут приходиться на периоды между измерениями. Следующий пример демонстрирует то, как, с использованием процесса Пуассона, производить измерения, свободные от систематических ошибок. Этот подход гарантирует то, что сеансы измерений могут, с равной вероятностью, прийтись на любой момент времени (вот демонстрация этого подхода, вот — исходный код).
Сначала объявим функцию, которая планирует следующий запуск сеанса измерения объёма потребляемой памяти с использованием функции setTimeout()
со случайно задаваемым интервалом. Эта функция должна быть вызвана после загрузки страницы в окно браузера.
function scheduleMeasurement() {
if (!performance.measureMemory) {
console.log("performance.measureMemory() is not available.");
return;
}
const interval = measurementInterval();
console.log("Scheduling memory measurement in " +
Math.round(interval / 1000) + " seconds.");
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.measureMemory().
let result;
try {
result = await performance.measureMemory();
} catch (error) {
if (error instanceof DOMException &&
error.name === "SecurityError") {
console.log("The context is not secure.");
return;
}
// Повторный выброс других ошибок.
throw error;
}
// 2. Запись результата.
console.log("Memory usage:", result);
// 3. Планирование следующего измерения.
scheduleMeasurement();
}
Результаты измерений могут выглядеть так:
// То, что выводится в консоль:
{
bytes: 60_000_000,
breakdown: [
{
bytes: 40_000_000,
attribution: ["https://foo.com"],
userAgentSpecificTypes: ["Window", "JS"]
},
{
bytes: 20_000_000,
attribution: ["https://foo.com/iframe"],
userAgentSpecificTypes: ["Window", "JS"]
}
]
}
Оценка общего уровня использования памяти выводится в поле bytes
. При выводе этой оценки используются разделители разрядов чисел. Эти значения сильно зависят от реализации. Если они получены для разных браузеров, то сравнивать их нельзя. То, как они получаются, может различаться даже в разных версиях одного и того же браузера. Пока длится программа Origin Trial — в возвращаемые значения входят показатели использования JavaScript-памяти главным окном, показатели использования памяти элементов iframe
с того же сайта, и показатели связанных окон. Когда API будет готов, это значение будет представлять собой сведения о памяти, потребляемой JavaScript, DOM, всеми элементами iframe
, связанными окнами и веб-воркерами.
Список breakdown
даёт более подробную информацию об используемой памяти. Каждая запись описывает некий фрагмент памяти и связывает этот фрагмент с набором окон, элементов iframe
или воркеров, идентифицируемых с помощью URL. В поле userAgentSpecificTypes
перечисляются типы памяти, определяемые особенностями реализации.
Важно рассматривать эти списки в общем виде и не пытаться, опираясь на особенности некоего браузера, анализировать всё, основываясь на них. Например, некоторые браузеры могут возвращать пустой список breakdown
или пустые поля attribution
. Другие браузеры могут возвращать в элементе attribution
по несколько URL, указывая на то, что они не могут точно определить, какому из этих URL принадлежит память.
Обратная связь
Web Performance Community Group и команда разработчиков Chrome рады будут узнать о том, что вы думаете о performance.measureMemory()
, и узнать о вашем опыте использования этого API.
Поделитесь с нами своими идеями об устройстве API
Есть ли в этом API что-то такое, что работает не так, как ожидается? Может, в нём не хватает чего-то такого, что нужно вам для реализации вашей идеи? Откройте новую задачу в трекере проекта или прокомментируйте существующую задачу.
Сообщите о проблеме с реализацией
Нашли ошибку в реализации Chrome? А может, оказалось, что реализация отличается от спецификации? Сделайте запись об ошибке здесь: new.crbug.com. Постарайтесь включить как можно больше деталей в своё сообщение, включите в него простую инструкцию о том, как воспроизвести ошибку, и укажите, что проблема имеет отношение к Blink>PerformanceAPIs
. Для демонстрации ошибок очень хорошо подходит Glitch.
Поддержите нас
Планируете пользоваться performance.measureMemory()
? Если так — расскажите об этом. Такие рассказы помогают команде разработчиков Chrome расставлять приоритеты. Эти рассказы показывают создателям других браузеров важность поддержки новых возможностей. Если хотите — отправьте твит @ChromiumDev и расскажите нам о том, где и как вы пользуетесь новым API.
Уважаемые читатели! Пробовали ли вы performance.measureMemory()
?