Наличие ориентированных на пользователя метрик , которые можно измерять универсально на любом веб-сайте, имеет огромную ценность. Эти метрики позволяют:
- Поймите, как реальные пользователи воспринимают интернет в целом.
- Сравните свой сайт с сайтом конкурента.
- Отслеживайте полезные и применимые на практике данные в своих аналитических инструментах без необходимости написания собственного кода.
Универсальные метрики обеспечивают хорошую отправную точку, но во многих случаях для полного понимания особенностей вашего конкретного сайта необходимо измерять не только эти метрики.
Пользовательские метрики позволяют измерять аспекты взаимодействия с вашим сайтом, которые могут быть актуальны только для вашего сайта, например:
- Сколько времени требуется одностраничному приложению (SPA) для перехода с одной «страницы» на другую.
- Сколько времени требуется для отображения на странице данных, полученных из базы данных, для авторизованных пользователей.
- Сколько времени требуется приложению, отрисовываемому на стороне сервера (SSR), для гидратации ?
- Показатель попадания в кэш для ресурсов, загружаемых повторными посетителями.
- Задержка событий щелчка мыши или нажатия клавиш в игре.
API для измерения пользовательских метрик
Исторически сложилось так, что у веб-разработчиков было мало низкоуровневых API для измерения производительности, и в результате им приходилось прибегать к обходным путям, чтобы оценить, насколько хорошо работает сайт.
Например, можно определить, заблокирован ли основной поток из-за длительных задач JavaScript, запустив цикл requestAnimationFrame и вычислив разницу между каждым кадром. Если разница значительно превышает частоту кадров дисплея, это можно расценить как длительную задачу. Однако такие обходные пути не рекомендуются, поскольку они сами по себе влияют на производительность (например, разряжая батарею).
Первое правило эффективного измерения производительности — убедиться, что используемые вами методы измерения производительности сами по себе не вызывают проблем с производительностью. Поэтому для любых пользовательских метрик, которые вы измеряете на своем сайте, лучше всего использовать один из следующих API, если это возможно.
API наблюдателя производительности
API Performance Observer — это механизм, который собирает и отображает данные из всех других API производительности, обсуждаемых на этой странице. Понимание его работы имеет решающее значение для получения качественных данных.
Вы можете использовать PerformanceObserver для пассивной подписки на события, связанные с производительностью. Это позволяет обратным вызовам API срабатывать в периоды простоя , а значит, они обычно не будут мешать производительности страницы.
Чтобы создать объект PerformanceObserver , передайте ему функцию обратного вызова, которая будет запускаться всякий раз, когда поступают новые данные о производительности. Затем укажите наблюдателю, какие типы данных следует отслеживать, используя метод observe() :
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
po.observe({type: 'some-entry-type'});
В следующих разделах перечислены все доступные типы записей для наблюдения, но в более новых браузерах вы можете проверить, какие типы записей доступны, с помощью статического свойства PerformanceObserver.supportedEntryTypes .
Просмотрите записи, которые уже произошли.
По умолчанию объекты PerformanceObserver могут отслеживать записи только по мере их появления. Это может вызвать проблемы, если вы хотите использовать отложенную загрузку кода анализа производительности, чтобы он не блокировал ресурсы с более высоким приоритетом.
Чтобы получить исторические записи (после того, как они произошли), установите флаг buffered в true при вызове observe() . Браузер будет включать исторические записи из своего буфера записей производительности при первом вызове функции обратного вызова PerformanceObserver , вплоть до максимального размера буфера для данного типа .
po.observe({
type: 'some-entry-type',
buffered: true,
});
Следует избегать использования устаревших API для повышения производительности.
До появления API Performance Observer разработчики могли получать доступ к данным о производительности, используя следующие три метода, определенные для объекта performance :
Хотя эти API по-прежнему поддерживаются, их использование не рекомендуется, поскольку они не позволяют отслеживать появление новых записей. Кроме того, многие новые API (например largest-contentful-paint ) не доступны через объект performance , а только через PerformanceObserver .
Если вам не требуется совместимость с Internet Explorer, лучше избегать этих методов в вашем коде и в дальнейшем использовать PerformanceObserver .
API для управления временем пользователя
API для измерения времени пользователем — это универсальный API для измерения временных показателей. Он позволяет произвольно отмечать точки во времени, а затем измерять продолжительность между этими точками.
// Record the time immediately before running a task.
performance.mark('myTask:start');
await doMyTask();
// Record the time immediately after running a task.
performance.mark('myTask:end');
// Measure the delta between the start and end of the task
performance.measure('myTask', 'myTask:start', 'myTask:end');
Хотя такие API, как Date.now() или performance.now() , предоставляют схожие возможности, преимущество использования API User Timing заключается в его хорошей интеграции с инструментами анализа производительности. Например, инструменты разработчика Chrome визуализируют измерения User Timing на панели «Производительность» , а многие поставщики аналитических услуг также автоматически отслеживают любые проведенные вами измерения и отправляют данные о продолжительности в свой аналитический бэкэнд.
Для формирования отчетов об измерениях времени выполнения пользователем можно использовать PerformanceObserver и зарегистрироваться для наблюдения за записями типа measure :
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `measure` entries to be dispatched.
po.observe({type: 'measure', buffered: true});
API для длительных задач
API для длительных задач полезен для определения момента блокировки основного потока браузера, достаточно долгого для того, чтобы повлиять на частоту кадров или задержку ввода. API сообщит о любых задачах, выполняющихся дольше 50 миллисекунд.
Всякий раз, когда вам нужно запустить ресурсоемкий код или загрузить и выполнить большие скрипты, полезно отслеживать, блокирует ли этот код основной поток. Фактически, многие высокоуровневые метрики построены на основе самого API длительных задач (например, время до интерактивности (TTI) и общее время блокировки (TBT) ).
Чтобы определить, когда возникают длительные задачи, можно использовать PerformanceObserver и зарегистрироваться для наблюдения за записями типа longtask :
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `longtask` entries to be dispatched.
po.observe({type: 'longtask', buffered: true});
API для длинных кадров анимации
API для длинных кадров анимации — это новая версия API для длинных задач, которая рассматривает длинные кадры , а не длинные задачи , длительностью более 50 миллисекунд. Это устраняет некоторые недостатки API для длинных задач , включая улучшенную атрибуцию и более широкий спектр потенциально проблемных задержек.
Чтобы определить, когда появляются длинные кадры, можно использовать PerformanceObserver и зарегистрироваться для наблюдения за записями типа long-animation-frame :
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `long-animation-frame` entries to be dispatched.
po.observe({type: 'long-animation-frame', buffered: true});
API синхронизации элементов
Показатель Largest Contentful Paint (LCP) полезен для определения момента отрисовки на экране самого большого изображения или текстового блока, но в некоторых случаях может потребоваться измерение времени отрисовки другого элемента.
В таких случаях используйте API Element Timing . API LCP фактически построен на основе API Element Timing и добавляет автоматическое отображение самого большого элемента, содержащего контент, но вы также можете отображать данные о других элементах, явно добавив к ним атрибут elementtiming и зарегистрировав PerformanceObserver для отслеживания типа element .
<img elementtiming="hero-image" />
<p elementtiming="important-paragraph">This is text I care about.</p>
<!-- ... -->
<script>
const po = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `element` entries to be dispatched.
po.observe({type: 'element', buffered: true});
</script>
API синхронизации событий
Показатель «Взаимодействие до следующей отрисовки» (INP) оценивает общую отзывчивость страницы, отслеживая все клики, касания и действия с клавиатурой на протяжении всего жизненного цикла страницы. Чаще всего INP страницы определяется взаимодействием, которое заняло больше всего времени, от момента инициирования пользователем взаимодействия до момента отрисовки браузером следующего кадра, отображающего визуальный результат ввода пользователя.
Метрика INP становится возможной благодаря API отслеживания времени событий . Этот API предоставляет доступ к ряду временных меток, возникающих в течение жизненного цикла события, включая:
-
startTime: время, когда браузер получает событие. -
processingStart: время, когда браузер может начать обработку обработчиков событий для данного события. -
processingEnd: время, когда браузер завершает выполнение всего синхронного кода, инициированного обработчиками событий для данного события. -
duration: время (округленное до 8 миллисекунд в целях безопасности) между моментом получения браузером события и моментом, когда он сможет отрисовать следующий кадр после завершения выполнения всего синхронного кода, инициированного обработчиками событий.
В следующем примере показано, как использовать эти значения для создания пользовательских измерений:
const po = new PerformanceObserver((entryList) => {
// Get the last interaction observed:
const entries = Array.from(entryList.getEntries()).forEach((entry) => {
// Get various bits of interaction data:
const inputDelay = entry.processingStart - entry.startTime;
const processingTime = entry.processingEnd - entry.processingStart;
const presentationDelay = entry.startTime + entry.duration - entry.processingEnd;
const duration = entry.duration;
const eventType = entry.name;
const target = entry.target || "(not set)"
console.log("----- INTERACTION -----");
console.log(`Input delay (ms): ${inputDelay}`);
console.log(`Event handler processing time (ms): ${processingTime}`);
console.log(`Presentation delay (ms): ${presentationDelay}`);
console.log(`Total event duration (ms): ${duration}`);
console.log(`Event type: ${eventType}`);
console.log(target);
});
});
// A durationThreshold of 16ms is necessary to include more
// interactions, since the default is 104ms. The minimum
// durationThreshold is 16ms.
po.observe({type: 'event', buffered: true, durationThreshold: 16});
API синхронизации ресурсов
API Resource Timing предоставляет разработчикам подробную информацию о том, как загружались ресурсы для конкретной страницы. Несмотря на название API, предоставляемая им информация не ограничивается только данными о времени загрузки (хотя и этого предостаточно ). Среди других доступных данных:
-
initiatorType: способ получения ресурса: например, из тега<script>или<link>, или из вызова функцииfetch(). -
nextHopProtocol: протокол, используемый для получения ресурса, напримерh2илиquic. -
encodedBodySize/ decodedBodySize ]: размер ресурса в закодированном или декодированном виде (соответственно). -
transferSize: размер ресурса, фактически переданного по сети. Когда ресурсы заполнены кэшем, это значение может быть значительно меньшеencodedBodySize, а в некоторых случаях может быть равно нулю (если повторная проверка кэша не требуется).
Свойство transferSize записей времени выполнения ресурсов можно использовать для измерения коэффициента попадания в кэш или общего размера кэшированных ресурсов , что может быть полезно для понимания того, как ваша стратегия кэширования ресурсов влияет на производительность для повторных посетителей.
В следующем примере регистрируются все ресурсы, запрошенные страницей, и указывается, был ли каждый ресурс заполнен кэшем.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log(entry.name, entry.transferSize === 0);
}
});
// Start listening for `resource` entries to be dispatched.
po.observe({type: 'resource', buffered: true});
API синхронизации навигации
API Navigation Timing аналогичен API Resource Timing, но сообщает только о запросах навигации . Тип записи navigation также похож на тип записи resource , но содержит некоторую дополнительную информацию, специфичную только для запросов навигации (например, когда срабатывают события DOMContentLoaded и load ).
Один из показателей, который многие разработчики отслеживают для понимания времени ответа сервера ( время до первого байта (TTFB) ), доступен с помощью API Navigation Timing — в частности, это метка времени responseStart для элемента.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log('Time to first byte', entry.responseStart);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});
Ещё один показатель, который может заинтересовать разработчиков, использующих Service Worker, — это время запуска Service Worker для запросов навигации. Это время, необходимое браузеру для запуска потока Service Worker, прежде чем он сможет начать перехватывать события выборки (fetch events).
Время запуска сервис-воркера для конкретного запроса навигации можно определить по разнице между entry.responseStart и entry.workerStart .
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Service Worker startup time:',
entry.responseStart - entry.workerStart);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});
API синхронизации сервера
API Server Timing позволяет передавать данные о времени выполнения конкретного запроса с вашего сервера в браузер через заголовки ответа. Например, вы можете указать, сколько времени потребовалось для поиска данных в базе данных для конкретного запроса, что может быть полезно при отладке проблем с производительностью, вызванных медленной работой сервера.
Для разработчиков, использующих сторонние аналитические инструменты, API Server Timing — единственный способ сопоставить данные о производительности сервера с другими бизнес-показателями, которые могут измерять эти аналитические инструменты.
Чтобы указать данные о времени работы сервера в ваших ответах, вы можете использовать заголовок ответа Server-Timing . Вот пример.
HTTP/1.1 200 OK
Server-Timing: miss, db;dur=53, app;dur=47.2
Затем, со своих страниц, вы можете считывать эти данные как для resource , так и для элементов navigation из API Resource Timing и Navigation Timing.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Logs all server timing data for this response
console.log('Server Timing', entry.serverTiming);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});