Если вы хотите улучшить производительность, для начала вам надо ее измерить. Но что конкретно надо мерять?

При измерении производительности самой популярной метрикой является время загрузки страницы (т.е. события window.onload или document complete). Время загрузки страницы было отличным индикатором при оценке пользовательского опыта во времена Web 1.0, когда страницы были проще, а каждое действие пользователя вело к загрузке новой страницы. В эпоху Web 2.0 и одностраничных приложений, время загрузки страницы уже не коррелирует с впечатлениями пользователя от скорости страницы. Отличная иллюстрация этого — сравнение Gmail и Amazon.

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

Всякая метрика производительности, измеряющая все содержимое одинаково, не является хорошей метрикой.

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

Спецификация User Timing

Спецификация W3C User Timing это API для для создания кастомных метрик производительности веб-приложений. Для этого есть две основных функции: performance.mark и performance.measure:

  • performance.mark записывает время (в миллисекундах) с момента navigationStart
  • performance.measure это разница между двумя метриками

Существуют и другие функции, но эти две являются главными. После того, как вы заполнили свою страницу пометками и метриками, встает вопрос — как собирать эти метрики, чтобы их можно было отслеживать. Благодаря тому, что User Timing это официальная спецификация W3C, многие сервисы по измерению производительности автоматически извлекают данные этих метрик и используют в своих отчетах. Это относится к SOASTA’s mPulse, WebPageTest и SpeedCurve.

Образцы пользовательских метрик

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

Небольшое замечание насчет поддержки в браузерах: User Timing работает во всех современных десктопных браузерах, кроме Safari. В примерах мы используем нативный API, для совместимости используйте полифилл от Пэта Менэна или от Ника Джэнсмы.

Пример 1: блокирующие таблицы стилей

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

<link rel="stylesheet" href="/sheet1.css">
<link rel="stylesheet" href="/sheet4.css">
<script>
performance.mark("stylesheets done blocking");
</script>

Главное, чтобы метрика располагалась после подключения всех тегов link с подключением стилей. Это логичное требование, так как большинство таблиц стилей подключается в верхней части страницы. Несмотря на внешнюю простоту здесь есть некоторые сложности, о которых надо знать:

  • Строчные скрипты не выполняются, пока не загрузятся и не применятся все таблицы стилей, расположенные до них. Поэтому размещение скрипта с метрикой после них гарантирует, что метрика активирована после обработки всех блокирующих CSS.
  • В таблицах стилей могут быть директивы @import, подключающие другие таблицы стилей, например sheet1.css может вызвать загрузку и обработку sheet2.css и sheet3.css. Значит, простого отслеживания времени загрузки таблицы стилей в таком случае будет недостаточно, полученная метрика будет слишком короткой и не даст полной оценки влияния стилей на блокировку рендеринга.

Чтобы добиться удобства для пользователей, важно понимать, что может блокировать рендеринг страницы. Основными виновниками являются таблицы стилей и синхронные скрипты, но часто бывает сложно однозначно установить, что является причиной задержки рендеринга. Пользовательская метрика “stylesheets done blocking” удобна тем, что учитывает рендеринг после обработки стилей, то есть позволяет сфокусировать поиск на синхронных скриптах. К ним мы и перейдем в следующем примере.

Пример 2: блокирующие скрипты

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

<script src="a.js"></script>
<script src="b.js" async></script>
<script>
performance.mark("scripts done blocking");
</script>

Строчный скрипт ( performance.mark) гарантированно исполняется только после загрузки, парсинга и выполнения скрипта a.js. С другой стороны, пометка сработает до полной загрузки скрипта b.js, так как у него задана асинхронная загрузка. Это поведение подходит для нашей метрики, так как скрипт a.js блокирует рендеринг, а скрипт b.js — нет.

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

Следовательно, этот пример работает только со страницами, на которых все синхронные скрипты подключаются в head. Даже с этим ограничением, пользовательская метрика может быть полезной для страниц, подключающих большие скрипты, которые блокируют загрузку дольше, чем загружаются, так как требуют время на парсинг и выполнение. Если смотреть на них в инспекторе страницы в режиме “водопада”, между их закачкой и последующим рендерингом есть промежуток. С метрикой scripts done blocking можно выявлять скрипты с длинным временем парсинга и выполнения, как виновников блокировки рендеринга.

Пример 3: загрузка шрифтов

Если на вашем сайте используются кастомные шрифты, важно помнить, что некоторые браузеры не выводят текст до загрузки файла со шрифтом. В других браузерах текст сначала выводится системным шрифтом, а затем заменяется (см. FOUT) кастомным. Следовательно, метрика после загрузки шрифтов будет полезной, когда текстовые элементы уже выведены.

