Разделы портала

Онлайн-тренинги

.
Измеряем изменения в скорости загрузки сайта в различных сетевых (и не только) условиях. Теперь удобнее
16.09.2020 00:00

Автор: Рубан Виталий (drag13)

Блог автора: https://drag13.io/

Всем, привет. Хочу поделиться своим проектом, который я делал в последние несколько месяцев. Это open-source инструмент командной строки, предназначенный для удобного сбора метрик производительности веб-сайта в различных сетевых (и не только) условиях.

Уже реализована эмуляция slow3g, fast3g, и 4g сетей, тестирование с браузерным кешированием или без, эмуляция замедления процессора. Собираются события первой и наибольшей отрисовки, время потраченное на построение макета и пересчет стилей, размер ресурсов загруженных до FCP и другие полезные метрики.

Кому интересны подробности, немного кода и чуть-чуть про новое CSS правило которое появится в Chrome 85, прошу за мной!

Зачем?

Когда появляется какой-то новый инструмент, вопрос номер один это — "Зачем?". Какую проблему ты пытаешься решить (кроме "потому что могу")?

Поэтому давайте начнем с проблемы. Был май, я пытался оптимизировать загрузку одного приложения на React.JS и, если честно, немного устал. Почему устал? Потому что на каждый чих мне надо было:

  • Выбрать параметры сети (например fast-3g)
  • Запустить профилирование
  • Записать результаты
  • Повторить все вышеперечисленное еще два раза, что бы вывести среднюю величину
  • Поменять параметры сети на новые
  • Повторить все вышеперечисленное еще раз
  • Отключить кэш
  • Повторить все вышеперечисленное еще раз

И так на каждую гипотезу. Понимаете, да? Одно изменение и минимум 12 запусков плюс подсчеты. Тяжело… Поэтому, пока я этим занимался, в голове крутилась мысль что было бы неплохо это как-то автоматизировать, но было не понятно как, да и времени было не много, нужно было катить:


perfrunner как все начиналось


И тут, мой коллега подбросил мне один очень любопытный репозиторий, где решалась схожая проблема, но для автотестов. Я посмотрел код, и, внезапно, все оказалось не очень сложно. Так и появился Perfrunner инструмент, который упрощает тестирование гипотез по улучшению (или как повезет) производительности для веб сайтов и веб приложений.

А с тебя какая польза?

Хотя разработка еще не закончена (есть как минимум одна фича которой мне не хватает и один "может-быть-баг"), но вот что уже умеет Perfrunner

  • Эмулирование разных сетевых условий. Сейчас поддерживаются пять вариантов: online/regular4g/fast3g/hspa/slow3g. Параметры для них я честно нагуглил, так что я не уверен, что они 100% корректны. Если найдется кто-то более сведущий и поправит я буду очень благодарен.
  • Работа с кэшем или без. Т. е. можно эмулировать и первый заход пользователя и повторный.
  • Эмуляция замедления процессора для тестирования на более слабых устройствах. Кстати, чудесная штука. Если никогда не пробовали очень рекомендую поставить где-то на десятикратное замедление и понаблюдать как умирает React. Впрочем, Angular умирает точно так же.
  • Многократное тестирование. По умолчанию, (и в память о моих мучениях), Perfrunner запускает все тесты по 3 раза и агрегирует результат. Если нравится другое число, значение можно поменять с помощью флага --runs. Валидация выглядит как requiredPositiveInteger, так что, теоретически, можно выставить тысяч пять запусков и уйти пить кофе на целый день.

Что действительно полезно, это то, что Perfrunner позволяет просто перечислить набор параметров (варианты сети, кэш) и через несколько минут получить результат. Выглядит это вот так:

npx drag13.io --network slow-3g fast-3g hspa regular-4g online --cache true false

С такими параметрами Perfrunner самостоятельно запустит сайт 24 раза, соберет результаты, агрегирует их и выведет в виде HTML отчета. Согласитесь, намного проще чем делать все вручную.

