Reland "Measure iOS CPU/GPU percentage #41426" (#41578)

This reverts commit baea9bf7cc7447b5ec6e6d377a68b17e79cb9ccf.

Additionally,   we let the test run on mac8 with iphonexs because the test won't run on Xcode 10.1 (mac3-7). Hence we force it to run on mac8 which currently has Xcode 10.2.
diff --git a/dev/benchmarks/macrobenchmarks/lib/common.dart b/dev/benchmarks/macrobenchmarks/lib/common.dart
index 746d824..7695b53 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 5f87dfd..267b0f2 100644
--- a/dev/benchmarks/macrobenchmarks/lib/main.dart
+++ b/dev/benchmarks/macrobenchmarks/lib/main.dart
@@ -8,6 +8,7 @@
 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 @@
         kCullOpacityRouteName: (BuildContext context) => CullOpacityPage(),
         kCubicBezierRouteName: (BuildContext context) => CubicBezierPage(),
         kBackdropFilterRouteName: (BuildContext context) => BackdropFilterPage(),
+        kSimpleAnimationRouteName: (BuildContext conttext) => SimpleAnimationPage(),
       },
     );
   }
@@ -39,24 +41,31 @@
           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 5e93117..57ceb41 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 0000000..7182bed
--- /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 0000000..80361f3
--- /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 0000000..494f2c6
--- /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 840c775..20dc420 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 @@
 
 Future<void> 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 0000000..3982569
--- /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<void> 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 e5f3d34..307de3b 100644
--- a/dev/devicelab/lib/framework/utils.dart
+++ b/dev/devicelab/lib/framework/utils.dart
@@ -622,3 +622,46 @@
     throw FileSystemException('Expected file to exit.', file);
   }
 }
+
+void _checkExitCode(int code) {
+  if (code != 0) {
+    throw Exception(
+      'Unexpected exit code = $code!',
+    );
+  }
+}
+
+Future<void> _execAndCheck(String executable, List<String> 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<Map<String, dynamic>> measureIosCpuGpu({
+    Duration duration = const Duration(seconds: 10),
+    String deviceId,
+}) async {
+  await _execAndCheck('pub', <String>[
+    'global',
+    'activate',
+    'gauge',
+    '0.1.4',
+  ]);
+
+  await _execAndCheck('pub', <String>[
+    'global',
+    'run',
+    'gauge',
+    'ioscpugpu',
+    'new',
+    if (deviceId != null) ...<String>['-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 88c6f81..9aa2bf5 100644
--- a/dev/devicelab/lib/tasks/perf_tests.dart
+++ b/dev/devicelab/lib/tasks/perf_tests.dart
@@ -54,11 +54,21 @@
   ).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 @@
 /// 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<TaskResult> run() {
     return inDirectory<TaskResult>(testDirectory, () async {
       final Device device = await devices.workingDevice;
@@ -202,6 +218,12 @@
         );
       }
 
+      if (needsMeasureCpuGPu) {
+        await inDirectory<void>('$testDirectory/build', () async {
+          data.addAll(await measureIosCpuGpu(deviceId: deviceId));
+        });
+      }
+
       return TaskResult.success(data, benchmarkScoreKeys: <String>[
         'average_frame_build_time_millis',
         'worst_frame_build_time_millis',
@@ -213,6 +235,8 @@
         '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 46e63df..9f75f2d 100644
--- a/dev/devicelab/manifest.yaml
+++ b/dev/devicelab/manifest.yaml
@@ -535,6 +535,12 @@
     stage: devicelab_ios
     required_agent_capabilities: ["mac/ios"]
 
+  simple_animation_perf_iphonexs:
+    description: >
+      Measure CPU/GPU usage percentages of a simple animation.
+    stage: devicelab_ios
+    required_agent_capabilities: ["mac/iphonexs"]
+
   smoke_catalina_start_up_ios:
     description: >
       A smoke test that runs on macOS Catalina, which is a clone of the Gallery startup latency test.