| // 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:async'; |
| 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:args/command_runner.dart'; |
| import 'package:intl/intl.dart'; |
| import 'package:path/path.dart' as path; |
| |
| import 'perf/benchmarks_impl.dart'; |
| import 'perf/flutter_analyze_benchmark.dart'; |
| |
| Future main(List<String> args) async { |
| final List<Benchmark> benchmarks = [ |
| new ColdAnalysisBenchmark(), |
| new AnalysisBenchmark(), |
| new FlutterAnalyzeBenchmark(), |
| ]; |
| |
| CommandRunner runner = new CommandRunner( |
| 'benchmark', 'A benchmark runner for the analysis server.'); |
| runner.addCommand(new ListCommand(benchmarks)); |
| runner.addCommand(new RunCommand(benchmarks)); |
| runner.run(args); |
| } |
| |
| 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 name => 'list'; |
| |
| @override |
| String get description => 'List available benchmarks.'; |
| |
| @override |
| String get invocation => '${runner.executableName} $name'; |
| |
| void run() { |
| if (argResults['machine'] as bool) { |
| final Map map = { |
| 'benchmarks': benchmarks.map((b) => b.toJson()).toList() |
| }; |
| print(new JsonEncoder.withIndent(' ').convert(map)); |
| } else { |
| for (Benchmark benchmark in benchmarks) { |
| print('${benchmark.id}: ${benchmark.description}'); |
| } |
| } |
| } |
| } |
| |
| class RunCommand extends Command { |
| final List<Benchmark> benchmarks; |
| |
| RunCommand(this.benchmarks) { |
| 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: '10', 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 name => 'run'; |
| |
| @override |
| String get description => 'Run a given benchmark.'; |
| |
| @override |
| String get invocation => '${runner.executableName} $name <benchmark-id>'; |
| |
| Future run() async { |
| if (argResults.rest.isEmpty) { |
| printUsage(); |
| exit(1); |
| } |
| |
| final String benchmarkId = argResults.rest.first; |
| final int repeatCount = int.parse(argResults['repeat'] as String); |
| final bool quick = argResults['quick']; |
| final bool verbose = argResults['verbose']; |
| |
| final Benchmark benchmark = |
| benchmarks.firstWhere((b) => b.id == benchmarkId, orElse: () { |
| print("Benchmark '$benchmarkId' not found."); |
| exit(1); |
| // Never reached. |
| return null; |
| }); |
| |
| int 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; |
| Stopwatch time = new Stopwatch()..start(); |
| print('Running $benchmarkId $actualIterations times...'); |
| |
| for (int iteration = 0; iteration < actualIterations; iteration++) { |
| BenchMarkResult newResult = await benchmark.run( |
| quick: quick, |
| verbose: verbose, |
| ); |
| print(' $newResult'); |
| result = result == null ? newResult : result.combine(newResult); |
| } |
| |
| time.stop(); |
| print('Finished in ${time.elapsed.inSeconds} seconds.\n'); |
| Map m = {'benchmark': benchmarkId, 'result': result.toJson()}; |
| print(json.encode(m)); |
| |
| await benchmark.oneTimeCleanup(); |
| } catch (error, st) { |
| print('$benchmarkId threw exception: $error'); |
| print(st); |
| exit(1); |
| } |
| } |
| } |
| |
| 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, this.kind: 'cpu'}); |
| |
| bool get needsSetup => false; |
| |
| Future oneTimeSetup() => new Future.value(); |
| |
| Future oneTimeCleanup() => new Future.value(); |
| |
| Future<BenchMarkResult> run({ |
| bool quick: false, |
| bool verbose: false, |
| }); |
| |
| int get maxIterations => 0; |
| |
| Map toJson() => |
| {'id': id, 'description': description, 'enabled': enabled, 'kind': kind}; |
| |
| String toString() => '$id: $description'; |
| } |
| |
| class BenchMarkResult { |
| static final NumberFormat nf = new NumberFormat.decimalPattern(); |
| |
| /// One of 'bytes', 'micros', or 'compound'. |
| final String kindName; |
| |
| final int value; |
| |
| BenchMarkResult(this.kindName, this.value); |
| |
| BenchMarkResult combine(BenchMarkResult other) { |
| return new BenchMarkResult(kindName, math.min(value, other.value)); |
| } |
| |
| Map toJson() => {kindName: value}; |
| |
| String toString() => '$kindName: ${nf.format(value)}'; |
| } |
| |
| class CompoundBenchMarkResult extends BenchMarkResult { |
| final String name; |
| |
| CompoundBenchMarkResult(this.name) : super('compound', 0); |
| |
| Map<String, BenchMarkResult> results = {}; |
| |
| void add(String name, BenchMarkResult result) { |
| results[name] = result; |
| } |
| |
| BenchMarkResult combine(BenchMarkResult other) { |
| BenchMarkResult _combine(BenchMarkResult a, BenchMarkResult b) { |
| if (a == null) return b; |
| if (b == null) return a; |
| return a.combine(b); |
| } |
| |
| CompoundBenchMarkResult o = other as CompoundBenchMarkResult; |
| |
| CompoundBenchMarkResult combined = new CompoundBenchMarkResult(name); |
| List<String> keys = (new Set<String>() |
| ..addAll(results.keys) |
| ..addAll(o.results.keys)) |
| .toList(); |
| |
| for (String key in keys) { |
| combined.add(key, _combine(results[key], o.results[key])); |
| } |
| |
| return combined; |
| } |
| |
| Map toJson() { |
| Map m = {}; |
| for (String key in results.keys) { |
| m['$name-$key'] = results[key].toJson(); |
| } |
| return m; |
| } |
| |
| String toString() => '${toJson()}'; |
| } |
| |
| List<String> getProjectRoots({bool quick: false}) { |
| String script = Platform.script.toFilePath(windows: Platform.isWindows); |
| String pkgPath = path.normalize(path.join(path.dirname(script), '..', '..')); |
| return <String>[path.join(pkgPath, quick ? 'meta' : 'analysis_server')]; |
| } |
| |
| String get analysisServerSrcPath { |
| String script = Platform.script.toFilePath(windows: Platform.isWindows); |
| String pkgPath = path.normalize(path.join(path.dirname(script), '..', '..')); |
| return path.join(pkgPath, 'analysis_server'); |
| } |
| |
| void deleteServerCache() { |
| // ~/.dartServer/.analysis-driver/ |
| ResourceProvider resourceProvider = PhysicalResourceProvider.INSTANCE; |
| Folder stateLocation = resourceProvider.getStateLocation('.analysis-driver'); |
| try { |
| if (stateLocation.exists) { |
| stateLocation.delete(); |
| } |
| } catch (e) { |
| // ignore any exception |
| } |
| } |