Теперь об отчетах. Вот что входит в текущую версию отчета:

  • first-contentful-paint, largest-contentful-pain, dom-interactive и еще чуть-чуть. Это нужно что бы понимать как быстро пользователь увидит что-то полезное или сможет взаимодействовать c сайтом.
  • layout duration, script duration, recalculate-style-duration. Это нужно что бы смотреть за счет чего у нас улучшаются (или не улучшаются) метрики из первого пункта.
  • Размер ресурсов, загруженных до FCP. Это нужно для понимания и контроля сколько ресурсов грузится во время критической секции загрузки.
  • Размер всех ресурсов, которые грузит сайт.
  • И метки производительности, если они есть.

Все это выводится в виде графиков (кликабельно):

perfrunner reporter for the fast-3g

Здесь показано как изменяются метрики после добавления jQuery в шапку страницы. Точно так же можно тестировать любые другие гипотезы, например влияние внедрения критического CSS в index.html для SPA приложений, использование директив preload и prefetch, lazy-loading и все остальное. Причем посмотреть можно не только как изменились метрики на вашем любимом 100мбит канале, но и, например, для slow-3g. Правда есть один нюанс, — для более-менее честной картины, ресурс желательно хостить удаленно, а не на localhost.

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

Как это все устроено?

На самом деле все довольно просто. Весь проект написан на TypeScript, код лежит в монорепозитории под управлением Lerna и разбит на 3 отдельных пакета – CLI, Reporters и Core

CLI обслуживает ввод-вывод и основан на command-line-args. Из интересного, именно здесь зашиты параметры сетевых условий, например вот так выглядят параметры для slow3g:

export const Slow3g: NetworkSetup = {
  downloadThroughput: (0.4 * 1024 * 1024) / 8,
  uploadThroughput: (0.4 * 1024 * 1024) / 8,
  latency: 2000,
  name: "slow3g",
};

Reporters содержит логику по отображению данных. Здесь лежит код для генерации HTML, JSON и CSV отчетов. По умолчанию используется HTML отчет, но с помощью флага --reporter можно переключиться на JSON, CSV или даже подключить свой собственный, например вот так:

//reporer.js
module.exports = (outputFolder, data, args) =>
  console.log(outputFolder, JSON.stringify(data), args);


npx perfrunner drag13@io --reporter "./reporter.js"

