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

  • Почему эта страница так долго загружается?
  • Как я могу оптимизировать свой JavaScript, чтобы он работал быстрее?
  • Если я внесу некоторые изменения в этот код, это сделает это приложение медленнее?

Я работал над тем, чтобы упростить ответы на такие вопросы для Gaia, слоя пользовательского интерфейса для Firefox OS, полностью веб-ориентированной операционной системы для мобильных устройств. Написание производительных веб-страниц для настольных компьютеров имеет свои особенности, а написание нативных приложений с использованием веб-технологий делает задачу еще более сложной. Я хочу рассказать о проблемах, с которыми я столкнулся, чтобы сделать производительность более простой темой для решения в Firefox OS, а также задокументировать свои решения и выявить дыры в веб-API, которые необходимо заполнить.

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

Исправление жизненного цикла приложений

Распространенный вопрос, который мне задают относительно приложений Firefox OS:

Сколько времени потребовалось для загрузки приложения?

Сложный вопрос, поскольку мы не можем быть уверены, что говорим на одном языке. Основываясь на UX и собственных исследованиях в Mozilla, я попытался принять это определение для определения времени, которое требуется приложению для загрузки:

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

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

Итак, загрузка окна, верно?

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

Для Firefox OS я помог разработать ряд стандартных моментов, которые имеют отношение почти к каждому приложению, подразумевая его жизненный цикл загрузки (также задокументировано на MDN):

навигация загружена

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

интерактивная навигация

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

визуально загружается

Приложение обозначает, что оно визуально загружено, например. содержимое «выше сгиба» существует в DOM и помечено как готовое к отображению, опять же не отображать: нет или другие функции скрытия.

Интерактивный контент

Приложение указывает, что оно связало события с минимальным набором функций, чтобы пользователь мог взаимодействовать с содержимым «вверху страницы», доступным при визуальной загрузке.

полностью загружен

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

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

Обозначая моменты

Имея четко определенный жизненный цикл запуска приложения, мы можем обозначить эти моменты с помощью User Timing API, доступного в ОС Firefox, начиная с версии 2.2:

window.performance.mark( string markName )

В частности, во время запуска:

performance.mark('navigationLoaded'); performance.mark('navigationInteractive'); performance.mark('visuallyLoaded'); performance.mark('contentInteractive'); performance.mark('fullyLoaded');

Вы даже можете использовать метод Measure() для создания измерения между другой меткой или даже двумя другими метками:

// Denote point of interaction performance.mark('tapOnButton'); loadUI();
// Capture time from now (sectionLoaded) to tapOnButton performance.measure('sectionLoaded', 'tapOnButton');

И получить эти показатели производительности довольно просто с помощью getEntries, getEntriesByName или getEntriesByType, которые извлекают коллекцию записей. Однако цель этой статьи не в том, чтобы охватить использование User Timing, поэтому я пойду дальше.

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

В то время как настольные сайты могут легко получить момент, когда был инициирован запрос, сделать это в ОС Firefox не так просто. Чтобы запустить приложение, пользователь обычно нажимает значок на главном экране. Главный экран находится в процессе, отдельном от запускаемого приложения, и мы не можем обмениваться оценками производительности между ними.

Решение проблем с Раптором

Без API-интерфейсов или механизмов взаимодействия, присутствующих в платформе, чтобы преодолеть эту и другие трудности, мы создаем инструменты, которые помогут нам. Так появился Инструмент тестирования производительности Raptor. С его помощью мы можем собирать метрики из приложений Gaia и отвечать на вопросы о производительности, которые у нас есть.

Raptor создавался с учетом нескольких целей:

  1. Тест производительности Firefox OS без влияния на производительность. Нам не нужны полифиллы, тестовый код или взлом, чтобы получить реалистичные показатели производительности.
  2. Максимально используйте веб-API, заполняя пробелы другими средствами по мере необходимости.
  3. Будьте достаточно гибкими, чтобы соответствовать множеству различных архитектурных стилей приложений.
  4. Быть расширяемым для сценариев тестирования производительности, выходящих за рамки нормы.

Проблема: определение момента намерения пользователя запустить

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

Первый недостаток наших API производительности — это невозможность связать оценку производительности в одном приложении с другим. Raptor преодолевает это довольно грубо: синтаксический анализ журнала.

