Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/web_benchmarks/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.1.0-wip

* Restructure the `testing/test_app` to make the example benchmarks easier to follow.

## 2.0.2

* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
Expand Down
4 changes: 2 additions & 2 deletions packages/web_benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ benchmarks in Chrome.

# Writing a benchmark

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

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

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

# Analyzing benchmark results

Expand Down
9 changes: 7 additions & 2 deletions packages/web_benchmarks/lib/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ const int defaultChromeDebugPort = 10000;
/// can be different (and typically is) from the production entry point of the
/// app.
///
/// If [useCanvasKit] is true, builds the app in CanvasKit mode.
///
/// [benchmarkServerPort] is the port this benchmark server serves the app on.
/// By default uses [defaultBenchmarkServerPort].
///
Expand All @@ -42,6 +40,13 @@ const int defaultChromeDebugPort = 10000;
///
/// If [headless] is true, runs Chrome without UI. In particular, this is
/// useful in environments (e.g. CI) that doesn't have a display.
///
/// If [treeShakeIcons] is false, '--no-tree-shake-icons' will be passed as a
/// build argument when building the benchmark app.
///
/// [compilationOptions] specify the compiler and renderer to use for the
/// benchmark app. This can either use dart2wasm & skwasm or
/// dart2js & canvaskit.
Future<BenchmarkResults> serveWebBenchmark({
required io.Directory benchmarkAppDirectory,
required String entryPoint,
Expand Down
9 changes: 7 additions & 2 deletions packages/web_benchmarks/lib/src/runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,20 @@ class BenchmarkServer {
/// can be different (and typically is) from the production entry point of the
/// app.
///
/// If [useCanvasKit] is true, builds the app in CanvasKit mode.
///
/// [benchmarkServerPort] is the port this benchmark server serves the app on.
///
/// [chromeDebugPort] is the port Chrome uses for DevTool Protocol used to
/// extract tracing data.
///
/// If [headless] is true, runs Chrome without UI. In particular, this is
/// useful in environments (e.g. CI) that doesn't have a display.
///
/// If [treeShakeIcons] is false, '--no-tree-shake-icons' will be passed as a
/// build argument when building the benchmark app.
///
/// [compilationOptions] specify the compiler and renderer to use for the
/// benchmark app. This can either use dart2wasm & skwasm or
/// dart2js & canvaskit.
BenchmarkServer({
required this.benchmarkAppDirectory,
required this.entryPoint,
Expand Down
2 changes: 1 addition & 1 deletion packages/web_benchmarks/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: web_benchmarks
description: A benchmark harness for performance-testing Flutter apps in Chrome.
repository: https://github.com/flutter/packages/tree/main/packages/web_benchmarks
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+web_benchmarks%22
version: 2.0.2
version: 2.1.0-wip

environment:
sdk: ^3.3.0
Expand Down
9 changes: 8 additions & 1 deletion packages/web_benchmarks/testing/test_app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
*.iml
Expand All @@ -26,7 +29,6 @@
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
Expand All @@ -36,3 +38,8 @@ app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
24 changes: 22 additions & 2 deletions packages/web_benchmarks/testing/test_app/.metadata
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,27 @@
# This file should be version controlled and should not be manually edited.

version:
revision: d26268bb9e6d713a73d6148da7fa75936d442741
channel: master
revision: "0cd170798c6462aec738d4c749ce3a5fff1c80cf"
channel: "master"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 0cd170798c6462aec738d4c749ce3a5fff1c80cf
base_revision: 0cd170798c6462aec738d4c749ce3a5fff1c80cf
- platform: web
create_revision: 0cd170798c6462aec738d4c749ce3a5fff1c80cf
base_revision: 0cd170798c6462aec738d4c749ce3a5fff1c80cf

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: avoid_print

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:test_app/home_page.dart' show aboutPageKey, textKey;
import 'package:test_app/main.dart';
import 'package:web/web.dart';
import 'package:web_benchmarks/client.dart';

import 'common.dart';

/// A class that automates the test web app.
class Automator {
Automator({
required this.benchmark,
required this.stopWarmingUpCallback,
required this.profile,
});

/// The current benchmark.
final BenchmarkName benchmark;

/// A function to call when warm-up is finished.
///
/// This function is intended to ask `Recorder` to mark the warm-up phase
/// as over.
final void Function() stopWarmingUpCallback;

/// The profile collected for the running benchmark
final Profile profile;

/// Whether the automation has ended.
bool finished = false;

/// A widget controller for automation.
late LiveWidgetController controller;

Widget createWidget() {
Future<void>.delayed(const Duration(milliseconds: 400), automate);
return const MyApp();
}

Future<void> automate() async {
await warmUp();

switch (benchmark) {
case BenchmarkName.appNavigate:
await _handleAppNavigate();
case BenchmarkName.appScroll:
await _handleAppScroll();
case BenchmarkName.appTap:
await _handleAppTap();
case BenchmarkName.simpleCompilationCheck:
_handleSimpleCompilationCheck();
case BenchmarkName.simpleInitialPageCheck:
_handleSimpleInitialPageCheck();
}

// At the end of the test, mark as finished.
finished = true;
}

/// Warm up the animation.
Future<void> warmUp() async {
// Let animation stop.
await animationStops();

// Set controller.
controller = LiveWidgetController(WidgetsBinding.instance);

await controller.pumpAndSettle();

// When warm-up finishes, inform the recorder.
stopWarmingUpCallback();
}

Future<void> _handleAppNavigate() async {
for (int i = 0; i < 10; ++i) {
print('Testing round $i...');
await controller.tap(find.byKey(aboutPageKey));
await animationStops();
await controller.tap(find.byType(BackButton));
await animationStops();
}
}

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

Future<void> _handleAppTap() async {
for (int i = 0; i < 10; ++i) {
print('Testing round $i...');
await controller.tap(find.byIcon(Icons.add));
await animationStops();
}
}

void _handleSimpleCompilationCheck() {
// Record whether we are in wasm mode or not. Ideally, we'd have a more
// first-class way to add metadata like this, but this will work for us to
// pass information about the environment back to the server for the
// purposes of our own tests.
profile.extraData['isWasm'] = kIsWasm ? 1 : 0;
}

void _handleSimpleInitialPageCheck() {
// Record whether the URL contains the expected initial page so we can
// verify the behavior of setting the `initialPage` on the benchmark server.
final bool containsExpectedPage =
window.location.toString().contains(testBenchmarkInitialPage);
profile.extraData['expectedUrl'] = containsExpectedPage ? 1 : 0;
}
}

const Duration _animationCheckingInterval = Duration(milliseconds: 50);

Future<void> animationStops() async {
if (!WidgetsBinding.instance.hasScheduledFrame) {
return;
}

final Completer<void> stopped = Completer<void>();

Timer.periodic(_animationCheckingInterval, (Timer timer) {
if (!WidgetsBinding.instance.hasScheduledFrame) {
stopped.complete();
timer.cancel();
}
});

await stopped.future;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:web_benchmarks/client.dart';

import '../common.dart';
import '../recorder.dart';

Future<void> main() async {
await runBenchmarks(
<String, RecorderFactory>{
BenchmarkName.appNavigate.name: () =>
TestAppRecorder(benchmark: BenchmarkName.appNavigate),
BenchmarkName.appScroll.name: () =>
TestAppRecorder(benchmark: BenchmarkName.appScroll),
BenchmarkName.appTap.name: () =>
TestAppRecorder(benchmark: BenchmarkName.appTap),
},
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:web_benchmarks/client.dart';

import '../common.dart';
import '../recorder.dart';

Future<void> main() async {
await runBenchmarks(
<String, RecorderFactory>{
BenchmarkName.simpleCompilationCheck.name: () => TestAppRecorder(
benchmark: BenchmarkName.simpleCompilationCheck,
),
},
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:web_benchmarks/client.dart';

import '../common.dart';
import '../recorder.dart';

Future<void> main() async {
await runBenchmarks(
<String, RecorderFactory>{
BenchmarkName.simpleInitialPageCheck.name: () => TestAppRecorder(
benchmark: BenchmarkName.simpleInitialPageCheck,
),
},
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

const String testBenchmarkInitialPage = 'index.html#about';

enum BenchmarkName {
appNavigate,
appScroll,
appTap,
simpleInitialPageCheck,
simpleCompilationCheck;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:web_benchmarks/client.dart';

import 'automator.dart';
import 'common.dart';

/// A recorder that measures frame building durations for the test app.
class TestAppRecorder extends WidgetRecorder {
TestAppRecorder({required this.benchmark})
: super(name: benchmark.name, useCustomWarmUp: true);

/// The name of the benchmark to be run.
///
/// See `common.dart` for the list of the names of all benchmarks.
final BenchmarkName benchmark;

Automator? _automator;
bool get _finished => _automator?.finished ?? false;

/// Whether we should continue recording.
@override
bool shouldContinue() => !_finished || profile.shouldContinue();

/// Creates the [Automator] widget.
@override
Widget createWidget() {
_automator = Automator(
benchmark: benchmark,
stopWarmingUpCallback: profile.stopWarmingUp,
profile: profile,
);
return _automator!.createWidget();
}
}
Loading