Для генерации HTML отчета я использовал Parcel и Mustache. Кстати, c Parcel я столкнулся впервые, оказалось очень удобно. TypeScript, бандлинг и минификация поддерживается из коробки. Для инлайнинга (что бы отчет можно было отправить как самостоятельный файл) нашелся плагин parcel-plugin-inline-source. Была и одна неприятная проблема с рендером обратных кавычек (во имя более широкой поддержки среди браузеров, Parcel рендерит ` в виде "), но с помощью костыля это худо-бедно победилось. Для вывода графиков я взял Chart.JS, который пытался стилизовать но, безуспешно, дизайнер во мне явно умер.

Ну и теперь про Core. Он основан на Puppeter и отвечает за запуск браузера, сбор метрик и их хранение. Причем, что любопытно, все строится примерно на следующем коде (если упростить):

import puppeteer, { Browser, Page } from "puppeteer";

const browser = await puppeteer.launch({ headless: true, timeout: 60000 });
const page = await browser.newPage();

await page.setCacheEnabled(false);
await pageSession.send("Network.setCacheDisabled", { cacheDisabled: true });
await pageSession.send("Network.enable");
await pageSession.send("Network.emulateNetworkConditions", {
  latency: 20,
  downloadThroughput: 500000,
  uploadThroughput: 50000,
  offline: false,
});
await pageSession.send("Emulation.setCPUThrottlingRate", { rate: 4 });
await pageSession.send("Performance.enable");

await page.goto(url.href, { waitUntil: "networkidle0" });
const metrics = await page.metrics();
const entries = await page.evaluate(() =>
  JSON.stringify(performance.getEntries())
);
return { metrics, entries };

Как видите, идея довольно проста, но вот что бы довести все до ума, пришлось потрудиться.

Так, например, largest-contentfull-paint нельзя просто взять и вытянуть из performance.getEntries(), его там попросту нет. Вместо этого мы должны подписаться на это событие и ждать пока оно прилетит. Что, для моих целей довольно плохо, потому что если на сайте нет JavaScript-а (как, например на моем блоге, на котором я тестировал), то, внезапно, он в метриках все равно появится. Но, увы, тут или метрику выбрасывать или оверхед терпеть, другого решения я не нашел. Так же пришлось добавить обработку трейсов браузера. Это понадобилось что-бы достать оттуда типы ресурсов (mimetype) и размер переданных по сети файлов (и вообще там много интересного, очень рекомендую покурить трейсы на досуге).

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

Еще был «веселый» случай, который почти свел меня с ума часа на три. В случайном порядке, один из запусков, иногда, выдавал цифры в два раза хуже, чем остальные тесты (причем уже после прогрева). Трейсы показывали аномально высокие значение TTFB, а именно Stalled часть, которая могла длиться 1200-1500ms. Проблема оказалось в использовании Proxy, которая почему-то включилась на Windows машине. Поседеть я не поседел, но wtf/sec зашкаливал.

Для тестирования я взял стандартную связку chai + mocha которые повешены на preversion и prepublish хук с помощью husky. Кроме этого, с помощью того же hasky и lint-staged, на prepush повешен prettier. Для CI/CD — традиционно взят Travis CI.

Content-Visiblity и как он влияет на сайт

А теперь давайте попробуем потестировать что-то действительно интересное. Наверное, вы уже в курсе, что в Chrome версии 85 появится новое, довольно любопытное, css правило — content-visibility. Если нет, то, упрощенно, оно позволяет отложить рендер той части сайта, которую пользователь на данный момент не видит. По идее это должно ускорить момент первой значимой отрисовки, но вот на сколько именно — это вопрос интересный. Попробуем замерить, сколько оно может сэкономить.

Для этого нам нужно запустить Canary версию Chrome вместо Puppeteer, и, на всякий случай, выключить headless режим. Perfrunner такие трюки позволяет.

npx perfrunner "https://drag13.io" --network slow-3g fast-3g regular-4g --cache true false --executable-path" "C:\Users\ACCOUNT_NAME\AppData\Local\Google\Chrome SxS\Application\chrome.exe" --no-headless

И вот результат:

Network Cache FMP before (ms) FMP after (ms) Diff (ms)
slow-3g false 4358 4267 91
slow-3g true 2953 2857 96
fast-3g false 421 329 92
fast-3g true 221 122 99
regular-4g false 316 223 93
regular-4g true 221 123 98

Итого, от 90ms до 100ms экономии на моих несчастных 700 нодах (что не плохо) и CoreI7 процессоре. Для бюджетных смартфонов все должно быть еще лучше.

Если не работает

Если у вас не работает, ничего страшного. У меня тоже не работает не работало. Под капотом у Perfrunner-а стоит Puppeter у которого свои ограничения. Поэтому если возникли проблемы — вам сюда. В свою очередь, Perfrunner поддерживает --chrome-args, --ignore-default-args и, на худой конец, --executable-path флаги.

Итоги подведем (С).

Получился простой инструмент для проверки различных гипотез по улучшению производительности. Теперь не надо гадать сколько стоит убрать jQuery или добавить внедрить critical CSS в приложение. Добавили, запустили, и через минуты три ответ готов.

На этом, собственно, все. Дополнительные настройки можно посмотреть в readme. Фидбек или багу оставить тут. Из следующих планов — поддержка perfrunner.config с кастомными настройками и списком страниц для запуска, рефакторинг и, наверное, commitizen.

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

P.S. Cпасибо veri-ivanova за КДПВ и raharrison за работающий пример.
P.P.S. Если нужна английская версия статьи (она немного другая), ее можно найти тут

Обсудить в форуме