diff --git a/dev/benchmarks/macrobenchmarks/lib/common.dart b/dev/benchmarks/macrobenchmarks/lib/common.dart index 746d824a8b4f..7695b53397f9 100644 --- a/dev/benchmarks/macrobenchmarks/lib/common.dart +++ b/dev/benchmarks/macrobenchmarks/lib/common.dart @@ -5,3 +5,4 @@ const String kCullOpacityRouteName = '/cull_opacity'; const String kCubicBezierRouteName = '/cubic_bezier'; const String kBackdropFilterRouteName = '/backdrop_filter'; +const String kSimpleAnimationRouteName = '/simple_animation'; diff --git a/dev/benchmarks/macrobenchmarks/lib/main.dart b/dev/benchmarks/macrobenchmarks/lib/main.dart index 5f87dfda45b3..267b0f2b360a 100644 --- a/dev/benchmarks/macrobenchmarks/lib/main.dart +++ b/dev/benchmarks/macrobenchmarks/lib/main.dart @@ -8,6 +8,7 @@ import 'common.dart'; import 'src/backdrop_filter.dart'; import 'src/cubic_bezier.dart'; import 'src/cull_opacity.dart'; +import 'src/simple_animation.dart'; const String kMacrobenchmarks ='Macrobenchmarks'; @@ -24,6 +25,7 @@ class MacrobenchmarksApp extends StatelessWidget { kCullOpacityRouteName: (BuildContext context) => CullOpacityPage(), kCubicBezierRouteName: (BuildContext context) => CubicBezierPage(), kBackdropFilterRouteName: (BuildContext context) => BackdropFilterPage(), + kSimpleAnimationRouteName: (BuildContext conttext) => SimpleAnimationPage(), }, ); } @@ -39,24 +41,31 @@ class HomePage extends StatelessWidget { RaisedButton( key: const Key(kCullOpacityRouteName), child: const Text('Cull opacity'), - onPressed: (){ + onPressed: () { Navigator.pushNamed(context, kCullOpacityRouteName); }, ), RaisedButton( key: const Key(kCubicBezierRouteName), child: const Text('Cubic Bezier'), - onPressed: (){ + onPressed: () { Navigator.pushNamed(context, kCubicBezierRouteName); }, ), RaisedButton( key: const Key(kBackdropFilterRouteName), child: const Text('Backdrop Filter'), - onPressed: (){ + onPressed: () { Navigator.pushNamed(context, kBackdropFilterRouteName); }, ), + RaisedButton( + key: const Key(kSimpleAnimationRouteName), + child: const Text('Simple Animation'), + onPressed: () { + Navigator.pushNamed(context, kSimpleAnimationRouteName); + }, + ), ], ), ); diff --git a/dev/benchmarks/macrobenchmarks/lib/src/backdrop_filter.dart b/dev/benchmarks/macrobenchmarks/lib/src/backdrop_filter.dart index 5e9311715592..57ceb4105bc2 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/backdrop_filter.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/backdrop_filter.dart @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium 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:ui'; import 'package:flutter/material.dart'; diff --git a/dev/benchmarks/macrobenchmarks/lib/src/simple_animation.dart b/dev/benchmarks/macrobenchmarks/lib/src/simple_animation.dart new file mode 100644 index 000000000000..7182bedb1df5 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/simple_animation.dart @@ -0,0 +1,12 @@ +// Copyright 2019 The Chromium 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 SimpleAnimationPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return const Center(child: LinearProgressIndicator()); + } +} diff --git a/dev/benchmarks/macrobenchmarks/test_driver/simple_animation_perf.dart b/dev/benchmarks/macrobenchmarks/test_driver/simple_animation_perf.dart new file mode 100644 index 000000000000..80361f370446 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test_driver/simple_animation_perf.dart @@ -0,0 +1,11 @@ +// Copyright 2019 The Chromium 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/simple_animation_perf_test.dart b/dev/benchmarks/macrobenchmarks/test_driver/simple_animation_perf_test.dart new file mode 100644 index 000000000000..494f2c6443cc --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test_driver/simple_animation_perf_test.dart @@ -0,0 +1,14 @@ +// Copyright 2019 The Chromium 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( + 'simple_animation_perf', + kSimpleAnimationRouteName, + ); +} diff --git a/dev/devicelab/bin/tasks/backdrop_filter_perf_ios__timeline_summary.dart b/dev/devicelab/bin/tasks/backdrop_filter_perf_ios__timeline_summary.dart index 840c7754165c..20dc420f2e14 100644 --- a/dev/devicelab/bin/tasks/backdrop_filter_perf_ios__timeline_summary.dart +++ b/dev/devicelab/bin/tasks/backdrop_filter_perf_ios__timeline_summary.dart @@ -10,5 +10,5 @@ import 'package:flutter_devicelab/framework/framework.dart'; Future main() async { deviceOperatingSystem = DeviceOperatingSystem.ios; - await task(createBackdropFilterPerfTest()); + await task(createBackdropFilterPerfTest(needsMeasureCpuGpu: true)); } diff --git a/dev/devicelab/bin/tasks/simple_animation_perf_ios.dart b/dev/devicelab/bin/tasks/simple_animation_perf_ios.dart new file mode 100644 index 000000000000..3982569aa3cc --- /dev/null +++ b/dev/devicelab/bin/tasks/simple_animation_perf_ios.dart @@ -0,0 +1,13 @@ +// Copyright 2019 The Chromium 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/tasks/perf_tests.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.ios; + await task(createSimpleAnimationPerfTest(needsMeasureCpuGpu: true)); +} diff --git a/dev/devicelab/lib/framework/utils.dart b/dev/devicelab/lib/framework/utils.dart index e5f3d34b85d0..307de3b77a35 100644 --- a/dev/devicelab/lib/framework/utils.dart +++ b/dev/devicelab/lib/framework/utils.dart @@ -622,3 +622,46 @@ void checkFileExists(String file) { throw FileSystemException('Expected file to exit.', file); } } + +void _checkExitCode(int code) { + if (code != 0) { + throw Exception( + 'Unexpected exit code = $code!', + ); + } +} + +Future _execAndCheck(String executable, List args) async { + _checkExitCode(await exec(executable, args)); +} + +// Measure the CPU/GPU percentage for [duration] while a Flutter app is running +// on an iOS device (e.g., right after a Flutter driver test has finished, which +// doesn't close the Flutter app, and the Flutter app has an indefinite +// animation). The return should have a format like the following json +// ``` +// {"gpu_percentage":12.6,"cpu_percentage":18.15} +// ``` +Future> measureIosCpuGpu({ + Duration duration = const Duration(seconds: 10), + String deviceId, +}) async { + await _execAndCheck('pub', [ + 'global', + 'activate', + 'gauge', + '0.1.4', + ]); + + await _execAndCheck('pub', [ + 'global', + 'run', + 'gauge', + 'ioscpugpu', + 'new', + if (deviceId != null) ...['-w', deviceId], + '-l', + '${duration.inMilliseconds}', + ]); + return json.decode(file('$cwd/result.json').readAsStringSync()); +} diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index 88c6f81eb626..9aa2bf5626ca 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -54,11 +54,21 @@ TaskFunction createCubicBezierPerfTest() { ).run; } -TaskFunction createBackdropFilterPerfTest() { +TaskFunction createBackdropFilterPerfTest({bool needsMeasureCpuGpu = false}) { return PerfTest( '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', 'test_driver/backdrop_filter_perf.dart', 'backdrop_filter_perf', + needsMeasureCpuGPu: needsMeasureCpuGpu, + ).run; +} + +TaskFunction createSimpleAnimationPerfTest({bool needsMeasureCpuGpu = false}) { + return PerfTest( + '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', + 'test_driver/simple_animation_perf.dart', + 'simple_animation_perf', + needsMeasureCpuGPu: needsMeasureCpuGpu, ).run; } @@ -168,12 +178,18 @@ class StartupTest { /// Measures application runtime performance, specifically per-frame /// performance. class PerfTest { - const PerfTest(this.testDirectory, this.testTarget, this.timelineFileName); + const PerfTest( + this.testDirectory, + this.testTarget, + this.timelineFileName, + {this.needsMeasureCpuGPu = false}); final String testDirectory; final String testTarget; final String timelineFileName; + final bool needsMeasureCpuGPu; + Future run() { return inDirectory(testDirectory, () async { final Device device = await devices.workingDevice; @@ -202,6 +218,12 @@ class PerfTest { ); } + if (needsMeasureCpuGPu) { + await inDirectory('$testDirectory/build', () async { + data.addAll(await measureIosCpuGpu(deviceId: deviceId)); + }); + } + return TaskResult.success(data, benchmarkScoreKeys: [ 'average_frame_build_time_millis', 'worst_frame_build_time_millis', @@ -213,6 +235,8 @@ class PerfTest { 'missed_frame_rasterizer_budget_count', '90th_percentile_frame_rasterizer_time_millis', '99th_percentile_frame_rasterizer_time_millis', + if (needsMeasureCpuGPu) 'cpu_percentage', + if (needsMeasureCpuGPu) 'gpu_percentage', ]); }); } diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index 46e63dff272a..f6612bf6112c 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -535,6 +535,12 @@ tasks: stage: devicelab_ios required_agent_capabilities: ["mac/ios"] + simple_animation_perf_ios: + description: > + Measure CPU/GPU usage percentages of a simple animation. + stage: devicelab_ios + required_agent_capabilities: ["mac/ios"] + smoke_catalina_start_up_ios: description: > A smoke test that runs on macOS Catalina, which is a clone of the Gallery startup latency test.