diff --git a/dev/benchmarks/macrobenchmarks/.gitignore b/dev/benchmarks/macrobenchmarks/.gitignore index d3e68fd01e5..95375b10594 100644 --- a/dev/benchmarks/macrobenchmarks/.gitignore +++ b/dev/benchmarks/macrobenchmarks/.gitignore @@ -1 +1,2 @@ lib/generated_plugin_registrant.dart +devtools_memory.json diff --git a/dev/benchmarks/macrobenchmarks/lib/common.dart b/dev/benchmarks/macrobenchmarks/lib/common.dart index 9174d2102dc..bae09170853 100644 --- a/dev/benchmarks/macrobenchmarks/lib/common.dart +++ b/dev/benchmarks/macrobenchmarks/lib/common.dart @@ -8,6 +8,7 @@ const String kBackdropFilterRouteName = '/backdrop_filter'; const String kPostBackdropFilterRouteName = '/post_backdrop_filter'; const String kSimpleAnimationRouteName = '/simple_animation'; const String kPictureCacheRouteName = '/picture_cache'; +const String kLargeImageChangerRouteName = '/large_image_changer'; const String kLargeImagesRouteName = '/large_images'; const String kTextRouteName = '/text'; const String kAnimatedPlaceholderRouteName = '/animated_placeholder'; diff --git a/dev/benchmarks/macrobenchmarks/lib/main.dart b/dev/benchmarks/macrobenchmarks/lib/main.dart index 61dd0f0bcc9..5fa9a964718 100644 --- a/dev/benchmarks/macrobenchmarks/lib/main.dart +++ b/dev/benchmarks/macrobenchmarks/lib/main.dart @@ -3,18 +3,20 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:macrobenchmarks/src/color_filter_and_fade.dart'; -import 'package:macrobenchmarks/src/heavy_grid_view.dart'; -import 'package:macrobenchmarks/src/large_images.dart'; -import 'package:macrobenchmarks/src/picture_cache.dart'; import 'common.dart'; + import 'src/animated_placeholder.dart'; import 'src/backdrop_filter.dart'; +import 'src/color_filter_and_fade.dart'; import 'src/cubic_bezier.dart'; import 'src/cull_opacity.dart'; import 'src/filtered_child_animation.dart'; +import 'src/heavy_grid_view.dart'; +import 'src/large_image_changer.dart'; +import 'src/large_images.dart'; import 'src/multi_widget_construction.dart'; +import 'src/picture_cache.dart'; import 'src/post_backdrop_filter.dart'; import 'src/simple_animation.dart'; import 'src/simple_scroll.dart'; @@ -40,6 +42,7 @@ class MacrobenchmarksApp extends StatelessWidget { kPostBackdropFilterRouteName: (BuildContext context) => PostBackdropFilterPage(), kSimpleAnimationRouteName: (BuildContext context) => SimpleAnimationPage(), kPictureCacheRouteName: (BuildContext context) => PictureCachePage(), + kLargeImageChangerRouteName: (BuildContext context) => LargeImageChangerPage(), kLargeImagesRouteName: (BuildContext context) => LargeImagesPage(), kTextRouteName: (BuildContext context) => TextPage(), kAnimatedPlaceholderRouteName: (BuildContext context) => AnimatedPlaceholderPage(), @@ -162,6 +165,13 @@ class HomePage extends StatelessWidget { Navigator.pushNamed(context, kHeavyGridViewRouteName); }, ), + RaisedButton( + key: const Key(kLargeImageChangerRouteName), + child: const Text('Large Image Changer'), + onPressed: () { + Navigator.pushNamed(context, kLargeImageChangerRouteName); + }, + ), ], ), ); diff --git a/dev/benchmarks/macrobenchmarks/lib/src/large_image_changer.dart b/dev/benchmarks/macrobenchmarks/lib/src/large_image_changer.dart new file mode 100644 index 00000000000..426a07c5121 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/large_image_changer.dart @@ -0,0 +1,55 @@ +// 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'; + +/// Displays a new (from image cache's perspective) large image every 500ms. +class LargeImageChangerPage extends StatefulWidget { + @override + _LargeImageChangerState createState() => _LargeImageChangerState(); +} + +class _LargeImageChangerState extends State { + Timer _timer; + int imageIndex = 0; + ImageProvider currentImage; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + currentImage = ResizeImage( + const ExactAssetImage('assets/999x1000.png'), + width: (MediaQuery.of(context).size.width * 2).toInt() + imageIndex, + height: (MediaQuery.of(context).size.height * 2).toInt() + imageIndex, + allowUpscaling: true, + ); + _timer?.cancel(); + _timer = Timer.periodic(const Duration(seconds: 3), (Timer timer) { + currentImage.evict().then((_) { + setState(() { + imageIndex = (imageIndex + 1) % 6; + currentImage = ResizeImage( + const ExactAssetImage('assets/999x1000.png'), + width: (MediaQuery.of(context).size.width * 2).toInt() + imageIndex, + height: (MediaQuery.of(context).size.height * 2).toInt() + imageIndex, + allowUpscaling: true, + ); + }); + }); + }); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Image(image: currentImage); + } +} diff --git a/dev/benchmarks/macrobenchmarks/test_driver/large_image_changer.dart b/dev/benchmarks/macrobenchmarks/test_driver/large_image_changer.dart new file mode 100644 index 00000000000..464ffb808ed --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test_driver/large_image_changer.dart @@ -0,0 +1,23 @@ +// 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/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_driver/driver_extension.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:macrobenchmarks/common.dart'; +import 'package:macrobenchmarks/main.dart'; + +Future main() async { + enableFlutterDriverExtension(handler: (String message) async { + if (message == 'getTargetPlatform') { + return defaultTargetPlatform.toString(); + } + throw UnsupportedError('Message $message unsupported'); + }); + runApp(const MacrobenchmarksApp(initialRoute: kLargeImageChangerRouteName)); +} diff --git a/dev/benchmarks/macrobenchmarks/test_driver/large_image_changer_test.dart b/dev/benchmarks/macrobenchmarks/test_driver/large_image_changer_test.dart new file mode 100644 index 00000000000..e10b22809e7 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test_driver/large_image_changer_test.dart @@ -0,0 +1,45 @@ +// 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 20 seconds', () async { + final FlutterDriver driver = await FlutterDriver.connect(); + await driver.forceGC(); + + final String targetPlatform = await driver.requestData('getTargetPlatform'); + + Timeline timeline; + switch (targetPlatform) { + case 'TargetPlatform.iOS': + { + timeline = await driver.traceAction(() async { + await Future.delayed(const Duration(seconds: 20)); + }); + } + break; + case 'TargetPlatorm.android': + { + // Just run for 20 seconds to collect memory usage. The widget itself + // animates during this time. + await Future.delayed(const Duration(seconds: 20)); + } + break; + default: + throw UnsupportedError('Unsupported platform $targetPlatform'); + } + + if (timeline != null) { + final TimelineSummary summary = TimelineSummary.summarize(timeline); + await summary.writeSummaryToFile(fileName, pretty: true); + await summary.writeTimelineToFile(fileName, pretty: true); + } + + await driver.close(); + }); +} diff --git a/dev/devicelab/bin/tasks/large_image_changer_perf_android.dart b/dev/devicelab/bin/tasks/large_image_changer_perf_android.dart new file mode 100644 index 00000000000..0cdcb5e2e59 --- /dev/null +++ b/dev/devicelab/bin/tasks/large_image_changer_perf_android.dart @@ -0,0 +1,18 @@ +// 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/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/large_image_changer.dart', + ).run); +} diff --git a/dev/devicelab/bin/tasks/large_image_changer_perf_ios.dart b/dev/devicelab/bin/tasks/large_image_changer_perf_ios.dart new file mode 100644 index 00000000000..8bb66627fe5 --- /dev/null +++ b/dev/devicelab/bin/tasks/large_image_changer_perf_ios.dart @@ -0,0 +1,20 @@ +// 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/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.ios; + await task(PerfTest( + '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', + 'test_driver/large_image_changer.dart', + 'large_image_changer', + measureCpuGpu: true, + ).run); +} diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index b02975c2b10..e26f4e14254 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -1125,11 +1125,15 @@ class DevToolsMemoryTest { .listen((String line) { print('run stdout: $line'); final RegExpMatch match = RegExp(r'An Observatory debugger and profiler on .+ is available at: ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+)').firstMatch(line); - if (match != null) { + if (match != null && !observatoryUri.isCompleted) { observatoryUri.complete(match[1]); _observatoryUri = match[1]; } - }, onDone: () { observatoryUri.complete(null); }); + }, onDone: () { + if (!observatoryUri.isCompleted) { + observatoryUri.complete(); + } + }); _forwardStream(_runProcess.stderr, 'run stderr'); _observatoryUri = await observatoryUri.future; diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index 730849d9769..e045899ce25 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -867,6 +867,20 @@ tasks: stage: devicelab required_agent_capabilities: ["linux/android"] + large_image_changer_perf_android: + description: > + Measures memory usage when rotating through a series of large images. + stage: devicelab + required_agent_capabilities: ["linux/android"] + flaky: true + + large_image_changer_perf_ios: + description: > + Measures memory, cpu, and gpu usage when rotating through a series of large images. + stage: devicelab + required_agent_capabilities: ["mac/ios"] + flaky: true + animated_placeholder_perf: description: > Measures frame build and rasterizer times, as well as frame build counts