Как мы показываем веб-производительность в iPlayer
Понимание того, сколько времени требуется для загрузки веб-сайта BBC iPlayer, всегда было сложной задачей, которая оказывает реальное влияние на аудиторию; К счастью, браузеры теперь имеют огромное количество функций, которые помогают нам понять, что происходит. Инструменты разработчика особенно эффективны при профилировании и мониторинге производительности браузера, но у них нет средств, чтобы адекватно показать, на что тратится время, прежде чем будет получен ответ от сервера; Здесь вам поможет Server Timing, чтобы спасти положение.
Для тех, кто не знаком с Server Timing, он некоторое время находится в разработке, но недавно появилась официальная версия W3C в Chrome 65, который был выпущен на прошлой неделе, 6 марта. Серверное время - это набор заголовков, которые вы можете добавить в ответы вашего приложения для получения данных о времени в инструментах разработчика браузера, чтобы помочь демистифицировать время до первого байта (TTFB), например, в следующем примере с домашней страницы iPlayer:
Приведенный выше снимок экрана взят с веб-сайта iPlayer в Chrome Dev Tools при проверке запроса / iplayer на вкладке «Сеть» и на вкладке «Время» запроса. Он показывает, что какая бы обработка запроса ни выполнялась в нашем приложении, на него приходилось 68,53 мс из общего числа 101,52 мс до того, как появился первый байт. Это означает, что пропало всего 32,99 мс, и эту сумму можно легко отнести к магии Интернета.
Если вы посмотрите на спецификацию, есть много вариантов того, как вы это реализуете, вместо того, чтобы беспокоиться обо всех вариантах, мы просто выбрали самый простой вариант для начала, чтобы начать всплытие данных. Для этого вам просто нужен заголовок Server-Timing для каждого времени, как показано на веб-сайте iPlayer:
Как показано, каждый заголовок Server-Timing содержит две части информации: имя перед ;
и атрибут dur
, который является коротким по продолжительности. Старайтесь не раскрывать слишком много информации о том, что на самом деле означают эти значения, и будьте осторожны с установкой описания с использованием атрибута desc
для любых значений, поскольку все это отображается в ответе. Для iPlayer это означает, что мы используем только метку, которая является первой частью заголовка, и атрибут продолжительности, который форматируется в миллисекундах.
Другая интересная вещь заключается в том, что поскольку Server-Timing является поддерживаемой спецификацией W3C, браузеры начнут поддерживать ее в рамках API синхронизации производительности, чтобы она была доступна для мониторинга приложений на стороне клиента. В приведенных выше выходных данных вы можете видеть, что три значения, которые мы обсуждали ранее, представлены в записях navigation
, но также можно добавить Server Timing к записям resource
, что означает, что вы можете получить больше информации о вещах, поступающих после начальной загрузки.
Как мы это сделали
В iPlayer мы используем Express и Node.js (для получения дополнительной информации о нашей технологии вы можете прочитать отличный пост Энди Смита), поэтому мы реализовали это как промежуточное ПО. Для начала мы использовали таймер высокого разрешения, доступный в Node.js:
const startTime = process.hrtime(); const timeDifference = process.hrtime(startTime);
Это позволяет нам определить разницу во времени между двумя точками нашего кода, это также полезно для создания тестов и других инструментов измерения времени. Функция process.hrtime
принимает переданный startTime
и создает разницу между этой меткой времени и текущей меткой времени, которая сохраняется в timeDifference
в формате [seconds, nanoseconds]
. Чтобы превратить это в миллисекунды, мы немного вычислим:
function convertToMs(hrtime) { const ms = hrtime[0] * 1e3 + hrtime[1] * 1e-6; return ms.toFixed(3); }
Взяв секунды и умножив их на 1000, мы получим значение миллисекунды и эквивалент, чтобы преобразовать наносекунды в миллисекунды, а затем ограничить его до 3 знаков после запятой. Все, что оставалось, - это подключить его полностью:
const onHeaders = require('on-headers'); function convertToMs(hrtime) { const ms = hrtime[0] * 1e3 + hrtime[1] * 1e-6; return ms.toFixed(3); } module.exports = function serverTiming(req, res, next) { const startTime = process.hrtime(); onHeaders(res, () => { const timeDifference = process.hrtime(startTime); const timeDifferenceMs = convertToMs(timeDifference); res.append('server-timing', `total;dur=${timeDifferenceMs}`); }); next(); };
Чтобы измерить любой тип маршрута в Express, который вы хотите измерить, от момента первого вызова маршрута до момента, когда вы наконец ответите на запрос. Для этого мы не смогли найти какой-либо стандартный перехватчик Express, поэтому вместо этого использовали модуль npm on-headers, который позволяет вам перехватывать ответ непосредственно перед его отправкой. Важно отметить, что мы использовали res.append
, а не res.set
, который добавляется к существующим значениям, поэтому, если вы установите заголовки Server-Timing в другом месте, они накапливаются.
Немного дальше
Мы немного расширили эту идею, добавив две карты, содержащие наши runningTimers
и finishedTimers
, чтобы при необходимости можно было легко добавить больше таймингов. Они были доступны с помощью двух методов, добавленных к объекту res
как serverTimingStart
и serverTimingEnd
, что означало, что мы могли измерить любой фрагмент кода, который нам нужен, например:
res.serverTimingStart('data'); const data = await dataProcessing(); res.serverTimingEnd('data');
Затем это добавляется к ответу как дополнительная часть данных Server-Timing. И пока мы были там, мы хотели добавить эту статистику в наши существующие системы мониторинга, для этого мы немного усложнили приведенный выше код, добавив функцию hook
, которую вы можете при желании передать промежуточному программному обеспечению и которая вызывается вместе с res.append
и передается req
, key
и value
, чтобы он мог отправлять статистику, связанную с этим запросом:
express.use(timing((req, key, value) => { sendMyStats(req.url, key, value); }));
Если вы хотите быстро добавить серверную синхронизацию в свое экспресс-приложение, попробуйте модуль экспресс-простой синхронизации!
Но есть так много других забавных применений
Если у вас есть кэшированный ответ, все равно полезно посмотреть, сколько времени потребовалось вашему серверу, чтобы подготовить его, чтобы вы знали, сколько времени этому бедному человеку без кешированного ответа пришлось ждать.
Добавляя информацию кеша к своим ответам, вы можете помочь разработчикам сообщить, нужно ли им обращать внимание на определенные значения. Вы также можете отфильтровать значения, если не хотите, чтобы они были доступны. Ниже приведен пример для использования в сервере кеширования Varnish с использованием заголовка vmod:
vcl 4.0; import header; sub vcl_deliver { if (obj.hits > 0) { header.append(resp.http.server-timing, "hit"); } else { header.append(resp.http.server-timing, "miss"); } }
Или, что еще проще, использовать некоторые из переменных кеша по умолчанию nginx server:
add_header server-timing $upstream_cache_status;
Еще один пример из спецификации W3C - добавить информацию о том, через какой центр обработки данных был направлен ответ. На самом деле это не так полезно, как вы могли подумать, поскольку вместо этого в Dev Tools это выглядит так:
Вышеупомянутое было создано с заголовком Server Timing dc;desc=gb
, который представлен просто строкой gb
. Но вы все равно можете получить доступ к информации в полном объеме с помощью API производительности.
Последние мысли
Потратив некоторое время на добавление этих заголовков в ваше приложение и на уровни кеширования, разработчики могут легко понять, почему ответы сервера замедляются. Дополнительные заголовки могут быть добавлены в нескольких точках цепочки, чтобы дополнительно информировать о том, куда направляются ваши запросы и что занимает это жизненно важное время до первого байта.
Большое спасибо Гарри Робертсу, который упомянул об отсутствии информации о времени сервера и его использовании в дикой природе во время нашего семинара с ним!
Присоединяйтесь к нам
Если вам интересно и взволновано то, о чем мы здесь говорили, то, возможно, вас заинтересует наше присоединение к команде iPlayer. Мы всегда ждем, чтобы к нам присоединились целеустремленные инженеры.