В данный момент нет события onload для шрифтов. Тем не менее, существуют техники, отслеживающие загрузку шрифтов, например, Font Loading Revisited with Font Events от Скотта Джеля из Filament Group или Web Font Loader, используемый Typekit и Google Fonts. Вы можете использовать их для отслеживания окончания загрузки шрифтов и устанавливать performance.mark для пользовательской метрики.

Пример 4: основные изображения

Основное изображение (Hero image) часто оказывает решающее влияние на впечатления пользователей. В статье пользовательская метрика основного изображения подробно описана технология замера. Сниппет выглядит так:

<img src="hero.jpg" onload="performance.clearMarks('img displayed'); performance.mark('img displayed');">
<script>
performance.clearMarks("img displayed");
performance.mark("img displayed");
</script>

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

Пример 5: параграф текста

Неожиданно одной из самых сложных задач стало создание метрики, определяющей время, когда выведен текст. В дополнение к блокирующим таблицам стилей и синхронным скриптам, текстовые элементы (P, SPAN, LI, DIV, и т.д.) также могут блокироваться, если в них используется кастомный шрифт, требующий долгой загрузки.

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

<p>This is the call to action text element.</p>
<script>
performance.mark("text displayed");
</script>

Если текстовый элемент использует кастомный шрифт, то время рендеринга будет максимальным в следующей за ним пометке и в пометке кастомного шрифта. Ключевым здесь является использование паттерна, сходного с паттерном для основного изображения, когда мы записываем пометку “text displayed” дважды — первый раз при подключении шрифта и затем в скрипте после текстового элемента. Дополнительно, мы очищаем все предыдущие пометки с тем же именем, перед установкой новой — это гарантирует нам получение максимального значения, которое отражает реальное время вывода текста.

Пример 6: одностраничные приложения

Одним из недостатков метрики загрузки времени страницы является то, что она не измеряет действия внутри одностраничного приложения (SPA), ведь window.onload срабатывает раз, а пользователь совершает множество действий, каждое из которых можно измерить. Пользовательские метрики решают эту проблему, но для этого надо создавать пометку начала действия.

Для разбора этого на примере кода, предположим, что у нас есть SPA с кнопкой ‘Update’. При нажатии на нее запускается запрос XHR для получения новых данных. Возвращенные данные проходят через функцию updateData(), изменяющую DOM. Вот код, измеряющий это пользовательское действие в SPA.

<input type=button value="Update" onclick="fetchData()">
 
<script>
function fetchData() {
    performance.clearMarks("start update");
    performance.mark("start update");
 
    // Do an XHR or JSON request that calls updateData() with the new data.
}
 
function updateData(data) {
    // Update the DOM with the new data.
 
    performance.clearMarks("finish update");
    performance.mark("finish update");
    perforance.measure("update data", "start update", "finish update");
}
</script>

Отметьте, что пометка “start update” это первый код в действии, а “finish update” — последний. Это обеспечивает, что полное SPA приложение будет измерено, включая загрузку XHR или JSON и обновление DOM. Также обратите внимание, что для этого используется performance.measure. Это связано с тем, что мы хотим получить разницу во времени относительную от начала действия, а не от navigationStart.

Пользовательские метрики в SpeedCurve

Основной плюсом создания пользовательских метрик с User Timing является то, что большинство сервисов измерения производительности автоматически извлекают пользовательские пометки и метрики для использования в своих замерах. Это делает WebPageTest и построенный на его основе SpeedCurve.

Вы можете видеть пользовательские метрики в SpeedCurve, задав им названия в настройках. На speedcurve.com мы используем собственную метрику “heromark”, чтобы проводить замеры основного изображения (обычно это диаграмма вверху страницы). Мы можем дать ей более красивое название “Hero Mark”, используя форму в настройках.

добавление пользовательской метрики в настройках SpeedCurve

Вы можете добавить много пользовательских метрик в настройках SpeedCurve. Так как отдельные страницы содержат в себе десятки метрик, включая метрики от третьих сторон, подключение метрик в настройках гарантирует показ только значимых пользовательских метрик.

Пользовательские метрики видны на нескольких табло SpeedCurve. Например, табло сайта показывает изменения метрики “Hero Mark” в течение времени.

изменения метрики "Hero Mark" в течение времени

В диаграмме “водопад” пользовательские метрики чрезвычайно полезны. Это позволяет вам отлаживать их. Например, на диаграмме ниже, мы видим, что пользовательская метрика “Hero Mark” блокируется до загрузки таблиц стилей и скриптов. Миниатюра внизу экрана подтверждает реальное время загрузки изображения.

пользовательские метрики в режиме "водопад"

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