blob: fc62a8948f80d9189f503ca9e70b5150438ec275 [file] [log] [blame]
// 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<Benchmark> benchmarks = [
DartStartup(),
DartRunStartup(),
SdkSize(),
];
void main(List<String> 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 <options>');
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<BenchmarkResult> run(Context context);
}
class DartStartup extends Benchmark {
DartStartup()
: super(
'script-startup',
'Benchmark the startup time of a minimal Dart script (μs).',
);
@override
Future<BenchmarkResult> 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<BenchmarkResult> 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<BenchmarkResult> 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);
}
}