gallery.dart 6.86 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert';
import 'dart:io';
7
import 'dart:math' as math;
8

9
import '../framework/devices.dart';
10
import '../framework/framework.dart';
11
import '../framework/task_result.dart';
12 13
import '../framework/utils.dart';

14 15
TaskFunction createGalleryTransitionTest({bool semanticsEnabled = false}) {
  return GalleryTransitionTest(semanticsEnabled: semanticsEnabled);
16 17
}

18
TaskFunction createGalleryTransitionE2ETest({bool semanticsEnabled = false}) {
19
  return GalleryTransitionTest(
20 21 22
    testFile: semanticsEnabled
        ? 'transitions_perf_e2e_with_semantics'
        : 'transitions_perf_e2e',
23 24 25
    needFullTimeline: false,
    timelineSummaryFile: 'e2e_perf_summary',
    transitionDurationFile: null,
26
    timelineTraceFile: null,
27 28 29 30
    driverFile: 'transitions_perf_e2e_test',
  );
}

31
TaskFunction createGalleryTransitionHybridTest({bool semanticsEnabled = false}) {
32 33 34 35 36 37 38 39
  return GalleryTransitionTest(
    semanticsEnabled: semanticsEnabled,
    driverFile: semanticsEnabled
        ? 'transitions_perf_hybrid_with_semantics_test'
        : 'transitions_perf_hybrid_test',
  );
}

40 41 42
class GalleryTransitionTest {

  GalleryTransitionTest({
43 44 45 46
    this.semanticsEnabled = false,
    this.testFile = 'transitions_perf',
    this.needFullTimeline = true,
    this.timelineSummaryFile = 'transitions.timeline_summary',
47
    this.timelineTraceFile = 'transitions.timeline',
48 49
    this.transitionDurationFile = 'transition_durations.timeline',
    this.driverFile,
50 51
    this.measureCpuGpu = true,
    this.measureMemory = true,
52
  });
53 54

  final bool semanticsEnabled;
55
  final bool needFullTimeline;
56 57
  final bool measureCpuGpu;
  final bool measureMemory;
58 59
  final String testFile;
  final String timelineSummaryFile;
60 61 62
  final String? timelineTraceFile;
  final String? transitionDurationFile;
  final String? driverFile;
63

64 65 66 67 68 69
  Future<TaskResult> call() async {
    final Device device = await devices.workingDevice;
    await device.unlock();
    final String deviceId = device.deviceId;
    final Directory galleryDirectory = dir('${flutterDirectory.path}/dev/integration_tests/flutter_gallery');
    await inDirectory<void>(galleryDirectory, () async {
70
      String? applicationBinaryPath;
71 72 73 74 75 76 77
      if (deviceOperatingSystem == DeviceOperatingSystem.android) {
        section('BUILDING APPLICATION');
        await flutter(
          'build',
          options: <String>[
            'apk',
            '--no-android-gradle-daemon',
78 79 80
            '--profile',
            '-t',
            'test_driver/$testFile.dart',
81 82 83 84 85
            '--target-platform',
            'android-arm,android-arm64',
          ],
        );
        applicationBinaryPath = 'build/app/outputs/flutter-apk/app-profile.apk';
86
      }
87

88 89 90 91 92
      final String testDriver = driverFile ?? (semanticsEnabled
          ? '${testFile}_with_semantics_test'
          : '${testFile}_test');
      section('DRIVE START');
      await flutter('drive', options: <String>[
93
        '--no-dds',
94
        '--profile',
95 96
        if (needFullTimeline)
          '--trace-startup',
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
        if (applicationBinaryPath != null)
          '--use-application-binary=$applicationBinaryPath'
        else
          ...<String>[
            '-t',
            'test_driver/$testFile.dart',
          ],
        '--driver',
        'test_driver/$testDriver.dart',
        '-d',
        deviceId,
      ]);
    });

    final String testOutputDirectory = Platform.environment['FLUTTER_TEST_OUTPUTS_DIR'] ?? '${galleryDirectory.path}/build';
112
    final Map<String, dynamic> summary = json.decode(
113
      file('$testOutputDirectory/$timelineSummaryFile.json').readAsStringSync(),
114
    ) as Map<String, dynamic>;
115

116 117
    if (transitionDurationFile != null) {
      final Map<String, dynamic> original = json.decode(
118
        file('$testOutputDirectory/$transitionDurationFile.json').readAsStringSync(),
119 120 121 122 123 124 125 126
      ) as Map<String, dynamic>;
      final Map<String, List<int>> transitions = <String, List<int>>{};
      for (final String key in original.keys) {
        transitions[key] = List<int>.from(original[key] as List<dynamic>);
      }
      summary['transitions'] = transitions;
      summary['missed_transition_count'] = _countMissedTransitions(transitions);
    }
127

128
    final bool isAndroid = deviceOperatingSystem == DeviceOperatingSystem.android;
129
    return TaskResult.success(summary,
130 131
      detailFiles: <String>[
        if (transitionDurationFile != null)
132
          '$testOutputDirectory/$transitionDurationFile.json',
133
        if (timelineTraceFile != null)
134
          '$testOutputDirectory/$timelineTraceFile.json'
135
      ],
136 137 138 139 140 141 142 143 144 145 146
      benchmarkScoreKeys: <String>[
        if (transitionDurationFile != null)
          'missed_transition_count',
        'average_frame_build_time_millis',
        'worst_frame_build_time_millis',
        '90th_percentile_frame_build_time_millis',
        '99th_percentile_frame_build_time_millis',
        'average_frame_rasterizer_time_millis',
        'worst_frame_rasterizer_time_millis',
        '90th_percentile_frame_rasterizer_time_millis',
        '99th_percentile_frame_rasterizer_time_millis',
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
        'average_layer_cache_count',
        '90th_percentile_layer_cache_count',
        '99th_percentile_layer_cache_count',
        'worst_layer_cache_count',
        'average_layer_cache_memory',
        '90th_percentile_layer_cache_memory',
        '99th_percentile_layer_cache_memory',
        'worst_layer_cache_memory',
        'average_picture_cache_count',
        '90th_percentile_picture_cache_count',
        '99th_percentile_picture_cache_count',
        'worst_picture_cache_count',
        'average_picture_cache_memory',
        '90th_percentile_picture_cache_memory',
        '99th_percentile_picture_cache_memory',
        'worst_picture_cache_memory',
163 164 165 166 167 168 169 170 171 172 173
        if (measureCpuGpu && !isAndroid) ...<String>[
          // See https://github.com/flutter/flutter/issues/68888
          if (summary['average_cpu_usage'] != null) 'average_cpu_usage',
          if (summary['average_gpu_usage'] != null) 'average_gpu_usage',
        ],
        if (measureMemory && !isAndroid) ...<String>[
          // See https://github.com/flutter/flutter/issues/68888
          if (summary['average_memory_usage'] != null) 'average_memory_usage',
          if (summary['90th_percentile_memory_usage'] != null) '90th_percentile_memory_usage',
          if (summary['99th_percentile_memory_usage'] != null) '99th_percentile_memory_usage',
        ],
174 175
      ],
    );
176 177
  }
}
178 179

int _countMissedTransitions(Map<String, List<int>> transitions) {
180
  const int kTransitionBudget = 100000; // µs
181 182
  int count = 0;
  transitions.forEach((String demoName, List<int> durations) {
183
    final int longestDuration = durations.reduce(math.max);
184 185
    if (longestDuration > kTransitionBudget) {
      print('$demoName missed transition time budget ($longestDuration µs > $kTransitionBudget µs)');
186 187 188 189 190
      count++;
    }
  });
  return count;
}