Это перевод статьи Alex Danilo о User Timing API, опубликованной 21 января 2014.
Высокая производительность веб-приложений является решающей для достижения хорошего user experience. В то время, как веб-приложения становятся все более сложными, понимание влияния производительности жизненно небходимо для создания конкурентноспособного user experience. За последние несколько лет в браузерах появились различные API, позволяющие анализировать производительность сети, время загрузки и т.д., но они не предоставляют необходимую информацию с достаточной гибкостью для поиска проблем, которые замедляют приложение. Использование User Timing API предоставляет механизм, позволяющий определить какая часть вашего приложения является наиболее медленной. В этой статье будет показана работа с User Timing API и примеры его использования.
Первый шаг в ускорении веб-приложения — это понимание того, где происходят потери времени. Измерение времени, затраченного на выполнение отдельных частей Javascript-кода — идеальный путь для определения горячих точек, а это — первый шаг в понимании того, как улучшить производительность. К счастью, User Timing API предоставляет способ внедрения вызовов API в различные участки кода с последующим получением детальной информации о времени выполнения.
Точность — это основа верного измерения времени. Некогда можно было довольствоваться миллисекундной точностью измерения, но разработка сайта способного отрисовыватся со скоростью 60 FPS означает, что каждый фрейм должен быть отрендерен за 16мс. То есть, миллисекундной точности недостаточно для качественного анализа. Так был введен High Resolution Time, новый тип измерения времени, встроенный в современные браузеры. High Resolution Time предоставляет timestamps в формате с плавающей точкой, что позволяет проводить измерения с точностью до микросекундного уровня — в тысячи раз лучше, чем ранее.
Чтобы получить текущее время в веб-приложении, вызовите метод 'now()', расширение интерфейса Performance:
Существует еще один интерфейс — PerformanceTiming, предоставляющий дополнительную информацию о том, как загружается приложение. Метод 'now()' возвращает время, прошедшее с момента, когда произошло событие navigationStart в PerformanceTiming.
При попытке профайлинга веб-приложений в прошлом, скорее всего пришлось бы иметь дело с чем-то вроде Date.now(), который возвращает DOMTimeStamp. DOMTimeStamp представляет собой целое число миллисекунд. Для обеспечения более высокой точности, был введен новый тип DOMHighResTimeStamp. Он является типом с плавающей запятой, также представляющим время в миллисекундах. Но, в силу своего типа, значение может представлять дробные части миллисекунд и дает возможность получить точность до одной тысячной миллисекунды.
Теперь, имея в своем распоряжении отметки High Resolution time, можно воспользоваться интерфейсом User Timing для получения информации. Интерфейс User Timing предоставляет функции, позволяющие вызывать методы в различных местах нашего приложения, оставляя хлебные крошки подобно Гензелю и Гретель, чтобы отследить, где же происходят временные затраты.
Метод 'mark()' является основным инструментом в инструментарии временного анализа. Mark() позволяет сохранять временную метку. Особенно полезным является возможность именования временных меток.
Вызов mark() в различных участках кода, позволит определить сколько времени потребовалось, чтобы достичь этой метки.
Спецификация предусматривает ряд предопределенных меток, которые могут полезны — 'mark_fully_loaded', 'mark_fully_visible', 'mark_above_the_fold'» и т.д.
Например, можно установить метку для определения момента полной загрузки приложения, используя следующий код:
Установка именованных меток в веб-приложении поможет собрать больше информации о времени выполнения для анализа существующих временных затрат.
Как только временные метки установлены, нужно измерить время, затраченное на выполнения приложения между ними. Для этого предназначен метод 'measure()'. Он также способен вычислить затраченное время между временными метками и любым известным событием в интерфейсе PerformanceTiming.
Например, можно измерить время между полной загрузкой DOM и моментом, когда ваше приложение полностью загружено:
Примечание: в этом примере передается имя события "domComplete" из интерфейса PerformanceTiming.
Вызов measure() сохраняет результат измерения доступным для дальнейшего получения. Сохраняя данные во время работы, приложение остается работоспособным и данные могут быть получены позднее, после окончания работы.
Иногда полезно иметь возможность избавиться от меток, которые уже созданы. Например, это может понадобится при проведении пакетных запусков приложения в рамках профайлинга, когданеобходимо обнулить результаты перед каждым запуском. С помощью вызова 'clearMarks()' легко избавиться от любых меток, которые были установлены.
Этот код удалит все существующие отметки:
Конечно, есть некоторые ситуации, в которых не следует удалять все временные отметки. Если необходимо избавиться от конкретных меток, следует просто передать имя отметки, которую нужно удалить. Например, приведенный ниже код, удалит установленную в первом примере отметку, оставив все остальные:
Возможно, также необходимо удалить сделанные ранее измерения, для того существует соответствующий метод 'clearMeasures()'. Принцип работы аналогичен clearMarks(), но вместо отметок удаляются измерения. Например, следующий код удалит измерения, установленные в предыдущем примере о measure():
Если необходимо удалить все измерения, то следует просто вызвать clearMeasures() без аргументов.
Возможность устанавливать отметки и измерения полезна, но в какой-то момент появится необходимость получить данные для последующего анализа. Это также легко сделать, — все, что вам нужно, это использовать интерфейс PerformanceTimeline.
Например, метод 'getEntriesByType()' позволяет получить все временные метки или змерения в виде списка, для последующего перебора и выборки информации. Список составлен в хронологическом порядке, так что метки представлены в порядке их размещения в веб-приложении.
Приведенный ниже код возвращает список всех отметок, которые существуют в приложении:
В то время как следующий код, возвращает список всех измерений:
Также можно получить список именованных отметок. Например, следующий код вернет список с одной записью, содержащей отметку 'mark_fully_loaded' в свойстве startTime.
Теперь, после краткого ознакомления с User Timing API, можно воспользоваться им для анализа длительности XMLHttpRequest в приложении.
Для начала, следует изменить все запросы send(), чтобы вызвать установку временных отметок и заменить success callbacks на вызов функции, устанавливающей другую отметку и затем генерирующую измерение продолжительности запроса.
Обычный XMLHttpRequest будет выглядеть примерно так:
Для кода же примера, следует добавить глобальный счетчик для отслеживания количества запросов, а также использовать его для хранения измерений для каждого совершенного запроса:
Этот код генерирует измерения с уникальными именами для каждого посланного XMLHttpRequest. Предполагается, что запросы выполняются последовательно — код для параллельных запросов будет немногим сложнее. Это послужит упражнением для читателя.
После того, как веб-приложение сделало все запросы, можно вывести их в консоль с помощью следующего кода:
User Timing API предоставляет много отличных инструментов применимых к любому аспекту вашего веб-приложения. Уменьшение количества «горячих точек» в приложении может быть легко достигнуто с помощью вызовов API по всему приложению и пост-обработки полученных данных для получения картины задержек. Но что, если браузер не поддерживает эту API? Это не проблема, существует хорошая эмуляция, которая способна пройти тест webpagetest.org. Так стоит ли ждать? Стоит попробовать User Timing API прямо сейчас, и появится новая возможность добиться большей производительности, а ваши пользователи скажут спасибо.
Высокая производительность веб-приложений является решающей для достижения хорошего user experience. В то время, как веб-приложения становятся все более сложными, понимание влияния производительности жизненно небходимо для создания конкурентноспособного user experience. За последние несколько лет в браузерах появились различные API, позволяющие анализировать производительность сети, время загрузки и т.д., но они не предоставляют необходимую информацию с достаточной гибкостью для поиска проблем, которые замедляют приложение. Использование User Timing API предоставляет механизм, позволяющий определить какая часть вашего приложения является наиболее медленной. В этой статье будет показана работа с User Timing API и примеры его использования.
Вы не можете оптимизировать то, что не можете измерить
Первый шаг в ускорении веб-приложения — это понимание того, где происходят потери времени. Измерение времени, затраченного на выполнение отдельных частей Javascript-кода — идеальный путь для определения горячих точек, а это — первый шаг в понимании того, как улучшить производительность. К счастью, User Timing API предоставляет способ внедрения вызовов API в различные участки кода с последующим получением детальной информации о времени выполнения.
High Resolution time и 'now()'
Точность — это основа верного измерения времени. Некогда можно было довольствоваться миллисекундной точностью измерения, но разработка сайта способного отрисовыватся со скоростью 60 FPS означает, что каждый фрейм должен быть отрендерен за 16мс. То есть, миллисекундной точности недостаточно для качественного анализа. Так был введен High Resolution Time, новый тип измерения времени, встроенный в современные браузеры. High Resolution Time предоставляет timestamps в формате с плавающей точкой, что позволяет проводить измерения с точностью до микросекундного уровня — в тысячи раз лучше, чем ранее.
Чтобы получить текущее время в веб-приложении, вызовите метод 'now()', расширение интерфейса Performance:
var myTime = window.performance.now();
Существует еще один интерфейс — PerformanceTiming, предоставляющий дополнительную информацию о том, как загружается приложение. Метод 'now()' возвращает время, прошедшее с момента, когда произошло событие navigationStart в PerformanceTiming.
Тип DOMHighResTimeStamp
При попытке профайлинга веб-приложений в прошлом, скорее всего пришлось бы иметь дело с чем-то вроде Date.now(), который возвращает DOMTimeStamp. DOMTimeStamp представляет собой целое число миллисекунд. Для обеспечения более высокой точности, был введен новый тип DOMHighResTimeStamp. Он является типом с плавающей запятой, также представляющим время в миллисекундах. Но, в силу своего типа, значение может представлять дробные части миллисекунд и дает возможность получить точность до одной тысячной миллисекунды.
Интерфейс User Timing
Теперь, имея в своем распоряжении отметки High Resolution time, можно воспользоваться интерфейсом User Timing для получения информации. Интерфейс User Timing предоставляет функции, позволяющие вызывать методы в различных местах нашего приложения, оставляя хлебные крошки подобно Гензелю и Гретель, чтобы отследить, где же происходят временные затраты.
Использование 'mark()'
Метод 'mark()' является основным инструментом в инструментарии временного анализа. Mark() позволяет сохранять временную метку. Особенно полезным является возможность именования временных меток.
Вызов mark() в различных участках кода, позволит определить сколько времени потребовалось, чтобы достичь этой метки.
Спецификация предусматривает ряд предопределенных меток, которые могут полезны — 'mark_fully_loaded', 'mark_fully_visible', 'mark_above_the_fold'» и т.д.
Например, можно установить метку для определения момента полной загрузки приложения, используя следующий код:
window.performance.mark('mark_fully_loaded');
Установка именованных меток в веб-приложении поможет собрать больше информации о времени выполнения для анализа существующих временных затрат.
Расчет измерений с 'measure() '
Как только временные метки установлены, нужно измерить время, затраченное на выполнения приложения между ними. Для этого предназначен метод 'measure()'. Он также способен вычислить затраченное время между временными метками и любым известным событием в интерфейсе PerformanceTiming.
Например, можно измерить время между полной загрузкой DOM и моментом, когда ваше приложение полностью загружено:
window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');
Примечание: в этом примере передается имя события "domComplete" из интерфейса PerformanceTiming.
Вызов measure() сохраняет результат измерения доступным для дальнейшего получения. Сохраняя данные во время работы, приложение остается работоспособным и данные могут быть получены позднее, после окончания работы.
Удаление меток с помощью ‘clearMarks()’
Иногда полезно иметь возможность избавиться от меток, которые уже созданы. Например, это может понадобится при проведении пакетных запусков приложения в рамках профайлинга, когданеобходимо обнулить результаты перед каждым запуском. С помощью вызова 'clearMarks()' легко избавиться от любых меток, которые были установлены.
Этот код удалит все существующие отметки:
window.performance.clearMarks();
Конечно, есть некоторые ситуации, в которых не следует удалять все временные отметки. Если необходимо избавиться от конкретных меток, следует просто передать имя отметки, которую нужно удалить. Например, приведенный ниже код, удалит установленную в первом примере отметку, оставив все остальные:
window.peformance.clearMarks('mark_fully_loaded');
Возможно, также необходимо удалить сделанные ранее измерения, для того существует соответствующий метод 'clearMeasures()'. Принцип работы аналогичен clearMarks(), но вместо отметок удаляются измерения. Например, следующий код удалит измерения, установленные в предыдущем примере о measure():
window.performance.clearMeasures('measure_load_from_dom');
Если необходимо удалить все измерения, то следует просто вызвать clearMeasures() без аргументов.
Получение данных
Возможность устанавливать отметки и измерения полезна, но в какой-то момент появится необходимость получить данные для последующего анализа. Это также легко сделать, — все, что вам нужно, это использовать интерфейс PerformanceTimeline.
Например, метод 'getEntriesByType()' позволяет получить все временные метки или змерения в виде списка, для последующего перебора и выборки информации. Список составлен в хронологическом порядке, так что метки представлены в порядке их размещения в веб-приложении.
Приведенный ниже код возвращает список всех отметок, которые существуют в приложении:
var items = window.performance.getEntriesByType('mark');
В то время как следующий код, возвращает список всех измерений:
var items = window.performance.getEntriesByType('measure');
Также можно получить список именованных отметок. Например, следующий код вернет список с одной записью, содержащей отметку 'mark_fully_loaded' в свойстве startTime.
var items = window.performance.getEntriesByName('mark_fully_loaded');
Тестирование XHR-запроса (пример)
Теперь, после краткого ознакомления с User Timing API, можно воспользоваться им для анализа длительности XMLHttpRequest в приложении.
Для начала, следует изменить все запросы send(), чтобы вызвать установку временных отметок и заменить success callbacks на вызов функции, устанавливающей другую отметку и затем генерирующую измерение продолжительности запроса.
Обычный XMLHttpRequest будет выглядеть примерно так:
var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
do_something(e.responseText);
}
myReq.send();
Для кода же примера, следует добавить глобальный счетчик для отслеживания количества запросов, а также использовать его для хранения измерений для каждого совершенного запроса:
var reqCount = 0;
var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
window.performance.mark('mark_end_xhr');
reqCount++;
window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
do_something(e.responseText);
}
Этот код генерирует измерения с уникальными именами для каждого посланного 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? Это не проблема, существует хорошая эмуляция, которая способна пройти тест webpagetest.org. Так стоит ли ждать? Стоит попробовать User Timing API прямо сейчас, и появится новая возможность добиться большей производительности, а ваши пользователи скажут спасибо.