Очень важно иметь ориентированные на пользователя показатели , которые можно измерить повсеместно на любом веб-сайте. Эти показатели позволяют:
- Поймите, как реальные пользователи воспринимают Интернет в целом.
- Сравните свой сайт с сайтом конкурента.
- Отслеживайте полезные и полезные данные в своих аналитических инструментах без необходимости писать собственный код.
Универсальные показатели предлагают хороший базовый уровень, но во многих случаях вам нужно измерить больше , чем просто эти метрики, чтобы захватить полный опыт для вашего конкретного сайта.
Специальные метрики позволяют измерять аспекты взаимодействия с вашим сайтом, которые могут относиться только к нему, например:
- Сколько времени требуется одностраничному приложению (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()
предоставляют аналогичные возможности, преимущество использования User Timing API заключается в том, что он хорошо интегрируется с инструментами повышения производительности. Например, Chrome DevTools визуализирует измерения пользовательского времени на панели «Производительность» , а многие поставщики аналитики также автоматически отслеживают любые ваши измерения и отправляют данные о продолжительности на свой аналитический сервер.
Чтобы сообщить об измерениях пользовательского времени, вы можете использовать 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 синхронизации элементов
The Largest Contentful Paint (LCP) metric is useful for knowing when the largest image or text block was painted to the screen, but in some cases you want to measure the render time of a different element.
В этих случаях используйте Element Timing API . API LCP фактически построен на основе API синхронизации элементов и добавляет автоматические отчеты о самом большом элементе с содержимым, но вы также можете создавать отчеты о других элементах, явно добавляя к ним атрибут 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 синхронизации ресурсов дает разработчикам подробную информацию о том, как были загружены ресурсы для конкретной страницы. Несмотря на название API, предоставляемая им информация не ограничивается только данными о времени (хотя их достаточно ). Другие данные, к которым вы можете получить доступ, включают:
-
initiatorType
: как был получен ресурс: например, из тега<script>
или<link>
или из вызоваfetch()
. -
nextHopProtocol
: протокол, используемый для получения ресурса, напримерh2
илиquic
. -
encodedBodySize
/ decodedBodySize ]: размер ресурса в закодированном или декодированном виде (соответственно) -
transferSize
: размер ресурса, который был фактически передан по сети. Когда ресурсы заполняются кешем, это значение может быть намного меньше, чемencodedBodySize
, а в некоторых случаях оно может быть нулевым (если повторная проверка кеша не требуется).
You can use the transferSize
property of resource timing entries to measure a cache hit rate metric or a total cached resource size metric, which can be useful in understanding how your resource caching strategy affects performance for repeat visitors.
В следующем примере регистрируются все ресурсы, запрошенные страницей, и указывается, был ли каждый ресурс обработан кэшем.
// 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 синхронизации навигации аналогичен API синхронизации ресурсов, но он сообщает только о запросах навигации . Тип записи navigation
также аналогичен типу записи resource
, но он содержит некоторую дополнительную информацию , специфичную только для запросов навигации (например, при возникновении событий DOMContentLoaded
и load
).
Одна метрика, которую многие разработчики отслеживают, чтобы понять время ответа сервера ( время до первого байта (TTFB) ), доступна с помощью API синхронизации навигации — в частности, это метка времени 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 для запросов навигации. Это количество времени, которое требуется браузеру для запуска рабочего потока службы, прежде чем он сможет начать перехватывать события выборки.
Время запуска сервис-воркера для конкретного запроса навигации можно определить по разнице между 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 синхронизации сервера позволяет передавать данные о времени конкретного запроса с вашего сервера в браузер через заголовки ответов. Например, вы можете указать, сколько времени потребовалось для поиска данных в базе данных для определенного запроса, что может быть полезно при отладке проблем с производительностью, вызванных медленной работой сервера.
Для разработчиков, которые используют сторонних поставщиков аналитики, API синхронизации сервера — единственный способ сопоставить данные о производительности сервера с другими бизнес-показателями, которые могут измеряться этими инструментами аналитики.
Чтобы указать данные синхронизации сервера в ваших ответах, вы можете использовать заголовок ответа Server-Timing
. Вот пример.
HTTP/1.1 200 OK
Server-Timing: miss, db;dur=53, app;dur=47.2
Затем со своих страниц вы можете прочитать эти данные как о resource
, так и о записях navigation
из API-интерфейсов синхронизации ресурсов и времени навигации.
// 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});