- PVSM.RU - https://www.pvsm.ru -

Мониторинг всей памяти, используемой веб-страницей: performance.measureMemory()

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

Мониторинг всей памяти, используемой веб-страницей: performance.measureMemory() - 1 [1]

Браузеры автоматически управляют памятью, выделяемой веб-страницам. Когда страница создаёт объект, браузер, используя свои внутренние механизмы, выделяет память для хранения этого объекта. Так как память — это не бесконечный ресурс, браузер периодически выполняет процедуру сборки мусора, в ходе которой обнаруживаются ненужные объекты и очищается занимаемая ими памяти. Процесс обнаружения таких объектов, правда, не идеален. Было доказано [2], что абсолютно точное и полное выявление таких объектов — неразрешимая задача. В результате браузеры заменяют идею поиска «ненужных объектов» на идею поиска «недостижимых объектов». Если веб-страница не может обратиться к объекту через имеющиеся у неё переменные и поля других объектов, достижимых для неё, это значит, что браузер может безопасно очистить память, занимаемую таким объектом. Разница между «ненужным» и «недостижимым» приводит к утечкам памяти, что проиллюстрировано в следующем примере:

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

Здесь имеется большой ненужный массив b, но браузер не освобождает занимаемую им память из-за того, что он достижим через свойство объекта object.b в коллбэке. В результате память, занимаемая этим массивом, «утекает».

Утечки памяти — это распространённое [3] явление в веб-разработке. Они очень легко появляются в программах, когда, например, разработчики забывают отменить подписку на прослушиватель событий, когда случайно захватывают объекты в элементе iframe, когда забывают закрыть воркер, когда собирают объекты в массивах. Если на веб-странице есть утечки памяти, это приводит к тому, что со временем растёт потребление памяти страницей. Такая страница кажется пользователям медленной и неповоротливой.

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

Чем новый 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 в разных браузерах, не поддаются сравнению. Сравнивать имеет смысл лишь результаты, полученные в одном и том же браузере.

Текущий ход работ

Шаг Состояние
1. Создание пояснений к API Завершено [4]
2. Создание черновика спецификации Выполняется
3. Сбор отзывов и доработка проекта Выполняется
4. Испытания по схеме Origin Trial Выполняется [5]
5. Запуск Не начат

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

▍Включение поддержки на этапе Origin Trial

API performance.measureMemory() доступен в Chrome 83 по схеме Origin Trial. Ожидается, что эта фаза завершится с выходом Chrome 84.

Программа Origin Trial позволяет разработчикам пользоваться новыми возможностями Chrome и делиться с веб-сообществом отзывами об удобстве, практичности и эффективности этих возможностей. Подробности об этой программе можно почитать здесь [6]. Подписаться на участие в программе можно на странице [7] регистрации.

▍Регистрация в программе Origin Trial

  1. Запросите [5] токен для интересующей вас возможности.
  2. Добавьте токен на страницы экспериментального проекта. Существует 2 способа это сделать:
    • Добавьте <meta>-тег origin-trial в заголовок каждой страницы. Например, это может выглядеть так: <meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">.
    • Если у вас есть доступ к настройкам сервера — может добавить токен с использованием HTTP-заголовка Origin-Trial. В результате в заголовке ответа должно появиться нечто, подобное следующему: Origin-Trial: TOKEN_GOES_HERE.

▍Включение новой возможности через флаги Chrome

Для того чтобы поэкспериментировать с performance.measureMemory(), обойдясь при этом без токена Origin Trial, нужно включить флаг #experimental-web-platform-features в chrome://flags.

▍Проверка возможности использования API

Вызов функции performance.measureMemory() может завершиться неудачно, с выдачей ошибки SecurityError [8]. Это может произойти в том случае, если окружение не удовлетворяет требованиям по безопасности, касающимся утечек информации. В процессе Origin Trial-тестирования в Chrome этот API требует включения возможности Site Isolation [9]. Когда API будет готов к обычному использованию, он будет полагаться на свойство crossOriginIsolated [10]. Веб-страница может работать в таком режиме, установив заголовки COOP и COEP [11].

Вот пример кода:

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 минут. Это, правда, вносит в данные искажения, так как пики в использовании памяти могут приходиться на периоды между измерениями. Следующий пример демонстрирует то, как, с использованием процесса Пуассона [12], производить измерения, свободные от систематических ошибок. Этот подход гарантирует то, что сеансы измерений могут, с равной вероятностью, прийтись на любой момент времени (вот [13] демонстрация этого подхода, вот [14] — исходный код).

