diff --git a/dev/benchmarks/macrobenchmarks/lib/common.dart b/dev/benchmarks/macrobenchmarks/lib/common.dart index 34297c3e05e..3563a09ddc2 100644 --- a/dev/benchmarks/macrobenchmarks/lib/common.dart +++ b/dev/benchmarks/macrobenchmarks/lib/common.dart @@ -14,5 +14,6 @@ const String kAnimatedPlaceholderRouteName = '/animated_placeholder'; const String kColorFilterAndFadeRouteName = '/color_filter_and_fade'; const String kFadingChildAnimationRouteName = '/fading_child_animation'; const String kImageFilteredTransformAnimationRouteName = '/imagefiltered_transform_animation'; +const String kMultiWidgetConstructionRouteName = '/multi_widget_construction'; const String kScrollableName = '/macrobenchmark_listview'; diff --git a/dev/benchmarks/macrobenchmarks/lib/main.dart b/dev/benchmarks/macrobenchmarks/lib/main.dart index 1b2ae76fc7f..da9e6968851 100644 --- a/dev/benchmarks/macrobenchmarks/lib/main.dart +++ b/dev/benchmarks/macrobenchmarks/lib/main.dart @@ -13,6 +13,7 @@ import 'src/backdrop_filter.dart'; import 'src/cubic_bezier.dart'; import 'src/cull_opacity.dart'; import 'src/filtered_child_animation.dart'; +import 'src/multi_widget_construction.dart'; import 'src/post_backdrop_filter.dart'; import 'src/simple_animation.dart'; import 'src/text.dart'; @@ -43,6 +44,7 @@ class MacrobenchmarksApp extends StatelessWidget { kColorFilterAndFadeRouteName: (BuildContext context) => ColorFilterAndFadePage(), kFadingChildAnimationRouteName: (BuildContext context) => const FilteredChildAnimationPage(FilterType.opacity), kImageFilteredTransformAnimationRouteName: (BuildContext context) => const FilteredChildAnimationPage(FilterType.rotateFilter), + kMultiWidgetConstructionRouteName: (BuildContext context) => const MultiWidgetConstructTable(10, 20), }, ); } @@ -136,10 +138,10 @@ class HomePage extends StatelessWidget { }, ), RaisedButton( - key: const Key(kImageFilteredTransformAnimationRouteName), - child: const Text('ImageFiltered Transform Animation'), + key: const Key(kMultiWidgetConstructionRouteName), + child: const Text('Widget Construction and Destruction'), onPressed: () { - Navigator.pushNamed(context, kImageFilteredTransformAnimationRouteName); + Navigator.pushNamed(context, kMultiWidgetConstructionRouteName); }, ), ], diff --git a/dev/benchmarks/macrobenchmarks/lib/src/multi_widget_construction.dart b/dev/benchmarks/macrobenchmarks/lib/src/multi_widget_construction.dart new file mode 100644 index 00000000000..b014f995b65 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/multi_widget_construction.dart @@ -0,0 +1,123 @@ +// Copyright 2014 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'; + +class MultiWidgetConstructTable extends StatefulWidget { + const MultiWidgetConstructTable(this.column, this.row, {Key key}) + : super(key: key); + + final int column; + final int row; + + @override + _MultiWidgetConstructTableState createState() => + _MultiWidgetConstructTableState(); +} + +class _MultiWidgetConstructTableState extends State + with SingleTickerProviderStateMixin { + static const List colorList = [ + Colors.pink, Colors.red, Colors.deepOrange, Colors.orange, Colors.amber, + Colors.yellow, Colors.lime, Colors.lightGreen, Colors.green, Colors.teal, + Colors.cyan, Colors.lightBlue, Colors.blue, Colors.indigo, Colors.purple, + ]; + int counter = 0; + Color baseColor = colorList[0][900]; + + AnimationController controller; + CurvedAnimation curve; + + @override + void initState() { + super.initState(); + controller = AnimationController( + vsync: this, duration: const Duration(milliseconds: 10000)); + curve = CurvedAnimation(parent: controller, curve: Curves.linear) + ..addListener(() { + final double colorPosition = curve.value; + final int c1Position = (colorPosition * (colorList.length + 1)).floor(); + final Color c1 = colorList[c1Position % colorList.length][900]; + final Color c2 = colorList[(c1Position + 1) % colorList.length][900]; + setState(() { + baseColor = Color.lerp( + c1, c2, colorPosition * (colorList.length + 1) - c1Position); + }); + }) + ..addStatusListener((AnimationStatus state) { + if (state == AnimationStatus.completed) { + controller.reverse(); + } else if (state == AnimationStatus.dismissed) { + controller.reset(); + controller.forward(); + } + }); + + controller.forward(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final int totalLength = widget.row * widget.column; + final int widgetCounter = counter * totalLength; + final double height = MediaQuery.of(context).size.height / widget.column; + counter++; + return Scaffold( + body: Table( + children: List.generate( + widget.row, + (int row) => TableRow( + children: List.generate( + widget.column, + (int column) { + final int label = row * widget.column + column; + return counter % 2 == 0 + ? Container( + // This key forces rebuilding the element + key: ValueKey(widgetCounter + label), + color: Color.lerp( + Colors.white, baseColor, label / totalLength), + child: Text('${widgetCounter + label}'), + constraints: BoxConstraints.expand(height: height), + ) + : MyContainer( + // This key forces rebuilding the element + key: ValueKey(widgetCounter + label), + color: Color.lerp( + Colors.white, baseColor, label / totalLength), + child: Text('${widgetCounter + label}'), + constraints: BoxConstraints.expand(height: height), + ); + }, + ), + ), + ), + ), + ); + } +} + +// This class is intended to break the original Widget tree +class MyContainer extends StatelessWidget { + const MyContainer({this.color, this.child, this.constraints, Key key}) + : super(key: key); + final Color color; + final Widget child; + final BoxConstraints constraints; + + @override + Widget build(BuildContext context) { + return Container( + color: color, + child: child, + constraints: constraints, + ); + } +} diff --git a/dev/benchmarks/macrobenchmarks/test_driver/multi_widget_construction_perf.dart b/dev/benchmarks/macrobenchmarks/test_driver/multi_widget_construction_perf.dart new file mode 100644 index 00000000000..8169d132b69 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test_driver/multi_widget_construction_perf.dart @@ -0,0 +1,11 @@ +// Copyright 2014 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_driver/driver_extension.dart'; +import 'package:macrobenchmarks/main.dart' as app; + +void main() { + enableFlutterDriverExtension(); + app.main(); +} diff --git a/dev/benchmarks/macrobenchmarks/test_driver/multi_widget_construction_perf_test.dart b/dev/benchmarks/macrobenchmarks/test_driver/multi_widget_construction_perf_test.dart new file mode 100644 index 00000000000..7b4b00de3ff --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test_driver/multi_widget_construction_perf_test.dart @@ -0,0 +1,17 @@ +// Copyright 2014 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:macrobenchmarks/common.dart'; + +import 'util.dart'; + +void main() { + macroPerfTest( + 'multi_widget_construction_perf', + kMultiWidgetConstructionRouteName, + pageDelay: const Duration(seconds: 1), + duration: const Duration(seconds: 10), + timeout: const Duration(seconds: 45), + ); +} diff --git a/dev/devicelab/bin/tasks/multi_widget_construction_perf__timeline_summary.dart b/dev/devicelab/bin/tasks/multi_widget_construction_perf__timeline_summary.dart new file mode 100644 index 00000000000..77bc67c900e --- /dev/null +++ b/dev/devicelab/bin/tasks/multi_widget_construction_perf__timeline_summary.dart @@ -0,0 +1,14 @@ +// Copyright 2014 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 'dart:async'; + +import 'package:flutter_devicelab/tasks/perf_tests.dart'; +import 'package:flutter_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createsMultiWidgetConstructPerfTest()); +} diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index 98b007a6944..d01778091b9 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -242,6 +242,14 @@ TaskFunction createImageFilteredTransformAnimationPerfTest() { ).run; } +TaskFunction createsMultiWidgetConstructPerfTest() { + return PerfTest( + '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', + 'test_driver/multi_widget_construction_perf.dart', + 'multi_widget_construction_perf', + ).run; +} + /// Measure application startup performance. class StartupTest { const StartupTest(this.testDirectory, { this.reportMetrics = true }); diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index 8021231872e..7f67fb9344b 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -156,6 +156,12 @@ tasks: stage: devicelab required_agent_capabilities: ["mac/android"] + multi_widget_construction_perf__timeline_summary: + description: > + Measures the runtime performance of constructing and destructing widgets on Android. + stage: devicelab + required_agent_capabilities: ["linux/android"] + picture_cache_perf__timeline_summary: description: > Measures the runtime performance of raster caching many pictures on Android.