Время загрузки страницы и ресурсов
Тайминги навигации (Navigation timings) - это показатели, указывающие временные метки, в которые произошли события навигации. Тайминги ресурсов (Resource timings) - это детальные показатели по времени загрузки ресурсов.
В этой статье мы рассмотрим как Performance Timing API, так и Performance Entry API. И хотя первый API считается устаревшим, он все ещё поддерживается всеми браузерами, он прост и о нем полезно знать. В свою очередь, Performance Entry API является более продвинутым инструментом, который позволяет не только получить более сложные данные, но и позволяет разработчику измерять другие показатели, в дополнение к данным о навигации и загрузке ресурсов.
это JavaScript API для измерения времени загрузки страницы. Этот API считается устаревшим, но поддерживается во всех браузерах. На текущий момент рекомендуется использовать performanceNavigationTiming API.
PerformanceTiming API предоставляет собой read only данные в виде объекта, где значениями полей являются числа, указывающие на количество миллисекунд, которые прошли к моменту срабатывания того или иного события. Как показано на изображении ниже, процесс навигации можно разбить на следующие этапы: [navigationStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart)
, [unloadEventStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/unloadEventStart)
, [unloadEventEnd](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/unloadEventEnd)
, [redirectStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/redirectStart)
, [redirectEnd](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/redirectEnd)
, [fetchStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/fetchStart)
, [domainLookupStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/domainLookupStart)
, [domainLookupEnd](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/domainLookupEnd)
, connectStart
, [connectEnd](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/connectEnd)
, [secureConnectionStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/secureConnectionStart)
, [requestStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/requestStart)
, [responseStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/responseStart)
, [responseEnd](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/responseEnd)
, [domLoading](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/domLoading)
, [domInteractive](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/domInteractive)
, [domContentLoadedEventStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/domContentLoadedEventStart)
, [domContentLoadedEventEnd](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/domContentLoadedEventEnd)
, [domComplete](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/domComplete)
, [loadEventStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/loadEventStart)
, и [loadEventEnd](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/loadEventEnd)
.

Благодаря этим метрикам и небольшим вычислениям мы можем определить важные показатели, например время до первого байта (time to first byte), скорость загрузки страницы, поиска записи dns и даже узнать, является ли соединение безопасным.
Чтобы получить доступ к этим данным, обратитесь к следующему объекту:
let time = window.performance.timing
Мы можем использовать эти данные, чтобы понять, как быстро работает приложение:

Описание показателей:
Untitled
Мы можем использовать все эти значения, чтобы вычислить, сколько времени потребовалось на тот или иной этап:
let dns = time.domainLookupEnd - time.domainLookupStart, tcp = time.connectEnd - time.connectStart, ssl != time.secureConnectionStart,
Время до первого байта (Time to First Byte) - это время между navigationStart
и responseStart
(момент, когда получен первый байт от сервера / кеша). Доступно в performanceTiming
API
let ttfb = time.responseStart - time.navigationStart;
Время загрузки страницы (Page load time) - это время между navigationStart
и моментом, когда событие load
отправлено текущего документу. Доступно только в performanceTiming
API
let pageloadtime = time.loadEventStart - time.navigationStart;
Время поиска записи DNS (DNS lookup) - это время между [domainLookupStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/domainLookupStart)
и [domainLookupEnd](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/domainLookupEnd)
. Оба эти параметра доступны как в performanceTiming
, так и в performanceNavigationTiming
.
let dns = time.domainLookupEnd - time.domainLookupStart;
Время установки соединения TCP - это время между началом и окончанием попытки соединения:
tcp = time.connectEnd - time.connectStart;
[secureConnectionStart](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/secureConnectionStart)
будет равен undefined
, если SSL не доступен, 0
если https не используется или если временная метка доступна и используется. Другими словами, если безопасное соединение было использовано, то значение secureConnectionStart
будет правдиво (truthy), а время между secureConnectionStart
и requestStart
будет больше 0.
ssl = time.requestStart - time.secureConnectionStart;
Основные показатели производительности, рассмотренные выше, считаются устаревшими, но полностью поддерживаются современными браузерами. Взамен предлагается использовать [Performance Entry API
(en-US)](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry), который предоставляет инструмент для пометок и измерений времени одновременно с событиями navigation и загрузкой resource. Вы также можете создавать свои маркеры:
`performance.getEntriesByType('navigation').forEach((navigation) => { console.dir(navigation); });
performance.getEntriesByType('resource').forEach((resource) => { console.dir(resource); });
performance.getEntriesByType('mark').forEach((mark) => { console.dir(mark); });
performance.getEntriesByType("measure").forEach((measure) => { console.dir(measure); });
performance.getEntriesByType('paint').forEach((paint) => { console.dir(paint); });
performance.getEntriesByType('frame').forEach((frame) => { console.dir(frame); });`
В некоторых браузерах вы можете использовать performance.getEntriesByType('paint')
, чтобы запросить измерения для first-paint
и first-contentful-paint
. Мы используем performance.getEntriesByType('navigation')
и performance.getEntriesByType('resource')
для запроса данных по навигации и загрузки ресурсов, соответственно.
Когда пользователь запрашивает веб-приложение, браузер должен получить некоторые мета-данные, чтобы начать загрузку. Для этого пользовательский агент проходит серию шагов, такие как поиск записи DNS (DNS lookup), TCP рукопожатие TCP handshake (en-US), и установку безопасного соединения (SSL negotiation). Как только браузер установил соединение, происходит первый полезный запрос данных на сервера. Как только начинают поступать данные от сервера, браузер начинает парсить полученные данные, строит DOM, CSSOM, создаёт деревья рендера (render trees), чтобы в конце концов отрендерить страницу. В тот момент, когда браузер перестаёт парсить входящие данные, документ переходит в интерактивную стадию. Если в документе существуют отложенные к загрузке ресурсы (deferred scripts), которые должны быть обработаны, браузер парсит их. После этого запускается событие DOMContentLoaded, после которого готовность страницы завершена. Теперь документ может обрабатывать пост-загрузочные задачи. После этого документ маркируется, как полностью загруженный.
let navigationTimings = performance.getEntriesByType('navigation');
Метод performance.getEntriesByType('navigation')
возвращает массив PerformanceEntry, в котором содержатся объекты Navigation Timing.

