// Copyright (c) 2020, 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' as io; import 'package:args/args.dart'; import 'package:meta/meta.dart'; List benchmarks = [ DartStartup(), DartRunStartup(), SdkSize(), ]; void main(List args) async { ArgParser argParser = _createArgParser(); ArgResults argResults; try { argResults = argParser.parse(args); } on FormatException catch (e) { print(e.message); print(''); printUsage(argParser, includeDescription: false); io.exit(1); } if (argResults['help'] || argResults.arguments.isEmpty) { printUsage(argParser); io.exit(0); } if (!argResults.wasParsed('dart-sdk')) { print('No value passed for \`dart-sdk\`.'); print(''); printUsage(argParser); io.exit(1); } if (!argResults.wasParsed('run')) { print('No value passed for \`run\`.'); print(''); printUsage(argParser); io.exit(1); } Context context = Context(argResults['dart-sdk']); String benchmarkName = argResults['run']; Benchmark benchmark = benchmarks.singleWhere((b) => b.id == benchmarkName); BenchmarkResult result = await benchmark.run(context); print(result.toJson()); io.exit(0); } void printUsage(ArgParser argParser, {bool includeDescription = true}) { print('usage: dart bin/bench.dart '); print(''); if (includeDescription) { print('Run benchmarks for the dartdev tool.'); print(''); } print('Options:'); print(argParser.usage); } ArgParser _createArgParser() { ArgParser argParser = ArgParser(usageLineLength: io.stdout.terminalColumns); argParser.addFlag( 'help', abbr: 'h', negatable: false, help: 'Print this usage information.', ); argParser.addOption( 'dart-sdk', valueHelp: 'sdk path', help: 'The path to the Dart SDK to use for benchmarking.', ); argParser.addOption( 'run', valueHelp: 'benchmark', allowed: benchmarks.map((b) => b.id).toList(), allowedHelp: { for (var benchmark in benchmarks) benchmark.id: benchmark.description, }, help: 'The benchmark to run.', ); return argParser; } abstract class Benchmark { final String id; final String description; Benchmark(this.id, this.description); Future run(Context context); } class DartStartup extends Benchmark { DartStartup() : super( 'script-startup', 'Benchmark the startup time of a minimal Dart script (μs).', ); @override Future run(Context context) async { // setup io.Directory dir = io.Directory.systemTemp.createTempSync('dartdev'); io.File file = io.File('${dir.path}/hello.dart'); file.writeAsStringSync('void main() => print(\'hello\');'); // perform the benchmark Stopwatch timer = Stopwatch()..start(); io.Process.runSync( '${context.sdkPath}/bin/dart', [file.absolute.path], ); timer.stop(); // cleanup dir.deleteSync(recursive: true); // report the result int micros = timer.elapsedMicroseconds; return BenchmarkResult( id: id, value: micros, units: 'microseconds', userDescription: '${(micros / 1000.0).toStringAsFixed(2)}ms', ); } } class DartRunStartup extends Benchmark { DartRunStartup() : super( 'run-script-startup', 'Benchmark the startup time of a minimal Dart script, executed with ' '\`dart run\` (μs).', ); @override Future run(Context context) async { // setup io.Directory dir = io.Directory.systemTemp.createTempSync('dartdev'); io.File file = io.File('${dir.path}/hello.dart'); file.writeAsStringSync('void main() => print(\'hello\');'); // perform the benchmark Stopwatch timer = Stopwatch()..start(); io.Process.runSync( '${context.sdkPath}/bin/dart', ['run', file.absolute.path], ); timer.stop(); // cleanup dir.deleteSync(recursive: true); // report the result int micros = timer.elapsedMicroseconds; return BenchmarkResult( id: id, value: micros, units: 'microseconds', userDescription: '${(micros / 1000.0).toStringAsFixed(2)}ms', ); } } class SdkSize extends Benchmark { SdkSize() : super( 'sdk-size', 'Benchmark the compressed size of the Dart SDK (bytes).', ); @override Future run(Context context) async { // setup io.Directory tempDir = io.Directory.systemTemp.createTempSync('dartdev'); // perform the benchmark io.File sdkArchive = compress(io.Directory(context.sdkPath), tempDir); int bytes = sdkArchive.lengthSync(); // cleanup tempDir.deleteSync(recursive: true); // report the result return BenchmarkResult( id: id, value: bytes, units: 'bytes', userDescription: '${(bytes / (1024.0 * 1024.0)).toStringAsFixed(1)}MB', ); } io.File compress(io.Directory sourceDir, io.Directory targetDir) { String name = sourceDir.path.substring(sourceDir.path.lastIndexOf('/') + 1); io.File outFile = io.File('${targetDir.absolute.path}/$name.zip'); if (io.Platform.isMacOS || io.Platform.isLinux) { io.Process.runSync('zip', [ '-r', '-9', // optimized for compressed size outFile.absolute.path, sourceDir.absolute.path, ]); } else { throw Exception('platform not supported: ${io.Platform.operatingSystem}'); } return outFile; } } class Context { final String sdkPath; Context(this.sdkPath); } class BenchmarkResult { final String id; final int value; final String units; final String userDescription; BenchmarkResult({ @required this.id, @required this.value, @required this.units, @required this.userDescription, }); String toJson() { Map m = { 'id': id, 'value': value, 'units': units, 'userDescription': userDescription, }; return JsonEncoder.withIndent(' ').convert(m); } }