Да, вы прочитали это правильно. Каждый раз, когда Gecko получает оценку производительности, он регистрирует сообщение (т. е. в adb logcat), а Raptor выполняет потоковую передачу и анализирует журнал в поисках этих маркеров журнала. Типичная запись в журнале выглядит примерно так (расшифруем позже):

I/PerformanceTiming( 6118): Performance Entry: clock.gaiamobile.org|mark|visuallyLoaded|1074.739956|0.000000|1434771805380

Важно отметить в этой записи журнала ее происхождение: clock.gaiamobile.org или приложение Часы; здесь приложение Часы создало свой визуально загруженный маркер. В случае с домашним экраном мы хотим создать маркер, предназначенный для другого контекста. Вместе с маркером потребуются дополнительные метаданные, но, к сожалению, этот API пока не имеет такой возможности. В Gaia мы приняли соглашение @ для переопределения контекста маркера, и с его помощью давайте отметим момент запуска приложения, определяемый моментом, когда пользователь нажимает на значок:

performance.mark(`appLaunch@${appOrigin}`)

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

I/PerformanceTiming( 5582): Performance Entry: verticalhome.gaiamobile.org|mark|[email protected]|80081.169720|0.000000|1434771804212

С Raptor мы меняем контекст маркера, если видим это соглашение @.

Проблема: несравнимые числа

Второе разделение наших API-интерфейсов производительности связано с несопоставимостью оценок производительности между процессами. Использование performance.mark() в двух отдельных приложениях не приведет к каким-либо значимым числам, которые мы можем сравнить для определения продолжительности времени, поскольку их значения не имеют общей точки отсчета абсолютного времени. К счастью, существует абсолютная привязка ко времени, доступная всем JS: эпоха Unix.

Глядя на вывод Date.now() в любой момент, вы получите количество миллисекунд, прошедших с 1 января 1970 года, и подробности. Raptor пришлось пойти на важный компромисс: отказаться от точности времени с высоким разрешением ради сопоставимости эпохи Unix. Глядя на предыдущую запись журнала, давайте разберем, что выводится. Обратите внимание на корреляцию некоторых фрагментов с их аналогами User Timing:

  • Уровень журнала и тег: I/PerformanceTiming
  • Идентификатор процесса: 5582
  • Базовый контекст: verticalhome.gaiamobile.org
  • Тип записи: отметка, но может быть мера
  • Имя записи: [email protected], соглашение @ переопределяет контекст метки.
  • Время начала: 80081.169720,
  • Длительность: 0.000000, это отметка, а не мера
  • Эпоха: 1434771804212

Для каждой оценки производительности и меры Gecko также фиксирует эпоху отметки, и мы можем использовать это для сравнения времени разных процессов.

Плюсы и минусы

Все это игра компромиссов, и тестирование производительности с Raptor не является исключением:

  • Мы обмениваем время с высоким разрешением на разрешение в миллисекундах, чтобы мы могли сравнивать числа в разных процессах.
  • Мы обмениваем API-интерфейсы JavaScript на анализ журналов, поэтому мы можем получать доступ к данным, не внедряя пользовательскую логику в каждое приложение, что может повлиять на его производительность.
  • В настоящее время мы обмениваем высокоуровневый API взаимодействия, то есть Marionette, на низкоуровневое взаимодействие, используя Orangutan за кулисами. Хотя это позволяет нам иметь прозрачные события для платформы, это также затрудняет написание полнофункциональных тестов. В будущем планируется улучшить это, добавив интеграцию Marionette.

Анализ журнала?

Вы можете быть человеком, который считает синтаксический анализ журнала злом, и в определенной степени я с вами согласен. Хотя я хочу, чтобы каждое решение можно было решить с помощью API производительности, к сожалению, его пока просто не существует. Это лишь одна из причин, почему такие проекты, как Firefox OS, важны для продвижения Интернета: мы находим варианты использования, в которых Интернет не был полностью реализован, протыкаем дыры и находим то, чего не хватает, и, в конечном итоге, улучшаем API для всех, продвигая эти отверстия должны быть заполнены стандартами. Синтаксический анализ журналов — это временная мера Raptor, пока Интернет не наверстает упущенное.

Раптор Флоу

