| // 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:io'; | |
| import 'package:path/path.dart' as path; | |
| import '../framework/task_result.dart'; | |
| import '../framework/utils.dart'; | |
| /// Run each benchmark this many times and compute average, min, max. | |
| const int _kRunsPerBenchmark = 10; | |
| Future<TaskResult> flutterToolStartupBenchmarkTask() async { | |
| final Directory projectParentDirectory = Directory.systemTemp.createTempSync( | |
| 'flutter_tool_startup_benchmark', | |
| ); | |
| final Directory projectDirectory = dir(path.join(projectParentDirectory.path, 'benchmark')); | |
| await inDirectory<void>(flutterDirectory, () async { | |
| await flutter('update-packages'); | |
| await flutter('create', options: <String>[projectDirectory.path]); | |
| // Remove 'test' directory so we don't time the actual testing, but only the launching of the flutter tool | |
| rmTree(dir(path.join(projectDirectory.path, 'test'))); | |
| }); | |
| final data = <String, dynamic>{ | |
| // `flutter test` in dir with no `test` folder. | |
| ...(await _Benchmark( | |
| projectDirectory, | |
| 'test startup', | |
| 'test', | |
| ).run()).asMap('flutter_tool_startup_test'), | |
| // `flutter test -d foo_device` in dir with no `test` folder. | |
| ...(await _Benchmark( | |
| projectDirectory, | |
| 'test startup with specified device', | |
| 'test', | |
| options: <String>['-d', 'foo_device'], | |
| ).run()).asMap('flutter_tool_startup_test_with_specified_device'), | |
| // `flutter test -v` where no android sdk will be found (at least currently). | |
| ...(await _Benchmark( | |
| projectDirectory, | |
| 'test startup no android sdk', | |
| 'test', | |
| options: <String>['-v'], | |
| environment: <String, String>{ | |
| 'ANDROID_HOME': 'dummy value', | |
| 'ANDROID_SDK_ROOT': 'dummy value', | |
| 'PATH': pathWithoutWhereHits(<String>['adb', 'aapt']), | |
| }, | |
| ).run()).asMap('flutter_tool_startup_test_no_android_sdk'), | |
| // `flutter -h`. | |
| ...(await _Benchmark( | |
| projectDirectory, | |
| 'help startup', | |
| '-h', | |
| ).run()).asMap('flutter_tool_startup_help'), | |
| }; | |
| // Cleanup. | |
| rmTree(projectParentDirectory); | |
| return TaskResult.success(data, benchmarkScoreKeys: data.keys.toList()); | |
| } | |
| String pathWithoutWhereHits(List<String> whats) { | |
| final String pathEnvironment = Platform.environment['PATH'] ?? ''; | |
| List<String> paths; | |
| if (Platform.isWindows) { | |
| paths = pathEnvironment.split(';'); | |
| } else { | |
| paths = pathEnvironment.split(':'); | |
| } | |
| // This isn't great but will probably work for our purposes. | |
| final extensions = <String>['', '.exe', '.bat', '.com']; | |
| final notFound = <String>[]; | |
| for (final path in paths) { | |
| var found = false; | |
| for (final extension in extensions) { | |
| for (final what in whats) { | |
| final f = File('$path${Platform.pathSeparator}$what$extension'); | |
| if (f.existsSync()) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (found) { | |
| break; | |
| } | |
| } | |
| if (!found) { | |
| notFound.add(path); | |
| } | |
| } | |
| if (Platform.isWindows) { | |
| return notFound.join(';'); | |
| } else { | |
| return notFound.join(':'); | |
| } | |
| } | |
| class _BenchmarkResult { | |
| const _BenchmarkResult(this.mean, this.min, this.max); | |
| final int mean; // Milliseconds | |
| final int min; // Milliseconds | |
| final int max; // Milliseconds | |
| Map<String, dynamic> asMap(String name) { | |
| return <String, dynamic>{name: mean, '${name}_minimum': min, '${name}_maximum': max}; | |
| } | |
| } | |
| class _Benchmark { | |
| _Benchmark( | |
| this.directory, | |
| this.title, | |
| this.command, { | |
| this.options = const <String>[], | |
| this.environment, | |
| }); | |
| final Directory directory; | |
| final String title; | |
| final String command; | |
| final List<String> options; | |
| final Map<String, String>? environment; | |
| Future<int> execute(int iteration, int targetIterations) async { | |
| section('Benchmark $title - ${iteration + 1} / $targetIterations'); | |
| final stopwatch = Stopwatch(); | |
| await inDirectory<void>(directory, () async { | |
| stopwatch.start(); | |
| // canFail is set to true, as e.g. `flutter test` in a dir with no `test` | |
| // directory sets a non-zero return value. | |
| await flutter(command, options: options, canFail: true, environment: environment); | |
| stopwatch.stop(); | |
| }); | |
| return stopwatch.elapsedMilliseconds; | |
| } | |
| /// Runs `benchmark` several times and reports the results. | |
| Future<_BenchmarkResult> run() async { | |
| final results = <int>[]; | |
| var sum = 0; | |
| for (var i = 0; i < _kRunsPerBenchmark; i++) { | |
| final int thisRuntime = await execute(i, _kRunsPerBenchmark); | |
| results.add(thisRuntime); | |
| sum += thisRuntime; | |
| } | |
| results.sort(); | |
| return _BenchmarkResult(sum ~/ results.length, results.first, results.last); | |
| } | |
| } |