Тестирование производительности приложения в браузере Chrome



Книга Тестирование производительности приложения в браузере Chrome

Введение 


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


Образец приложения 


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



У приложения есть вторая страница, содержащая определенную информацию. 



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



Что будем тестировать? 


Наша цель  —  протестировать производительность приложения в следующих сценариях использования:


  1. Пользователь прокручивает бесконечный список. 
  2. Пользователь переключается между двумя страницами. 
  3. Пользователь нажимает плавающую кнопку действия. 

Установка фреймворка 


dependencies:
flutter:
sdk: flutter
web_benchmarks_framework:
git:
url: https://github.com/material-components/material-components-flutter-experimental.git
ref: f6ebb4ed3b6489547d9ae58216df9999112be568
path: web_benchmarks_framework

К pubspec.yaml добавьте следующее: 


Эта зависимость входит в минимальный пакет web_benchmarks_framework, выполняющий тестирование производительности в Chrome. 


Он сформирован из macrobenchmarks и devicelab  —  двух пакетов, используемых Flutter для веб-тестирования производительности во Flutter Gallery. В настоящее время оба этих пакета применяются для аналогичного тестирования во flutter/flutter, поэтому проще импортировать более общий пакет web_benchmarks_framework


Выполните flutter pub get, чтобы присоединить этот пакет. 


Написание первого теста 


Под lib добавьте директорию benchmarks, а к ней  —  файл .dart с именем runner.dart:



Содержит этот файл следующее: 


import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:web_benchmarks_framework/recorder.dart';
import 'package:web_benchmarks_framework/driver.dart';
import 'package:web_benchmarks_example/main.dart';
import 'package:web_benchmarks_example/homepage.dart' show textKey;

///Регистратор, измеряющий длительность формирования кадра.
abstract class AppRecorder extends WidgetRecorder {
AppRecorder({@required this.benchmarkName}) : super(name: benchmarkName);

final String benchmarkName;

Future<void> automate();

@override
Widget createWidget() {
Future.delayed(Duration(milliseconds: 400), automate);
return MyApp();
}

Future<void> animationStops() async {
while (WidgetsBinding.instance.hasScheduledFrame) {
await Future<void>.delayed(Duration(milliseconds: 200));
}
}
}

class ScrollRecorder extends AppRecorder {
ScrollRecorder() : super(benchmarkName: 'scroll');

Future<void> automate() async {
final scrollable = Scrollable.of(find.byKey(textKey).evaluate().single);
await scrollable.position.animateTo(
30000,
curve: Curves.linear,
duration: Duration(seconds: 20),
);
}
}

Future<void> main() async {
await runBenchmarks(
{
'scroll': () => ScrollRecorder(),
},
);
}

Что делает этот тест? 


  • При запуске этого приложения создается объект ScrollRecorder, который управляет приложением, выполняя автоматические жесты. В нашем случае он начинает прокручивать бесконечный список. 
  • Класс ScrollRecorder расширяет класс AppRecorder, в свою очередь расширяющий класс WidgetRecorder, который по мере управления приложением также записывает показатели производительности. 
  • runBenchmarks  —  это функция, определяемая в package:web_benchmarks_framework/driver.dart, которая позволяет пользователю выбрать, какой тест выполнять, и отображает результаты в браузере. 
  • Метод automate использует пакет flutter_test, предоставляющий способы выполнения жестов или поиска определенных виджетов в приложении. 

Выполнение первого теста 


В корневой директории проекта выполните flutter run -d chrome -t lib/benchmarks/runner.dart. Таким образом вы укажите Flutter использовать в качестве входной точки не main.dart, а runner.dart.



На данный момент у нас есть всего один тест производительности, так что запустите его нажатием “scroll”. 



После запуска теста список начинает автоматически прокручиваться вниз. 


Спустя несколько секунд тест заканчиваетсяи выводит на экран следующий результат: 



Этот график показывает время, затраченное приложением на отрисовку каждого (зарегистрированного) кадра. Горизонтальная ось отображает ход времени, а вертикальная  —  продолжительность каждого кадра. 


Первые 2/3 графика окрашены в серый фон  —  эти кадры считаются “подготовительными” и в статистику не включаются. Такие кадры обычно обеспечивают JIT-компилятору время для компиляции кода и заполняют различные кэши, чтобы показатели измеренных кадров отражали “итоговую” производительность приложения, а не только первых секунд его работы. Тем не менее подготовительным этапом не всегда следует пренебрегать, поскольку он может предоставить ценную информацию о производительности приложения в течение первых секунд его выполнения, благодаря чему уже можно будет делать выводы о его работоспособности. 


Красные кадры  —  это “выбросы”, отрисовка которых требует гораздо больше времени. Некоторые из них могут быть почти незаметными. Например, до определенного момента не будет видно подвисаний в начале или конце анимации. А вот заторможенный кадр в середине анимации будет сложно не заметить. 


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


Сбор данных из Chrome DevTools 


Данный тест производительности полностью выполняется внутри Chrome. Добавьте следующий файл в качестве test/run_benchmarks.dart:


import 'dart:convert' show JsonEncoder;

import 'package:web_benchmarks_framework/server.dart';

Future<void> main () async {
final taskResult = await runWebBenchmark(
macrobenchmarksDirectory: '.',
entryPoint: 'lib/benchmarks/runner.dart',
useCanvasKit: false,
);
print (JsonEncoder.withIndent(' ').convert(taskResult.toJson()));
}

Затем запустите dart test/run_benchmarks.dart


Спустя минуту вы увидите следующие результаты: 