Сначала объявим функцию, которая планирует следующий запуск сеанса измерения объёма потребляемой памяти с использованием функции 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() находит случайный интервал, выраженный в миллисекундах, задаваемый таким образом, чтобы одно измерение проводилось бы примерно каждые пять минут. Если вам интересны математические концепции, на которых основана эта функция — почитайте об экспоненциальном распределении [15].

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. При выводе этой оценки используются разделители [16] разрядов чисел. Эти значения сильно зависят от реализации. Если они получены для разных браузеров, то сравнивать их нельзя. То, как они получаются, может различаться даже в разных версиях одного и того же браузера. Пока длится программа Origin Trial — в возвращаемые значения входят показатели использования JavaScript-памяти главным окном, показатели использования памяти элементов iframe с того же сайта, и показатели связанных окон. Когда API будет готов, это значение будет представлять собой сведения о памяти, потребляемой JavaScript, DOM, всеми элементами iframe, связанными окнами и веб-воркерами.

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

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

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

Web Performance Community Group [17] и команда разработчиков Chrome рады будут узнать о том, что вы думаете о performance.measureMemory(), и узнать о вашем опыте использования этого API.

Поделитесь с нами своими идеями об устройстве API

Есть ли в этом API что-то такое, что работает не так, как ожидается? Может, в нём не хватает чего-то такого, что нужно вам для реализации вашей идеи? Откройте новую задачу в трекере [18] проекта или прокомментируйте существующую задачу.

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

Нашли ошибку в реализации Chrome? А может, оказалось, что реализация отличается от спецификации? Сделайте запись об ошибке здесь: new.crbug.com [19]. Постарайтесь включить как можно больше деталей в своё сообщение, включите в него простую инструкцию о том, как воспроизвести ошибку, и укажите, что проблема имеет отношение к Blink>PerformanceAPIs. Для демонстрации ошибок очень хорошо подходит Glitch [20].

Поддержите нас

Планируете пользоваться performance.measureMemory()? Если так — расскажите об этом. Такие рассказы помогают команде разработчиков Chrome расставлять приоритеты. Эти рассказы показывают создателям других браузеров важность поддержки новых возможностей. Если хотите — отправьте твит @ChromiumDev [21] и расскажите нам о том, где и как вы пользуетесь новым API.

Уважаемые читатели! Пробовали ли вы performance.measureMemory()?

Автор: ru_vds

Источник [22]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/352344

Ссылки в тексте:

[1] Image: https://habr.com/ru/company/ruvds/blog/497986/

[2] доказано: https://en.wikipedia.org/wiki/Halting_problem

[3] распространённое: https://docs.google.com/presentation/d/14uV5jrJ0aPs0Hd0Ehu3JPV8IBGc3U8gU6daLAqj6NrM/edit#slide=id.p

[4] performance.measureMemory(): https://github.com/WICG/performance-measure-memory

[5] Выполняется: https://developers.chrome.com/origintrials/#/view_trial/1281274093986906113

[6] здесь: https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/developer-guide.md

[7] странице: https://developers.chrome.com/origintrials/#/trials/active

[8] SecurityError: https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-SecurityError

[9] Site Isolation: https://developers.google.com/web/updates/2018/07/site-isolation

[10] crossOriginIsolated: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/crossOriginIsolated

[11] COOP и COEP: https://docs.google.com/document/d/1zDlfvfTJ_9e8Jdc8ehuV4zMEu9ySMCiTGMS9y0GU92k/edit

[12] процесса Пуассона: https://en.wikipedia.org/wiki/Poisson_point_process

[13] вот: https://performance-measure-memory.glitch.me/

[14] вот: https://glitch.com/edit/#!/performance-measure-memory?path=script.js:1:0

[15] экспоненциальном распределении: https://en.wikipedia.org/wiki/Exponential_distribution#Computational_methods

[16] разделители: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Numeric_separators

[17] Web Performance Community Group: https://www.w3.org/community/webperfs/

[18] трекере: https://github.com/WICG/performance-measure-memory/issues

[19] new.crbug.com: https://bugs.chromium.org/p/chromium/issues/entry?components=Blink%3EPerformanceAPIs

[20] Glitch: https://glitch.com/

[21] @ChromiumDev: https://twitter.com/chromiumdev

[22] Источник: https://habr.com/ru/post/497986/?utm_source=habrahabr&utm_medium=rss&utm_campaign=497986