[Flutter翻译]网络性能测试

469 阅读7分钟

原文地址:medium.com/flutter/per…

原文作者:medium.com/@tianguang

发布时间:2020年10月2日 - 6分钟阅读

概述

在开发过程中,我们经常想在浏览器中测试一个应用程序的性能。性能测试是很有用的,因为它可以揭示使应用程序变慢的潜在错误。 本文介绍了一种在Chrome中测试应用性能的方法。这种方法与我们测试新的Flutter Gallery的性能的方法类似。

示例应用

我们使用一个简单的应用程序,其中包含一个appbar,一个浮动的操作按钮,以及一个无限的项目列表。列表中还显示了按钮被按下的次数。

该应用有第二个页面,包含一些信息。

你可以在这里克隆这个应用。

要测试什么?

我们想在以下使用场景下测试该应用在Chrome中的性能。

  1. 用户在无限列表中滚动。
  2. 用户在两个页面之间切换。
  3. 用户点击浮动操作按钮。

设置框架

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中实现性能测试的最小包。

这个包改编自macrobenchmarksdevicelab,这两个包被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(免费版)翻译