Received profile data
{
"success": true,
"data": {
"scroll.html.preroll_frame.average": 93.88659793814433,
"scroll.html.preroll_frame.outlierAverage": 1061.3333333333333,
"scroll.html.preroll_frame.outlierRatio": 11.304417847077339,
"scroll.html.preroll_frame.noise": 0.3103013467989926,
"scroll.html.apply_frame.average": 391.1914893617021,
"scroll.html.apply_frame.outlierAverage": 1462.3333333333333,
"scroll.html.apply_frame.outlierRatio": 3.738152217266761,
"scroll.html.apply_frame.noise": 0.24804233283684318,
"scroll.html.drawFrameDuration.average": 1496.8690476190477,
"scroll.html.drawFrameDuration.outlierAverage": 3622.8125,
"scroll.html.drawFrameDuration.outlierRatio": 2.4202601461781335,
"scroll.html.drawFrameDuration.noise": 0.38481902033678567,
"scroll.html.totalUiFrame.average": 3441
},
"benchmarkScoreKeys": [
"scroll.html.drawFrameDuration.average",
"scroll.html.drawFrameDuration.outlierRatio",
"scroll.html.totalUiFrame.average"
]
}

Точные значения теста производительности могут варьироваться в зависимости от компьютера. 


Что делает этот тест? 


  • При выполнении test/run_benchmarks.dart собирается веб-приложение, затем запускается экземпляр Chrome, в котором это приложение выполняется.
  • После этого test/run_benchmarks.dart соединяется с портом Chrome DevTools, собирая и извлекая из него соответствующие показатели производительности.

Что означают результаты теста? 


  • При отображении кадра происходит двойной обход дерева слоев
  • “Preroll”  —  это первый обход. Он ничего не отображает, но вычисляет значения, которые позже используются для отрисовки. Примеры включают: матрицы преобразований, обратные преобразования и обрезку кадров. 
  • “Apply frame”  —  это второй обход, при котором отображается UI.
  • “Draw frame”  —  это общее время, необходимое фреймворку для отображения кадра. Этот этап включает “Preroll” и “Apply frame”, а также время, затраченное на создание и расположение виджетов.
  • “Total UI frame” включает все составляющие компоненты “Draw frame”, а также скрытую работу, выполняемую браузером: обновления дерева слоев, пересчет стилей и браузерный макет (не путать с собственным макетом Flutter).
  • Когда набор данных (список временных интервалов) собран, алгоритм удаляет выбросы. 
  • Сначала вычисляются среднее и стандартное отклонения данных, и любая точка данных, превышающая эти значения (среднее + 1 стандартное отклонение), считается выбросом. 
  • Затем с помощью среднего и стандартного отклонения данных без выбросов (чистых данных) вычисляются средние значения и шум всего набора, которые впоследствии сообщаются. 
  • Кроме того, сообщается средний показатель всех выбросов, а также отношение этого показателя к чистым данным.
  • Показатели “outlierRatio” и “noise” для каждого набора данных отчетливо указывают, сколько шума содержится в производительности приложения. Наличие чрезмерного шума свидетельствует о проблемах с последовательностью выполнения (например, торможение кадров в результате задержек сборки мусора). Уменьшив шум, вы сможете повысить плавность работы приложения.

Добавляем больше тестов 


Отредактируйте lib/benchmarks/runner.dart, чтобы добавить еще два теста. 


Сначала измените функцию main


Future<void> main() async {
await runBenchmarks(
{
'scroll': () => ScrollRecorder(),
'page': () => PageRecorder(),
'tap': () => TapRecorder(),
},
);
}

Затем добавьте еще два класса, расширяющих AppRecorder


class PageRecorder extends AppRecorder {
PageRecorder() : super(benchmarkName: 'page');

bool _completed = false;

@override
bool shouldContinue() => profile.shouldContinue() || !_completed;

Future<void> automate() async {
final controller = LiveWidgetController(WidgetsBinding.instance);
for (int i = 0; i < 10; ++i) {
print('Testing round $i...');
await controller.tap(find.byKey(aboutPageKey));
await animationStops();
await controller.tap(find.byKey(backKey));
await animationStops();
}
_completed = true;
}
}

class TapRecorder extends AppRecorder {
TapRecorder() : super(benchmarkName: 'tap');

bool _completed = false;

@override
bool shouldContinue() => profile.shouldContinue() || !_completed;

Future<void> automate() async {
final controller = LiveWidgetController(WidgetsBinding.instance);
for (int i = 0; i < 10; ++i) {
print('Testing round $i...');
await controller.tap(find.byIcon(Icons.add));
await animationStops();
}
_completed = true;
}
}


Что делают эти тесты? 


  • Мы добавили два оставшихся теста: один проверяет переключение между страницами, другой  —  касание плавающей кнопки действия.
  • animationStops систематически контролирует выполнение анимации и останавливается по мере ее полного прекращения. Это гарантирует, например, успешный переход на страницу “about”.
  • В тестах для проверки “page” и “tap” логический тип _completed отслеживает завершение автоматических жестов. 
  • В указанных тестах переопределение метода shouldContinue приводит к тому, что AppRecorder останавливает запись кадров после завершения всех жестов. 

Как выполнять эти тесты? 


Для запуска этих тестов (и просмотра анимации) в Chrome выполните: 


  • flutter run -d chrome -t lib/benchmarks/runner.dart --profile.

Для запуска данных тестов и сбора данных DevTools выполните:


  • dart test/run_benchmarks.dart.

Что дальше? 


Теперь, владея способом сбора данных производительности, вы можете использовать его по своему усмотрению:


  • Вы можете настроить задачу в CI, запускающую эти тесты производительности каждый раз, как кто-нибудь отправляет PR, чтобы избежать внесения изменений, сильно влияющих на производительность.  
  • Вы также можете настроить информационную панель, отслеживающую тенденции тестов производительности.

511   0  

Comments

    Ничего не найдено.