Raptor — это модуль Node.js, встроенный в проект Gaia, который позволяет проекту проводить тесты производительности на устройстве или эмуляторе. После установки зависимостей проекта запустить тесты производительности из каталога Gaia несложно:

  1. Установить профиль Raptor на устройство; это настраивает различные параметры для помощи в тестировании производительности. Обратите внимание, что это другой профиль, который сбросит Gaia, поэтому имейте это в виду, если у вас сохранены какие-либо определенные настройки.
    make raptor
  2. Выберите тест для запуска. В настоящее время тесты хранятся в файле testings/raptor в дереве Gaia, поэтому требуется некоторое ручное обнаружение. В ближайшее время планируется улучшить API командной строки.
  3. Запустите тест. Например, вы можете протестировать производительность холодного запуска приложения «Часы» с помощью следующей команды, указав количество запусков для его запуска:
APP=clock RUNS=5 node tests/raptor/launch_test

4. Наблюдайте за выводом консоли. В конце теста вам будет предоставлена ​​таблица результатов теста с некоторой статистикой о выполненных прогонах производительности. Пример:

[Cold Launch: Clock Results] Results for clock.gaiamobile.org Metric Mean Median Min Max StdDev p95 -------------------------------- ------- ------- ------- ------- ------ ------- coldlaunch.navigationLoaded 214.100 212.000 176.000 269.000 19.693 247.000 coldlaunch.navigationInteractive 245.433 242.000 216.000 310.000 19.944 274.000 coldlaunch.visuallyLoaded 798.433 810.500 674.000 967.000 71.869 922.000 coldlaunch.contentInteractive 798.733 810.500 675.000 967.000 71.730 922.000 coldlaunch.fullyLoaded 802.133 813.500 682.000 969.000 72.036 928.000 coldlaunch.rss 10.850 10.800 10.600 11.300 0.180 11.200 coldlaunch.uss 0.000 0.000 0.000 0.000 0.000 n/a coldlaunch.pss 6.190 6.200 5.900 6.400 0.114 6.300

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

Официальные показатели

На raptor.mozilla.org у нас есть информационные панели для сохранения значений показателей производительности с течением времени. В нашей инфраструктуре автоматизации мы выполняем тесты производительности на устройствах для каждой новой сборки, созданной mozilla-central или b2g-inbound (примечание: источник сборок может измениться в будущем). В настоящее время это ограничено Flames, работающим с 319 МБ памяти, но в ближайшем будущем планируется расширение до других конфигураций памяти и дополнительных типов устройств. Когда автоматизация получает новую сборку, мы запускаем ряд тестов производительности на устройствах, фиксируя такие показатели, как время запуска приложения и объем памяти при полной загрузке, продолжительность перезагрузки и ток питания. Эти числа сохраняются и визуализируются много раз в день, в зависимости от коммитов за день.

Глядя на эти графики, вы можете перейти к конкретным приложениям, сфокусировать или расширить свой запрос времени, а также выполнить расширенную обработку запросов, чтобы получить представление о производительности. Отслеживая тенденции с течением времени, вы даже можете использовать его для выявления регрессий, которые проникли в ОС Firefox.

Локальная визуализация

Тот же самый инструмент визуализации и серверная часть, используемые raptor.mozilla.org, также доступны в виде образа Docker. После запуска локальных тестов Raptor данные будут передаваться на вашу собственную панель визуализации на основе этих локальных показателей. Есть некоторые дополнительные условия для локальной визуализации, поэтому обязательно прочитайте документацию на MDN, чтобы начать.

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

Поиск регрессии в CI

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

Поиск регрессии в автоматизации

К счастью, у нас есть автоматизация после фиксации для запуска тестов производительности на реальных устройствах, и именно оттуда информационные панели получают свои данные. Основываясь на превосходном инструменте Python от Will Lachance, мы ежедневно запрашиваем наши исторические данные, пытаясь обнаружить любые небольшие регрессии, которые могли появиться в Firefox OS за предыдущие семь дней. О любых обнаруженных аномалиях производительности незамедлительно сообщается в Bugzilla, а соответствующие наблюдатели за ошибками уведомляются об этом.

Резюме и следующие шаги

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

Raptor был захватывающим инструментом для создания, и в то же время помог нам продвигать Интернет вперед в области производительности. Мы планируем использовать его для обеспечения быстрой работы Firefox OS и будем активно защищать ее производительность.

Первоначально опубликовано на eliperelman.com.