| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. 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:convert'; |
| import 'dart:io'; |
| import 'dart:math' as math; |
| |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/file_system/physical_file_system.dart'; |
| import 'package:analyzer_utilities/package_root.dart'; |
| import 'package:args/command_runner.dart'; |
| import 'package:path/path.dart' as path; |
| |
| import 'perf/benchmarks_impl.dart'; |
| import 'perf/flutter_analyze_benchmark.dart'; |
| import 'perf/flutter_completion_benchmark.dart'; |
| |
| Future main(List<String> args) async { |
| var benchmarks = <Benchmark>[ |
| ColdAnalysisBenchmark(ServerBenchmark.das), |
| ColdAnalysisBenchmark(ServerBenchmark.lsp), |
| AnalysisBenchmark(ServerBenchmark.das), |
| AnalysisBenchmark(ServerBenchmark.lsp), |
| FlutterAnalyzeBenchmark(), |
| FlutterCompletionBenchmark.das, |
| FlutterCompletionBenchmark.lsp, |
| ]; |
| |
| var runner = CommandRunner( |
| 'benchmark', |
| 'A benchmark runner for the analysis server.', |
| ); |
| runner.addCommand(ListCommand(benchmarks)); |
| runner.addCommand(RunCommand(benchmarks)); |
| runner.run(args); |
| } |
| |
| String get analysisServerSrcPath { |
| return path.join(packageRoot, 'analysis_server'); |
| } |
| |
| void deleteServerCache() { |
| // ~/.dartServer/.analysis-driver/ |
| ResourceProvider resourceProvider = PhysicalResourceProvider.INSTANCE; |
| var stateLocation = resourceProvider.getStateLocation('.analysis-driver'); |
| try { |
| stateLocation?.delete(); |
| } catch (e) { |
| // ignore any exception |
| } |
| } |
| |
| List<String> getProjectRoots({bool quick = false}) { |
| return [path.join(packageRoot, quick ? 'meta' : 'analysis_server')]; |
| } |
| |
| abstract class Benchmark { |
| final String id; |
| final String description; |
| final bool enabled; |
| |
| /// One of 'memory', 'cpu', or 'group'. |
| final String kind; |
| |
| Benchmark(this.id, this.description, |
| {this.enabled = true, required this.kind}); |
| |
| int get maxIterations => 0; |
| |
| bool get needsSetup => false; |
| |
| Future oneTimeCleanup() => Future.value(); |
| |
| Future oneTimeSetup() => Future.value(); |
| |
| Future<BenchMarkResult> run({ |
| required String dartSdkPath, |
| bool quick = false, |
| bool verbose = false, |
| }); |
| |
| Map toJson() => |
| {'id': id, 'description': description, 'enabled': enabled, 'kind': kind}; |
| |
| @override |
| String toString() => '$id: $description'; |
| } |
| |
| class BenchMarkResult { |
| /// One of 'bytes', 'micros', or 'compound'. |
| final String kindName; |
| |
| final int value; |
| |
| BenchMarkResult(this.kindName, this.value); |
| |
| BenchMarkResult combine(BenchMarkResult other) { |
| return BenchMarkResult(kindName, math.min(value, other.value)); |
| } |
| |
| Map toJson() => {kindName: value}; |
| |
| @override |
| String toString() => '$kindName: $value'; |
| } |
| |
| class CompoundBenchMarkResult extends BenchMarkResult { |
| final String name; |
| |
| Map<String, BenchMarkResult> results = {}; |
| |
| CompoundBenchMarkResult(this.name) : super('compound', 0); |
| |
| void add(String name, BenchMarkResult result) { |
| results[name] = result; |
| } |
| |
| @override |
| BenchMarkResult combine(BenchMarkResult other) { |
| BenchMarkResult combine(BenchMarkResult? a, BenchMarkResult? b) { |
| if (a == null) return b!; |
| if (b == null) return a; |
| return a.combine(b); |
| } |
| |
| var o = other as CompoundBenchMarkResult; |
| |
| var combined = CompoundBenchMarkResult(name); |
| var keys = (<String>{} |
| ..addAll(results.keys) |
| ..addAll(o.results.keys)) |
| .toList(); |
| |
| for (var key in keys) { |
| combined.add(key, combine(results[key], o.results[key])); |
| } |
| |
| return combined; |
| } |
| |
| @override |
| Map toJson() { |
| var m = <String, dynamic>{}; |
| for (var entry in results.entries) { |
| m['$name-${entry.key}'] = entry.value.toJson(); |
| } |
| return m; |
| } |
| |
| @override |
| String toString() => '${toJson()}'; |
| } |
| |
| /// This interface is implemented by benchmarks that need to know the location |
| /// of the Flutter repository. |
| abstract class FlutterBenchmark { |
| /// Must be called exactly one time. |
| set flutterRepositoryPath(String path); |
| } |
| |
| class ListCommand extends Command { |
| final List<Benchmark> benchmarks; |
| |
| ListCommand(this.benchmarks) { |
| argParser.addFlag('machine', |
| negatable: false, help: 'Emit the list of benchmarks as json.'); |
| } |
| |
| @override |
| String get description => 'List available benchmarks.'; |
| |
| @override |
| String get invocation => '${runner!.executableName} $name'; |
| |
| @override |
| String get name => 'list'; |
| |
| @override |
| void run() { |
| if (argResults!['machine'] as bool) { |
| var map = <String, dynamic>{ |
| 'benchmarks': benchmarks.map((b) => b.toJson()).toList() |
| }; |
| print(JsonEncoder.withIndent(' ').convert(map)); |
| } else { |
| for (var benchmark in benchmarks) { |
| print('${benchmark.id}: ${benchmark.description}'); |
| } |
| } |
| } |
| } |
| |
| class RunCommand extends Command { |
| final List<Benchmark> benchmarks; |
| |
| RunCommand(this.benchmarks) { |
| argParser.addOption('dart-sdk', |
| help: 'The absolute normalized path of the Dart SDK.'); |
| argParser.addOption('flutter-repository', |
| help: 'The absolute normalized path of the Flutter repository.'); |
| argParser.addFlag('quick', |
| negatable: false, |
| help: 'Run a quick version of the benchmark. This is not useful for ' |
| 'gathering accurate times,\nbut can be used to validate that the ' |
| 'benchmark works.'); |
| argParser.addOption('repeat', |
| defaultsTo: '4', help: 'The number of times to repeat the benchmark.'); |
| argParser.addFlag('verbose', |
| negatable: false, |
| help: 'Print all communication to and from the analysis server.'); |
| } |
| |
| @override |
| String get description => 'Run a given benchmark.'; |
| |
| @override |
| String get invocation => '${runner!.executableName} $name <benchmark-id>'; |
| |
| @override |
| String get name => 'run'; |
| |
| @override |
| Future run() async { |
| var args = argResults; |
| if (args == null) { |
| throw StateError('argResults have not been set'); |
| } |
| if (args.rest.isEmpty) { |
| printUsage(); |
| exit(1); |
| } |
| |
| var benchmarkId = args.rest.first; |
| var repeatCount = int.parse(args['repeat'] as String); |
| var dartSdkPath = args['dart-sdk'] as String?; |
| var flutterRepository = args['flutter-repository'] as String?; |
| var quick = args['quick'] as bool; |
| var verbose = args['verbose'] as bool; |
| |
| var benchmark = |
| benchmarks.firstWhere((b) => b.id == benchmarkId, orElse: () { |
| print("Benchmark '$benchmarkId' not found."); |
| exit(1); |
| }); |
| |
| dartSdkPath ??= path.dirname(path.dirname(Platform.resolvedExecutable)); |
| |
| if (benchmark is FlutterBenchmark) { |
| if (flutterRepository != null) { |
| if (path.isAbsolute(flutterRepository) && |
| path.normalize(flutterRepository) == flutterRepository) { |
| (benchmark as FlutterBenchmark).flutterRepositoryPath = |
| flutterRepository; |
| } else { |
| print('The path must be absolute and normalized: $flutterRepository'); |
| exit(1); |
| } |
| } else { |
| print('The option --flutter-repository is required to ' |
| "run '$benchmarkId'."); |
| exit(1); |
| } |
| } |
| |
| var actualIterations = repeatCount; |
| if (benchmark.maxIterations > 0) { |
| actualIterations = math.min(benchmark.maxIterations, repeatCount); |
| } |
| |
| if (benchmark.needsSetup) { |
| print('Setting up $benchmarkId...'); |
| await benchmark.oneTimeSetup(); |
| } |
| |
| try { |
| BenchMarkResult? result; |
| var time = Stopwatch()..start(); |
| print('Running $benchmarkId $actualIterations times...'); |
| |
| for (var iteration = 0; iteration < actualIterations; iteration++) { |
| var newResult = await benchmark.run( |
| dartSdkPath: dartSdkPath, |
| quick: quick, |
| verbose: verbose, |
| ); |
| print(' $newResult'); |
| result = result == null ? newResult : result.combine(newResult); |
| } |
| |
| time.stop(); |
| print('Finished in ${time.elapsed.inSeconds} seconds.\n'); |
| var m = <String, dynamic>{ |
| 'benchmark': benchmarkId, |
| 'result': result!.toJson() |
| }; |
| print(json.encode(m)); |
| |
| await benchmark.oneTimeCleanup(); |
| } catch (error, st) { |
| print('$benchmarkId threw exception: $error'); |
| print(st); |
| exit(1); |
| } |
| } |
| } |