Skip to content

Commit 330581f

Browse files
Refactor the test benchmark app to make the example easier to follow (flutter#7640)
A breaking change to this package will follow that changes the structure of setting the initial path for the benchmark run. This was originally done in flutter/packages#7632, which was closed because there were more changes needed to get the tests to pass. This cleanup is part of that work. This PR - moves the benchmark clients to `test_app/benchmark` following pub package guidance for naming conventions - regenerates the test_app web assets using `flutter create` to ensure the web configuration is up to date - adds more structure to the benchmark test_infra setup to provide a more thorough example of what a user of this package might need to do. I followed the structure that is used in DevTools benchmark tests.
1 parent 31e1e66 commit 330581f

25 files changed

+386
-217
lines changed

packages/web_benchmarks/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.1.0-wip
2+
3+
* Restructure the `testing/test_app` to make the example benchmarks easier to follow.
4+
15
## 2.0.2
26

37
* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.

packages/web_benchmarks/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ benchmarks in Chrome.
55

66
# Writing a benchmark
77

8-
An example benchmark can be found in [testing/web_benchmark_test.dart][1].
8+
An example benchmark can be found in [testing/test_app/benchmark/web_benchmark_test.dart][1].
99

1010
A web benchmark is made of two parts: a client and a server. The client is code
1111
that runs in the browser together with the benchmark code. The server serves the
1212
app's code and assets. Additionally, the server communicates with the browser to
1313
extract the performance traces.
1414

15-
[1]: https://github.com/flutter/packages/blob/master/packages/web_benchmarks/testing/web_benchmarks_test.dart
15+
[1]: https://github.com/flutter/packages/blob/master/packages/web_benchmarks/testing/test_app/benchmark/web_benchmarks_test.dart
1616

1717
# Analyzing benchmark results
1818

packages/web_benchmarks/lib/server.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ const int defaultChromeDebugPort = 10000;
3232
/// can be different (and typically is) from the production entry point of the
3333
/// app.
3434
///
35-
/// If [useCanvasKit] is true, builds the app in CanvasKit mode.
36-
///
3735
/// [benchmarkServerPort] is the port this benchmark server serves the app on.
3836
/// By default uses [defaultBenchmarkServerPort].
3937
///
@@ -42,6 +40,13 @@ const int defaultChromeDebugPort = 10000;
4240
///
4341
/// If [headless] is true, runs Chrome without UI. In particular, this is
4442
/// useful in environments (e.g. CI) that doesn't have a display.
43+
///
44+
/// If [treeShakeIcons] is false, '--no-tree-shake-icons' will be passed as a
45+
/// build argument when building the benchmark app.
46+
///
47+
/// [compilationOptions] specify the compiler and renderer to use for the
48+
/// benchmark app. This can either use dart2wasm & skwasm or
49+
/// dart2js & canvaskit.
4550
Future<BenchmarkResults> serveWebBenchmark({
4651
required io.Directory benchmarkAppDirectory,
4752
required String entryPoint,

packages/web_benchmarks/lib/src/runner.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,20 @@ class BenchmarkServer {
3939
/// can be different (and typically is) from the production entry point of the
4040
/// app.
4141
///
42-
/// If [useCanvasKit] is true, builds the app in CanvasKit mode.
43-
///
4442
/// [benchmarkServerPort] is the port this benchmark server serves the app on.
4543
///
4644
/// [chromeDebugPort] is the port Chrome uses for DevTool Protocol used to
4745
/// extract tracing data.
4846
///
4947
/// If [headless] is true, runs Chrome without UI. In particular, this is
5048
/// useful in environments (e.g. CI) that doesn't have a display.
49+
///
50+
/// If [treeShakeIcons] is false, '--no-tree-shake-icons' will be passed as a
51+
/// build argument when building the benchmark app.
52+
///
53+
/// [compilationOptions] specify the compiler and renderer to use for the
54+
/// benchmark app. This can either use dart2wasm & skwasm or
55+
/// dart2js & canvaskit.
5156
BenchmarkServer({
5257
required this.benchmarkAppDirectory,
5358
required this.entryPoint,

packages/web_benchmarks/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: web_benchmarks
22
description: A benchmark harness for performance-testing Flutter apps in Chrome.
33
repository: https://github.com/flutter/packages/tree/main/packages/web_benchmarks
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+web_benchmarks%22
5-
version: 2.0.2
5+
version: 2.1.0-wip
66

77
environment:
88
sdk: ^3.3.0

packages/web_benchmarks/testing/test_app/.gitignore

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
*.swp
66
.DS_Store
77
.atom/
8+
.build/
89
.buildlog/
910
.history
1011
.svn/
12+
.swiftpm/
13+
migrate_working_dir/
1114

1215
# IntelliJ related
1316
*.iml
@@ -26,7 +29,6 @@
2629
.dart_tool/
2730
.flutter-plugins
2831
.flutter-plugins-dependencies
29-
.packages
3032
.pub-cache/
3133
.pub/
3234
/build/
@@ -36,3 +38,8 @@ app.*.symbols
3638

3739
# Obfuscation related
3840
app.*.map.json
41+
42+
# Android Studio will place build artifacts here
43+
/android/app/debug
44+
/android/app/profile
45+
/android/app/release

packages/web_benchmarks/testing/test_app/.metadata

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,27 @@
44
# This file should be version controlled and should not be manually edited.
55

66
version:
7-
revision: d26268bb9e6d713a73d6148da7fa75936d442741
8-
channel: master
7+
revision: "0cd170798c6462aec738d4c749ce3a5fff1c80cf"
8+
channel: "master"
99

1010
project_type: app
11+
12+
# Tracks metadata for the flutter migrate command
13+
migration:
14+
platforms:
15+
- platform: root
16+
create_revision: 0cd170798c6462aec738d4c749ce3a5fff1c80cf
17+
base_revision: 0cd170798c6462aec738d4c749ce3a5fff1c80cf
18+
- platform: web
19+
create_revision: 0cd170798c6462aec738d4c749ce3a5fff1c80cf
20+
base_revision: 0cd170798c6462aec738d4c749ce3a5fff1c80cf
21+
22+
# User provided section
23+
24+
# List of Local paths (relative to this file) that should be
25+
# ignored by the migrate tool.
26+
#
27+
# Files that are not part of the templates will be ignored by default.
28+
unmanaged_files:
29+
- 'lib/main.dart'
30+
- 'ios/Runner.xcodeproj/project.pbxproj'
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// ignore_for_file: avoid_print
6+
7+
import 'dart:async';
8+
9+
import 'package:flutter/foundation.dart';
10+
import 'package:flutter/material.dart';
11+
import 'package:flutter_test/flutter_test.dart';
12+
import 'package:test_app/home_page.dart' show aboutPageKey, textKey;
13+
import 'package:test_app/main.dart';
14+
import 'package:web/web.dart';
15+
import 'package:web_benchmarks/client.dart';
16+
17+
import 'common.dart';
18+
19+
/// A class that automates the test web app.
20+
class Automator {
21+
Automator({
22+
required this.benchmark,
23+
required this.stopWarmingUpCallback,
24+
required this.profile,
25+
});
26+
27+
/// The current benchmark.
28+
final BenchmarkName benchmark;
29+
30+
/// A function to call when warm-up is finished.
31+
///
32+
/// This function is intended to ask `Recorder` to mark the warm-up phase
33+
/// as over.
34+
final void Function() stopWarmingUpCallback;
35+
36+
/// The profile collected for the running benchmark
37+
final Profile profile;
38+
39+
/// Whether the automation has ended.
40+
bool finished = false;
41+
42+
/// A widget controller for automation.
43+
late LiveWidgetController controller;
44+
45+
Widget createWidget() {
46+
Future<void>.delayed(const Duration(milliseconds: 400), automate);
47+
return const MyApp();
48+
}
49+
50+
Future<void> automate() async {
51+
await warmUp();
52+
53+
switch (benchmark) {
54+
case BenchmarkName.appNavigate:
55+
await _handleAppNavigate();
56+
case BenchmarkName.appScroll:
57+
await _handleAppScroll();
58+
case BenchmarkName.appTap:
59+
await _handleAppTap();
60+
case BenchmarkName.simpleCompilationCheck:
61+
_handleSimpleCompilationCheck();
62+
case BenchmarkName.simpleInitialPageCheck:
63+
_handleSimpleInitialPageCheck();
64+
}
65+
66+
// At the end of the test, mark as finished.
67+
finished = true;
68+
}
69+
70+
/// Warm up the animation.
71+
Future<void> warmUp() async {
72+
// Let animation stop.
73+
await animationStops();
74+
75+
// Set controller.
76+
controller = LiveWidgetController(WidgetsBinding.instance);
77+
78+
await controller.pumpAndSettle();
79+
80+
// When warm-up finishes, inform the recorder.
81+
stopWarmingUpCallback();
82+
}
83+
84+
Future<void> _handleAppNavigate() async {
85+
for (int i = 0; i < 10; ++i) {
86+
print('Testing round $i...');
87+
await controller.tap(find.byKey(aboutPageKey));
88+
await animationStops();
89+
await controller.tap(find.byType(BackButton));
90+
await animationStops();
91+
}
92+
}
93+
94+
Future<void> _handleAppScroll() async {
95+
final ScrollableState scrollable =
96+
Scrollable.of(find.byKey(textKey).evaluate().single);
97+
await scrollable.position.animateTo(
98+
30000,
99+
curve: Curves.linear,
100+
duration: const Duration(seconds: 20),
101+
);
102+
}
103+
104+
Future<void> _handleAppTap() async {
105+
for (int i = 0; i < 10; ++i) {
106+
print('Testing round $i...');
107+
await controller.tap(find.byIcon(Icons.add));
108+
await animationStops();
109+
}
110+
}
111+
112+
void _handleSimpleCompilationCheck() {
113+
// Record whether we are in wasm mode or not. Ideally, we'd have a more
114+
// first-class way to add metadata like this, but this will work for us to
115+
// pass information about the environment back to the server for the
116+
// purposes of our own tests.
117+
profile.extraData['isWasm'] = kIsWasm ? 1 : 0;
118+
}
119+
120+
void _handleSimpleInitialPageCheck() {
121+
// Record whether the URL contains the expected initial page so we can
122+
// verify the behavior of setting the `initialPage` on the benchmark server.
123+
final bool containsExpectedPage =
124+
window.location.toString().contains(testBenchmarkInitialPage);
125+
profile.extraData['expectedUrl'] = containsExpectedPage ? 1 : 0;
126+
}
127+
}
128+
129+
const Duration _animationCheckingInterval = Duration(milliseconds: 50);
130+
131+
Future<void> animationStops() async {
132+
if (!WidgetsBinding.instance.hasScheduledFrame) {
133+
return;
134+
}
135+
136+
final Completer<void> stopped = Completer<void>();
137+
138+
Timer.periodic(_animationCheckingInterval, (Timer timer) {
139+
if (!WidgetsBinding.instance.hasScheduledFrame) {
140+
stopped.complete();
141+
timer.cancel();
142+
}
143+
});
144+
145+
await stopped.future;
146+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:web_benchmarks/client.dart';
6+
7+
import '../common.dart';
8+
import '../recorder.dart';
9+
10+
Future<void> main() async {
11+
await runBenchmarks(
12+
<String, RecorderFactory>{
13+
BenchmarkName.appNavigate.name: () =>
14+
TestAppRecorder(benchmark: BenchmarkName.appNavigate),
15+
BenchmarkName.appScroll.name: () =>
16+
TestAppRecorder(benchmark: BenchmarkName.appScroll),
17+
BenchmarkName.appTap.name: () =>
18+
TestAppRecorder(benchmark: BenchmarkName.appTap),
19+
},
20+
);
21+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:web_benchmarks/client.dart';
6+
7+
import '../common.dart';
8+
import '../recorder.dart';
9+
10+
Future<void> main() async {
11+
await runBenchmarks(
12+
<String, RecorderFactory>{
13+
BenchmarkName.simpleCompilationCheck.name: () => TestAppRecorder(
14+
benchmark: BenchmarkName.simpleCompilationCheck,
15+
),
16+
},
17+
);
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:web_benchmarks/client.dart';
6+
7+
import '../common.dart';
8+
import '../recorder.dart';
9+
10+
Future<void> main() async {
11+
await runBenchmarks(
12+
<String, RecorderFactory>{
13+
BenchmarkName.simpleInitialPageCheck.name: () => TestAppRecorder(
14+
benchmark: BenchmarkName.simpleInitialPageCheck,
15+
),
16+
},
17+
);
18+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
const String testBenchmarkInitialPage = 'index.html#about';
6+
7+
enum BenchmarkName {
8+
appNavigate,
9+
appScroll,
10+
appTap,
11+
simpleInitialPageCheck,
12+
simpleCompilationCheck;
13+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:web_benchmarks/client.dart';
7+
8+
import 'automator.dart';
9+
import 'common.dart';
10+
11+
/// A recorder that measures frame building durations for the test app.
12+
class TestAppRecorder extends WidgetRecorder {
13+
TestAppRecorder({required this.benchmark})
14+
: super(name: benchmark.name, useCustomWarmUp: true);
15+
16+
/// The name of the benchmark to be run.
17+
///
18+
/// See `common.dart` for the list of the names of all benchmarks.
19+
final BenchmarkName benchmark;
20+
21+
Automator? _automator;
22+
bool get _finished => _automator?.finished ?? false;
23+
24+
/// Whether we should continue recording.
25+
@override
26+
bool shouldContinue() => !_finished || profile.shouldContinue();
27+
28+
/// Creates the [Automator] widget.
29+
@override
30+
Widget createWidget() {
31+
_automator = Automator(
32+
benchmark: benchmark,
33+
stopWarmingUpCallback: profile.stopWarmingUp,
34+
profile: profile,
35+
);
36+
return _automator!.createWidget();
37+
}
38+
}

0 commit comments

Comments
 (0)