// 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 args) async { final List 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 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 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 '; 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 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 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 keys = (new Set() ..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 getProjectRoots({bool quick: false}) { String script = Platform.script.toFilePath(windows: Platform.isWindows); String pkgPath = path.normalize(path.join(path.dirname(script), '..', '..')); return [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 } }