From 0866005f709fd2c0ea9df87f718887ba39f9ef3a Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 27 Apr 2021 10:39:02 -0700 Subject: [PATCH] Add benchmark for number of GCs in animated GIF (#81240) --- .../macrobenchmarks/lib/common.dart | 1 + dev/benchmarks/macrobenchmarks/lib/main.dart | 9 +++++ .../lib/src/animated_image.dart | 30 ++++++++++++++ dev/benchmarks/macrobenchmarks/pubspec.yaml | 1 + .../test_driver/animated_image.dart | 35 ++++++++++++++++ .../test_driver/animated_image_test.dart | 24 +++++++++++ .../bin/tasks/animated_image_gc_perf.dart | 16 ++++++++ .../flutter_gallery/lib/demo/images_demo.dart | 2 +- dev/prod_builders.json | 6 +++ .../lib/src/driver/timeline_summary.dart | 16 ++++++++ .../src/real_tests/timeline_summary_test.dart | 40 +++++++++++++++++++ 11 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 dev/benchmarks/macrobenchmarks/lib/src/animated_image.dart create mode 100644 dev/benchmarks/macrobenchmarks/test_driver/animated_image.dart create mode 100644 dev/benchmarks/macrobenchmarks/test_driver/animated_image_test.dart create mode 100644 dev/devicelab/bin/tasks/animated_image_gc_perf.dart diff --git a/dev/benchmarks/macrobenchmarks/lib/common.dart b/dev/benchmarks/macrobenchmarks/lib/common.dart index c867a62b0d6..5665e607b3f 100644 --- a/dev/benchmarks/macrobenchmarks/lib/common.dart +++ b/dev/benchmarks/macrobenchmarks/lib/common.dart @@ -21,6 +21,7 @@ const String kHeavyGridViewRouteName = '/heavy_gridview'; const String kSimpleScrollRouteName = '/simple_scroll'; const String kStackSizeRouteName = '/stack_size'; const String kAnimationWithMicrotasksRouteName = '/animation_with_microtasks'; +const String kAnimatedImageRouteName = '/animated_image'; const String kScrollableName = '/macrobenchmark_listview'; diff --git a/dev/benchmarks/macrobenchmarks/lib/main.dart b/dev/benchmarks/macrobenchmarks/lib/main.dart index 34d063af659..645c43340e5 100644 --- a/dev/benchmarks/macrobenchmarks/lib/main.dart +++ b/dev/benchmarks/macrobenchmarks/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'common.dart'; +import 'src/animated_image.dart'; import 'src/animated_placeholder.dart'; import 'src/animation_with_microtasks.dart'; import 'src/backdrop_filter.dart'; @@ -58,6 +59,7 @@ class MacrobenchmarksApp extends StatelessWidget { kSimpleScrollRouteName: (BuildContext context) => const SimpleScroll(), kStackSizeRouteName: (BuildContext context) => const StackSizePage(), kAnimationWithMicrotasksRouteName: (BuildContext context) => const AnimationWithMicrotasks(), + kAnimatedImageRouteName: (BuildContext context) => const AnimatedImagePage(), }, ); } @@ -201,6 +203,13 @@ class HomePage extends StatelessWidget { Navigator.pushNamed(context, kAnimationWithMicrotasksRouteName); }, ), + ElevatedButton( + key: const Key(kAnimatedImageRouteName), + child: const Text('Animated Image'), + onPressed: () { + Navigator.pushNamed(context, kAnimatedImageRouteName); + }, + ), ], ), ); diff --git a/dev/benchmarks/macrobenchmarks/lib/src/animated_image.dart b/dev/benchmarks/macrobenchmarks/lib/src/animated_image.dart new file mode 100644 index 00000000000..fc68f9eec4f --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/animated_image.dart @@ -0,0 +1,30 @@ +// 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 AnimatedImagePage extends StatelessWidget { + const AnimatedImagePage({Key key, this.onFrame}) : super(key: key); + + final ValueChanged onFrame; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Animated Image'), + ), + body: Image.asset( + 'animated_images/animated_flutter_lgtm.gif', + package: 'flutter_gallery_assets', + frameBuilder: (BuildContext context, Widget child, int/*?*/ frame, bool syncCall) { + if (onFrame != null && frame != null) { + onFrame(frame); + } + return child; + }, + ), + ); + } +} diff --git a/dev/benchmarks/macrobenchmarks/pubspec.yaml b/dev/benchmarks/macrobenchmarks/pubspec.yaml index 6a6ae82be0c..de319946d4e 100644 --- a/dev/benchmarks/macrobenchmarks/pubspec.yaml +++ b/dev/benchmarks/macrobenchmarks/pubspec.yaml @@ -81,6 +81,7 @@ dev_dependencies: flutter: uses-material-design: true assets: + - packages/flutter_gallery_assets/animated_images/animated_flutter_lgtm.gif - packages/flutter_gallery_assets/food/butternut_squash_soup.png - packages/flutter_gallery_assets/food/cherry_pie.png - assets/999x1000.png diff --git a/dev/benchmarks/macrobenchmarks/test_driver/animated_image.dart b/dev/benchmarks/macrobenchmarks/test_driver/animated_image.dart new file mode 100644 index 00000000000..e8b10915fcb --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test_driver/animated_image.dart @@ -0,0 +1,35 @@ +// 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/material.dart'; +import 'package:flutter_driver/driver_extension.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:macrobenchmarks/src/animated_image.dart'; + +/// This test is slightly different than most of the other tests in this +/// application, in that it directly instantiates the page we care about and +/// passes a callback. This way, we can make sure to consistently wait for a +/// set number of image frames to render. +Future main() async { + final Completer waiter = Completer(); + enableFlutterDriverExtension(handler: (String request) async { + if (request != 'waitForAnimation') { + throw UnsupportedError('Unrecognized request $request'); + } + await waiter.future; + return 'done'; + }); + runApp(MaterialApp( + home: AnimatedImagePage( + onFrame: (int frameNumber) { + if (frameNumber == 250) { + waiter.complete(); + } + }, + ), + )); +} diff --git a/dev/benchmarks/macrobenchmarks/test_driver/animated_image_test.dart b/dev/benchmarks/macrobenchmarks/test_driver/animated_image_test.dart new file mode 100644 index 00000000000..f0e62af443b --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test_driver/animated_image_test.dart @@ -0,0 +1,24 @@ +// 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/flutter_driver.dart'; +import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; + +Future main() async { + const String fileName = 'large_image_changer'; + + test('Animate for 250 frames', () async { + final FlutterDriver driver = await FlutterDriver.connect(); + await driver.forceGC(); + + + final Timeline timeline = await driver.traceAction(() async { + await driver.requestData('waitForAnimation'); + }); + final TimelineSummary summary = TimelineSummary.summarize(timeline); + await summary.writeTimelineToFile(fileName, pretty: true); + + await driver.close(); + }); +} diff --git a/dev/devicelab/bin/tasks/animated_image_gc_perf.dart b/dev/devicelab/bin/tasks/animated_image_gc_perf.dart new file mode 100644 index 00000000000..7371f6905fc --- /dev/null +++ b/dev/devicelab/bin/tasks/animated_image_gc_perf.dart @@ -0,0 +1,16 @@ +// 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_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; +import 'package:flutter_devicelab/tasks/perf_tests.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(DevToolsMemoryTest( + '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', + 'test_driver/animated_image.dart', + ).run); +} diff --git a/dev/integration_tests/flutter_gallery/lib/demo/images_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/images_demo.dart index a3f45c40061..b190b1d63c1 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/images_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/images_demo.dart @@ -34,7 +34,7 @@ class ImagesDemo extends StatelessWidget { exampleCodeTag: 'animated_image', demoWidget: Semantics( label: 'Example of animated GIF', - child:Image.asset( + child: Image.asset( 'animated_images/animated_flutter_lgtm.gif', package: 'flutter_gallery_assets', ), diff --git a/dev/prod_builders.json b/dev/prod_builders.json index 9e6a57d31c7..b05b9cd2893 100644 --- a/dev/prod_builders.json +++ b/dev/prod_builders.json @@ -324,6 +324,12 @@ "task_name": "linux_large_image_changer_perf_android", "flaky": false }, + { + "name": "Linux animated_image_gc_perf", + "repo": "flutter", + "task_name": "animated_image_gc_perf", + "flaky": true + }, { "name": "Linux linux_chrome_dev_mode", "repo": "flutter", diff --git a/packages/flutter_driver/lib/src/driver/timeline_summary.dart b/packages/flutter_driver/lib/src/driver/timeline_summary.dart index e781ce21e79..979d17a4489 100644 --- a/packages/flutter_driver/lib/src/driver/timeline_summary.dart +++ b/packages/flutter_driver/lib/src/driver/timeline_summary.dart @@ -95,6 +95,20 @@ class TimelineSummary { /// The total number of rasterizer cycles recorded in the timeline. int countRasterizations() => _extractGpuRasterizerDrawDurations().length; + /// The total number of old generation garbage collections recorded in the timeline. + int oldGenerationGarbageCollections() { + return _timeline.events!.where((TimelineEvent event) { + return event.category == 'GC' && event.name == 'CollectOldGeneration'; + }).length; + } + + /// The total number of new generation garbage collections recorded in the timeline. + int newGenerationGarbageCollections() { + return _timeline.events!.where((TimelineEvent event) { + return event.category == 'GC' && event.name == 'CollectNewGeneration'; + }).length; + } + /// Encodes this summary as JSON. /// /// Data ends with "_time_millis" means time in milliseconds and numbers in @@ -176,6 +190,8 @@ class TimelineSummary { 'missed_frame_rasterizer_budget_count': computeMissedFrameRasterizerBudgetCount(), 'frame_count': countFrames(), 'frame_rasterizer_count': countRasterizations(), + 'new_gen_gc_count': newGenerationGarbageCollections(), + 'old_gen_gc_count': oldGenerationGarbageCollections(), 'frame_build_times': _extractFrameDurations() .map((Duration duration) => duration.inMicroseconds) .toList(), diff --git a/packages/flutter_driver/test/src/real_tests/timeline_summary_test.dart b/packages/flutter_driver/test/src/real_tests/timeline_summary_test.dart index 8a1f53baebc..a62b20413ed 100644 --- a/packages/flutter_driver/test/src/real_tests/timeline_summary_test.dart +++ b/packages/flutter_driver/test/src/real_tests/timeline_summary_test.dart @@ -95,6 +95,38 @@ void main() { 'ts': timeStamp, }; + List> newGenGC(int count) => List>.filled( + count, + { + 'name': 'CollectNewGeneration', + 'cat': 'GC', + 'tid': 19695, + 'pid': 19650, + 'ts': 358849612473, + 'tts': 476761, + 'ph': 'B', + 'args': { + 'isolateGroupId': 'isolateGroups/10824099774666259225', + }, + }, + ); + + List> oldGenGC(int count) => List>.filled( + count, + { + 'name': 'CollectOldGeneration', + 'cat': 'GC', + 'tid': 19695, + 'pid': 19650, + 'ts': 358849612473, + 'tts': 476761, + 'ph': 'B', + 'args': { + 'isolateGroupId': 'isolateGroups/10824099774666259225', + }, + }, + ); + List> rasterizeTimeSequenceInMillis(List sequence) { final List> result = >[]; int t = 0; @@ -388,6 +420,8 @@ void main() { begin(1000), end(19000), begin(19000), end(29000), begin(29000), end(49000), + ...newGenGC(4), + ...oldGenGC(5), frameBegin(1000), frameEnd(18000), frameBegin(19000), frameEnd(28000), frameBegin(29000), frameEnd(48000), @@ -405,6 +439,8 @@ void main() { 'missed_frame_rasterizer_budget_count': 2, 'frame_count': 3, 'frame_rasterizer_count': 3, + 'new_gen_gc_count': 4, + 'old_gen_gc_count': 5, 'frame_build_times': [17000, 9000, 19000], 'frame_rasterizer_times': [18000, 10000, 20000], 'frame_begin_times': [0, 18000, 28000], @@ -475,6 +511,8 @@ void main() { lagBegin(1000, 4), lagEnd(2000, 4), lagBegin(1200, 12), lagEnd(2400, 12), lagBegin(4200, 8), lagEnd(9400, 8), + ...newGenGC(4), + ...oldGenGC(5), cpuUsage(5000, 20), cpuUsage(5010, 60), memoryUsage(6000, 20, 40), memoryUsage(6100, 30, 45), platformVsync(7000), vsyncCallback(7500), @@ -494,6 +532,8 @@ void main() { 'missed_frame_rasterizer_budget_count': 2, 'frame_count': 3, 'frame_rasterizer_count': 3, + 'new_gen_gc_count': 4, + 'old_gen_gc_count': 5, 'frame_build_times': [17000, 9000, 19000], 'frame_rasterizer_times': [18000, 10000, 20000], 'frame_begin_times': [0, 18000, 28000],