Из этих данных можно многое извлечь. На изображении выше вы видите, что помимо самих таймингов, данные содержат имя документа и некоторую другую полезную информацию.
let timing = performance.getEntriesByType('navigation')[0];
Мы можем проверить протокол, который используется дл получения ресурсов:
let protocol = timing.nextHopProtocol
В текущем случае в ответ будет h2
для http/2
.
Чтобы узнать, как эффективно сжимаются данные при передаче, мы можем разделить transferSize
на decodedBodySize
, а затем вычесть результат из 100%. Для текущей страницы сжатие составляет до 74%.
let compressionSavings = 1 - (timing.transferSize / timing.decodedBodySize)
Мы могли бы использовать
let compressionSavings = 1 - (timing.encodedBodySize / timing.decodedBodySize)
но transfersize
так же включает в себя байты заголовков.
Для сравнение, мы можем посмотреть на вкладку Network, где увидим, что было передано 22.04KB для файла, который в разархивированном виде занимает 87.24KB.

Если мы проверим вычисления, то результат получится схожим: 1 - (22.04 / 87.24) = 0.747
. Тайминги навигации позволяют нам получить такие данные программно.
Обратите внимание, что это данные для одного единственно документа, а не для всех ресурсов вместе взятых. В то же время, длительность загрузки, события-обработчики и тайминги построения DOM / CSSOM влияют на продолжительность загрузки всего приложения, не только одного конкретного ресурса. Клиентские приложения, выполняющиеся в браузере, могут выглядеть быстрее, если данные объёмом 300КБ вы передаёте сжатыми до 100КБ, но это все не значит, что JavaScript, CSS или другие медиа-ресурсы не раздувают приложение и не делают его медленнее. Проверка уровня сжатия - это очень важно, но не менее важно проверять длительность парсинга ресурсов и время между тем, как завершён DOMContentLoaded и DOM готов к работе. Может случиться так, что время парсинга скриптов и обработка скриптами результатов в основном потоке (main thread) приведёт к зависанию интерфейса.
API не предоставляет все измерения, которые разработчик хочет получить. Например, как долго продлилось выполнение запроса? Отдельного поля в объекте данных нет. Однако, мы можем использовать измерения, чтобы вычислить то, что нам нужно.
Чтобы определить время ответа, вычтите время старта запроса из времени старта получения ответа. Запрос стартует ровно в тот момент, когда клиент запрашивает ресурс с сервера (или из кеша). Ответ начинается ровно в тот момент, когда клиент получает первый байт.
request = timing.responseStart - timing.requestStart
load = timing.loadEventEnd - timing.loadEventStart
Длительность события DOMContentLoaded определяется разностью моментов, когда клиент запускает событие DOMContentLoaded и когда это событие завершено. Старайтесь держать эту величину меньше 50ms - тогда ваш интерфейс будет отзывчивым.
DOMContentLoaded = timing.domContentLoadedEventEnd - timing.domContentLoadedEventStart
В объекте данных есть поле Длительность (Duration
). Длительность - это разница между PerformanceNavigationTiming.loadEventEnd и PerformanceEntry.startTime properties.
Интерфейс PerformanceNavigationTiming, кроме того, даёт информацию о том, какой тип навигации вы измеряете, возвращая navigate
, reload
, back_forward
или prerender
.
В то время, как тайминги навигации измеряют производительность загрузки и парсинга основного файла HTML, этот файл служит лишь точкой входа для загрузки других ресурсов. Поэтому нам так же важно знать, как быстро загружаются дополнительные ресурсы. Для измерения этих данных нужно использовать Resource Timing. Большая часть измерений в этом объекте похожи: здесь и поиск домена в DNS, и TCP установка соединения и т.д.

Для того, чтобы получить эти данные, выполните команду:
performance.getEntriesByType("resource")
Last updated