User Timing API

О веб-приложении

Alex Danilo

Высокая производительность веб-приложений имеет решающее значение для удобства пользователей. Веб-приложения становятся все сложнее, поэтому, чтобы создавать привлекательный контент, важно понимать, как он влияет на производительность. За последние несколько лет в браузере появилось несколько разных API, которые помогают анализировать производительность сети, время загрузки и т. д., но они не обязательно предоставляют подробные сведения с достаточной гибкостью, чтобы найти то, что замедляет работу вашего приложения. Введите User Timing API, который позволяет определить, на что уходит время при работе веб-приложения. В этой статье мы расскажем об API и приведем примеры его использования.

Невозможно оптимизировать то, что нельзя измерить

Чтобы ускорить работу веб-приложения, сначала нужно выяснить, на что уходит время. Измерение времени выполнения фрагментов кода JavaScript – идеальный способ выявить проблемные места, что является первым шагом к повышению производительности. К счастью, User Timing API позволяет вставлять вызовы API в разные части кода JavaScript, а затем извлекать подробные данные о времени, которые можно использовать для оптимизации.

Высокое разрешение и now()

Точность – это фундаментальная часть точного измерения времени. Раньше мы использовали тайминг на основе миллисекунд, что нормально, но создание сайта со скоростью 60 кадров в секунду без задержек означает, что каждый кадр должен быть отрисован за 16 мс. Поэтому, когда у вас есть только точность в миллисекундах, ей не хватает точности, необходимой для хорошего анализа. Для этого в современных браузерах используется новый тип времени – время высокого разрешения. High Resolution Time предоставляет нам временные метки с плавающей запятой, которые могут быть точными до микросекундного разрешения – в тысячу раз лучше, чем раньше.

Чтобы получить текущее время в веб-приложении, вызовите метод now(), который формирует расширение интерфейса Performance. Ниже показано, как это сделать.

var myTime = window.performance.now();

Существует ещё один интерфейс, PerformanceTiming, который предоставляет ряд различных временных показателей, связанных с загрузкой веб-приложения. Метод now() возвращает время, прошедшее с момента navigationStart в PerformanceTiming.

Тип DOMHighResTimeStamp

Раньше для измерения времени в веб-приложениях использовался метод Date.now(), который возвращает DOMTimeStamp. DOMTimeStamp возвращает целое число миллисекунд. Чтобы обеспечить более высокую точность, необходимую для High Resolution Time, был введен новый тип под названием DOMHighResTimeStamp. Это значение с плавающей запятой, которое также возвращает время в миллисекундах. Но поскольку это значение с плавающей запятой, оно может представлять дробные миллисекунды и, следовательно, обеспечивать точность до одной тысячной миллисекунды.

Интерфейс User Timing

Теперь, когда у нас есть временные метки с высоким разрешением, давайте воспользуемся интерфейсом User Timing, чтобы получить информацию о времени.

Интерфейс User Timing предоставляет функции, позволяющие вызывать методы в разных местах приложения. Это позволяет отслеживать, на что тратится время.

Использование mark()

Метод mark() – основной инструмент для анализа времени. Функция mark() сохраняет временную метку. Что очень полезно в mark(), так это то, что мы можем назвать временную метку, и API запомнит имя и временную метку как единое целое.

Вызывая mark() в разных местах приложения, вы можете узнать, сколько времени потребовалось, чтобы достичь этой отметки в веб-приложении.

В спецификации предлагается ряд названий для меток, которые могут быть интересны и понятны, например mark_fully_loaded, mark_fully_visible, mark_above_the_fold и т. д.

Например, мы можем установить метку для момента, когда приложение полностью загружено, используя следующий код:

window.performance.mark('mark_fully_loaded');

Установив именованные метки в веб-приложении, мы можем собрать множество данных о времени и проанализировать их, чтобы понять, что и когда делает приложение.

Вычисление измерений с помощью measure()

После того как вы установили несколько временных меток, вы захотите узнать время, прошедшее между ними. Для этого используется метод measure().

Метод measure() вычисляет время, прошедшее между метками, а также может измерять время между вашей меткой и любым из известных названий событий в интерфейсе PerformanceTiming.

Например, вы можете определить время от завершения загрузки DOM до полной загрузки состояния приложения, используя следующий код:

window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');

