发布时间:2020年10月2日 - 6分钟阅读
概述
在开发过程中,我们经常想在浏览器中测试一个应用程序的性能。性能测试是很有用的,因为它可以揭示使应用程序变慢的潜在错误。 本文介绍了一种在Chrome中测试应用性能的方法。这种方法与我们测试新的Flutter Gallery的性能的方法类似。
示例应用
我们使用一个简单的应用程序,其中包含一个appbar,一个浮动的操作按钮,以及一个无限的项目列表。列表中还显示了按钮被按下的次数。
该应用有第二个页面,包含一些信息。
你可以在这里克隆这个应用。
要测试什么?
我们想在以下使用场景下测试该应用在Chrome中的性能。
- 用户在无限列表中滚动。
- 用户在两个页面之间切换。
- 用户点击浮动操作按钮。
设置框架
在 pubspec.yaml 中添加以下内容。
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
这个依赖关系引入了web_benchmarks_framework,一个在Chrome中实现性能测试的最小包。
这个包改编自macrobenchmarks和devicelab,这两个包被Flutter用于Flutter Gallery上的web性能测试。目前,这两个包是专门用于flutter/flutter内的web性能测试的,所以导入更通用的包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;
/// A recorder that measures frame building durations.
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类,AppRecorder类扩展了WidgetRecorder类,它在驱动应用时也会记录性能数据。runBenchmarks是一个定义在package:web_benchmarks_framework/driver.dart中的函数,它允许用户选择运行哪个基准,并在浏览器中显示结果。- 方法
automate使用的是flutter_test包,它提供了在应用程序中做手势或查找某些小部件的方法。
运行第一个测试
在项目的根目录下,运行flutter run -d chrome -t lib/benchmarks/runner.dart。这将告诉Flutter使用runner.dart作为入口点,而不是main.dart。
目前只有一个标杆,点击 "滚动 "即可开始。
测试开始,列表自动向下滚动。 几秒钟后测试结束,显示如下画面。
这张图显示了应用程序绘制每个(记录的)帧所花费的时间。横轴代表时间流向;纵轴代表每个帧所花费的时间。
图表的前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 "是第一次走动。它并不渲染任何东西,但它计算出以后用于渲染的值。例如:变换矩阵、变换的逆和剪辑。
- "应用帧 "是指UI实际渲染的第二走。
- "绘制框架 "是框架渲染一帧的总时间。它包括 "Preroll "和 "Apply frame",但它也包括构建和布局widget所花费的时间。
- "总UI框架 "包括 "绘制框架 "中的所有内容,但它也包括浏览器执行的一些隐藏工作,如图层树更新、样式重新计算和浏览器端布局(不要与Flutter自己的布局相混淆)。
- 当收集到一个数据集(一个持续时间的列表)时,算法会去除异常值。
- 首先,计算数据的均值和标准差,任何高于(均值+1标准差)的数据点都会被认为是异常值。
- 非离群值(干净数据)的均值和标准差被用来计算数据集的平均值和噪声,然后报告。
- 所有离群值的平均值,以及 "离群值平均值 "和 "非离群值平均值 "的比率也会被报告。
- 对于每个数据集,"outlierRatio "和 "noise "都是一个很好的指标,可以反映出应用程序的性能中有多少噪音。如果结果过于嘈杂,可能表明性能不一致(如GC停顿时的抖动帧)。通过旨在降低噪音,你可以让你的应用执行得更流畅。
添加更多测试
编辑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反复检查是否有动画发生,当所有动画停止后就停止。例如,这可以确保成功过渡到 "关于 "页面。
在 "page "和 "tap "基准中,_completed布尔值跟踪自动手势是否已经完成。
在 "page "和 "tap "基准中,覆盖shouldContinue方法会导致AppRecorder在所有手势完成后停止录制帧。
如何运行这些测试?
要在Chrome中运行这些测试(并查看动画),请运行。
flutter run -d chrome -t lib/benchmarks/runner.dart --profile
要运行这些测试并收集DevTools数据,请运行。
dart test/run_benchmarks.dart
下一步是什么?
一旦你有了收集性能数据的方法,你就可以随心所欲地使用它。
- 你可以在CI中设置一个作业,每当有人提交PR时,就运行这些基准测试,以避免引入性能重的变化。
- 你也可以设置一个仪表板,跟踪性能基准的趋势。这就是我们为Flutter Gallery做的事情(见Flutter Dashboard)。
通过www.DeepL.com/Translator(免费版)翻译