Собственная оценка эффективности страницы (Page Performance Score, PPS) Airbnb предназначена для отражения сложных и комплексных реалий эффективности работы приложений путем сбора множества ориентированных на пользователя показателей эффективности и объединения их в единую оценку от 0 до 100. В этом посте мы подробно рассмотрим, как мы определяем и реализуем эти метрики на Android. Обязательно сначала прочтите обзорную запись в блоге, чтобы ознакомиться с нашими метриками и формулой PPS. Об оценке эффективности страниц в iOS мы писали здесь.
Инструментарий
Универсальная система отслеживания страниц
Весь путь клиента на Airbnb разделен на разные страницы, каждая из которых имеет собственный измеренный PPS. Для поддержки этой страничной системы отслеживания производительности мы создали стандартизированную инфраструктуру, которая позволяет инженерам настраивать страницы, представляющие их функции.
В Android страница связана с фрагментом. Каждый фрагмент должен предоставлять объект LoggingConfig, определяющий имя страницы, которое впоследствии можно будет получить всякий раз, когда необходимо будет на него сослаться. Мы собираем данные о производительности на протяжении всего жизненного цикла фрагмента и генерируем событие лога только тогда, когда фрагмент приостановлен.
Универсальное перечисление PageName используется для уникальной идентификации каждой страницы и используется на всех платформах для единообразного представления каждой страницы на пути пользователя.
Регистрация воспринимаемого пользователями времени ожидания
Ключевым отличием нашего нового показателя эффективности страницы является то, что он измеряет время ожидания, которое могут видеть пользователи. В отличие от наших ранних усилий по измерению (упомянутые в нашей обзорной статье), основанных на широко известной метрике Time To Interactive (TTI), которая измеряет время выполнения кода и длину асинхронных вызовов. Например, PPS измеряет, как долго пользователь видит индикаторы загрузки на экране, а TTI измеряет, сколько времени требуется сетевому запросу для возврата результатов и сколько времени требуется для построения моделей представления. Мы считаем, что PPS более точно отражает производительность, которую ощущают наши пользователи.
Чтобы зафиксировать визуально воспринимаемое время ожидания, нам потребовались, чтобы все представления с состоянием загрузки имели реализацию API, который сообщает об изменениях их состояния загрузки. Мы создали простой интерфейс под названием LoadableView.
Мы предоставляем примитивы, такие как базовая группа ViewGroup, базовый TextView и базовый ImageView, все из которых реализуют интерфейс LoadableView. Нашим разработчикам просто нужно унаследовать эти примитивы, чтобы их представления были автоматически инструментированы.
Одна из проблем заключалась в том, что нам нужно было отслеживать видимость представления, потому что, если представление не отображается хотя бы на 10% на экране, мы не хотим включать время его загрузки в наши измерения. Вычисление процента видимости каждого представления выполняется часто и рекурсивно. Кроме того, большинство наших представлений находятся в RecyclerView, и мы должны убедиться, что их видимость обновляется правильно при каждом событии прокрутки, сохраняя при этом работоспособность RecyclerView. Мы разработали алгоритмы для уменьшения частоты и сложности этих вычислений, включая кэширование состояний видимости в RecyclerView.
Реализация метрики
Время до первого макета (Time to First Layout, TTFL)
TTFL измеряет, как долго пользователь должен ждать, прежде чем увидит какой-либо контент на экране. TTFL начинается при инициализации фрагмента и заканчивается первым событием onGlobalLayout после того, как фрагмент размещен, после чего система завершает наполнение, измерение и размещение иерархии представления фрагмента.
Медленный TTFL часто указывает на то, что иерархия представления фрагмента чрезмерно сложна или поток пользовательского интерфейса занят ненужными задачами во время инициализации фрагмента.
Время до начальной загрузки (Time to Initial Load, TTIL)
TTIL измеряет, как долго пользователь видит индикаторы загрузки (за исключением загрузки мультимедиа, которая измеряется отдельно) до того, как на экране отображается значимый контент. TTIL начинается при инициализации фрагмента, как TTFL, и заканчивается, когда больше нет представлений на экране в состоянии загрузки. Если экран (фрагмент) статичен или кеширован, индикатор загрузки не отображается. В этом сценарии TTIL будет таким же, как TTFL.
Медленный TTIL часто открывает возможности для уменьшения задержки в сети или времени рендеринга на клиенте. Для задержки в сети мы ищем медленные серверные службы, большие куски данных, неиспользуемый кеш или не оптимизированный парсер данных. Что касается времени рендеринга, мы стараемся следовать передовым методам использования RecyclerView, избегать тяжелых или рекурсивных вычислений при построении моделей представлений, а также сокращать время рисования и т.д.
Как упоминалось выше, представления с состоянием загрузки могут наследовать от базовых примитивов со встроенными реализациями LoadableView. API автоматически сообщает об изменениях состояния загрузки представления в наш фреймворк логирования. Мы используем простой счетчик, который увеличивается, когда представление входит в состояние загрузки, и уменьшается, когда данные загружаются. Когда счетчик равен 0, мы знаем, что на экране больше нет загружаемых представлений.
Этот GIF демонстрирует TTFL (записывается, когда отображается серый фон с логотипом Airbnb) и TTIL (записывается, когда точки загрузки заменяются значимым контентом).
Зависание основного потока (Main Thread Hangs, MTH)
Пользователи сталкиваются с зависаниями, задержками и подергиваниями экрана, когда рендеринг кадров пользовательского интерфейса занимает слишком много времени. У каждого устройства Android есть целевая частота обновления кадров в зависимости от возможностей устройства. Однако, когда основной поток слишком загружен, устройство работает медленнее, чем оно может и не способно поддерживать нужную частоту кадров. Мы определяем MTH как ситуацию, когда для рендеринга любого кадра требуется более чем удвоенная частота обновления кадров системы.
Частые MTH указывают на то, что основной поток может быть перегружен. Тяжелые операции или вычисления следует убрать из потока пользовательского интерфейса или отложить до визуализации содержимого.
MTH рассчитывается с использованием FrameMetrics, сообщаемых системой Android. Мы получаем частоту обновления кадров из системы и используем ее для расчета порога зависания потока. Затем мы прослушиваем системные колбеки для получения FrameMetrics, если длительность кадра превышает наш порог, мы записываем дельту (frameDuration — hangThreshold) как зависание.
Дополнительное время загрузки (Additional Load Time, ALT)
ALT измеряет любое время ожидания, которое происходит после начальной загрузки, например ожидание разбивки на страницы списка или обновления содержимого после нажатия кнопки «Сохранить». ALT запускается всякий раз, когда представление переходит в состояние загрузки после того, как TTIL уже был получен, и заканчивается, когда больше не отображаются представления загрузки. ALT может начинаться и заканчиваться несколько раз, каждый раз записывается как отдельный ALT.
Возможности улучшить ALT часто заключаются в прогнозировании и предварительной выборке дополнительного контента. Общий PPS также можно улучшить, сбалансировав количество загружаемого контента при начальной и дополнительных загрузках.
Этот GIF демонстрирует ALT (записывается, когда индикатор загрузки внизу заменяется разбитым на страницы контентом, загруженным из сети).
Время загрузки мультимедийного содержимого (Rich Content Load Time, RCLT)
RCLT измеряет, как долго пользователь видит заполнитель или индикатор загрузки, пока изображение, видео или какой-либо мультимедийный контент не отобразится полностью. ImageView и другие мультимедийные контейнеры реализуют тот же LoadableView API, чтобы сообщать об изменениях состояния загрузки в регистратор PPS.
Чтобы улучшить RCLT, мы стремимся уменьшить размер изображения, улучшить кэширование изображений, оптимизировать форматы изображений и работу с ними, стратегически планируем загрузку rich контента, который еще не отображается на экране, выбираем производительные библиотеки потоковой передачи и т.д.
Этот GIF демонстрирует RCLT (записывается, когда заполнители заменяются фактическими изображениями, загруженными из сети).
Выводы
Мы успешно создали инструментальный фреймворк для Android для сбора более обширных и ориентированных на пользователя показателей производительности, руководствуясь теми же принципами проектирования, что и в оценке производительности страницы Airbnb для веб и нативных платформ. На основе этой платформы и собранных данных мы создали информационные панели для мониторинга производительности всего приложения, настроили автоматические оповещения для владельцев страниц, оптимизировали постановку целей по производительности на уровне команды и организации, а также систематически отслеживаем и боремся со снижением производительности.
В 2022 году мы планируем улучшить детализацию и точность наших инструментов, таких как измерение отклика тапов, лучшая дифференциация производительности во время прокрутки и предоставление примитивов со встроенной оптимизацией производительности. Мы также выделим ресурсы на создание инструментария для улучшения отлаживаемости и включения раннего обнаружения и предотвращения регрессии с помощью синтетического тестирования.
PPS дает нашим инженерам и специалистам по обработке данных более глубокую информацию и больше возможностей для улучшения наших продуктов. Это также укрепляет нашу приверженность Культуре создания. Мы надеемся, что вы примените эти знания и в своей организации.