При вызове measure() результат сохраняется независимо от установленных меток, поэтому вы можете получить их позже. Сохраняя время выполнения приложения, вы можете выгрузить все данные после завершения работы приложения, чтобы проанализировать их позже.

Удаление меток с помощью clearMarks()

Иногда бывает полезно избавиться от кучи меток, которые вы установили. Например, вы можете выполнять пакетные запуски в своем веб-приложении и поэтому хотите начинать каждый запуск заново.

Чтобы удалить все установленные метки, вызовите метод clearMarks().

Приведенный ниже пример кода удалит все существующие метки, чтобы вы могли снова настроить запуск по времени.

window.performance.clearMarks();

Конечно, есть сценарии, в которых вы не захотите удалять все свои оценки. Если вы хотите удалить определенные метки, вы можете просто передать имя метки, которую хотите удалить. Например, в коде ниже:

window.performance.clearMarks('mark_fully_loaded');

удаляет метку, установленную в первом примере, оставляя без изменений все остальные метки.

Если вы хотите удалить все выполненные измерения, используйте метод clearMeasures(). Она работает так же, как и clearMarks(), но только с измерениями. Например, код:

window.performance.clearMeasures('measure_load_from_dom');

удалит измерение, которое мы сделали в примере measure() выше. Если вы хотите удалить все показатели, это работает так же, как и clearMarks() – вы просто вызываете clearMeasures() без аргументов.

Как получить данные о времени

Устанавливать метки и измерять интервалы – это хорошо, но в какой-то момент вам захочется получить данные о времени, чтобы провести анализ. Это тоже очень просто. Вам нужно лишь использовать интерфейс PerformanceTimeline.

Например, метод getEntriesByType() позволяет получить все временные метки или все измерения в виде списка, чтобы мы могли перебрать его и обработать данные. Список возвращается в хронологическом порядке, поэтому вы можете видеть метки в том порядке, в котором они были зафиксированы в вашем веб-приложении.

Код ниже:

var items = window.performance.getEntriesByType('mark');

возвращает список всех меток, которые были достигнуты в нашем веб-приложении, а код:

var items = window.performance.getEntriesByType('measure');

возвращает нам список всех измерений, которые мы сделали.

Вы также можете получить список записей, используя название, которое вы им дали. Например, код:

var items = window.performance.getEntriesByName('mark_fully_loaded');

возвращает список с одним элементом, содержащим временную метку mark_fully_loaded в свойстве startTime.

Пример измерения времени выполнения запроса XHR

Теперь, когда мы получили представление о User Timing API, мы можем использовать его для анализа времени выполнения всех запросов XMLHttpRequests в нашем веб-приложении.

Сначала мы изменим все запросы send(), чтобы они вызывали функцию, которая устанавливает метки. Одновременно мы изменим обратные вызовы, чтобы они вызывали функцию, которая устанавливает другую метку, а затем измеряет время выполнения запроса.

Обычно XMLHttpRequest выглядит примерно так:

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  do_something(e.responseText);
}
myReq.send();

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

var reqCnt = 0;

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  window.performance.mark('mark_end_xhr');
  reqCnt++;
  window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
  do_something(e.responseText);
}
window.performance.mark('mark_start_xhr');
myReq.send();

Приведенный выше код генерирует измерение с уникальным значением имени для каждого отправляемого нами XMLHttpRequest. Мы предполагаем, что запросы выполняются последовательно. Код для параллельных запросов должен быть немного сложнее, чтобы обрабатывать запросы, которые возвращаются не по порядку. Мы оставим это в качестве упражнения для читателя.

После того как веб-приложение выполнит несколько запросов, мы можем вывести их все в консоль с помощью следующего кода:

var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length; ++i) {
  var req = items[i];
  console.log('XHR ' + req.name + ' took ' + req.duration + 'ms');
}

Заключение

User Timing API предоставляет множество отличных инструментов, которые можно применять к любому аспекту веб-приложения. Чтобы выявить проблемные места в веб-приложении, можно добавить в него вызовы API и обработать полученные данные о времени выполнения. Но что, если ваш браузер не поддерживает этот API? Вы можете воспользоваться полифилом, который отлично эмулирует API и совместим с webpagetest.org. Чего же вы ждете? Попробуйте User Timing API в своих приложениях, чтобы понять, как ускорить их работу. Пользователи будут вам благодарны за улучшение интерфейса.