Skip to content

Commit

Permalink
Add the ability to average benchmark runs
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzieschmoll committed Dec 8, 2023
1 parent 6dd6896 commit aa16136
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 15 deletions.
82 changes: 67 additions & 15 deletions packages/devtools_app/benchmark/scripts/run_benchmarks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,39 @@ import 'utils.dart';

/// Runs the DevTools web benchmarks and reports the benchmark data.
///
/// Arguments:
/// * --browser - runs the benchmark tests in the browser (non-headless mode)
/// * --wasm - runs the benchmark tests with the dart2wasm compiler
///
/// See [BenchmarkArgs].
/// To see available arguments, run this script with the `-h` flag.
Future<void> main(List<String> args) async {
if (args.isNotEmpty && args.first == '-h') {
stdout.writeln(BenchmarkArgs._buildArgParser().usage);
return;
}

final benchmarkArgs = BenchmarkArgs(args);
final benchmarkResults = <BenchmarkResults>[];
for (var i = 0; i < benchmarkArgs.averageOf; i++) {
stdout.writeln('Starting web benchmark tests (run #$i) ...');
benchmarkResults.add(
await serveWebBenchmark(
benchmarkAppDirectory: projectRootDirectory(),
entryPoint: 'benchmark/test_infra/client.dart',
compilationOptions: CompilationOptions(useWasm: benchmarkArgs.useWasm),
treeShakeIcons: false,
initialPage: benchmarkInitialPage,
headless: !benchmarkArgs.useBrowser,
),
);
stdout.writeln('Web benchmark tests finished (run #$i).');
}

stdout.writeln('Starting web benchmark tests...');
final taskResult = await serveWebBenchmark(
benchmarkAppDirectory: projectRootDirectory(),
entryPoint: 'benchmark/test_infra/client.dart',
compilationOptions: CompilationOptions(useWasm: benchmarkArgs.useWasm),
treeShakeIcons: false,
initialPage: benchmarkInitialPage,
headless: !benchmarkArgs.useBrowser,
);
stdout.writeln('Web benchmark tests finished.');
late final BenchmarkResults taskResult;
if (benchmarkArgs.averageOf == 1) {
taskResult = benchmarkResults.first;
} else {
stdout.writeln(
'Taking the average of ${benchmarkResults.length} benchmark runs.',
);
taskResult = averageBenchmarkResults(benchmarkResults);
}

final resultsAsMap = taskResult.toJson();
final resultsAsJsonString =
Expand Down Expand Up @@ -84,6 +99,8 @@ class BenchmarkArgs {

bool get useWasm => argResults[_wasmFlag];

int get averageOf => int.parse(argResults[_averageOfOption]);

String? get saveToFileLocation => argResults[_saveToFileOption];

String? get baselineLocation => argResults[_baselineOption];
Expand All @@ -96,15 +113,19 @@ class BenchmarkArgs {

static const _baselineOption = 'baseline';

static const _averageOfOption = 'average-of';

/// Builds an arg parser for DevTools benchmarks.
static ArgParser _buildArgParser() {
return ArgParser()
..addFlag(
_browserFlag,
negatable: false,
help: 'Runs the benchmark tests in browser mode (not headless mode).',
)
..addFlag(
_wasmFlag,
negatable: false,
help: 'Runs the benchmark tests with dart2wasm',
)
..addOption(
Expand All @@ -118,6 +139,37 @@ class BenchmarkArgs {
'baseline file should be created by running this script with the '
'$_saveToFileOption in a separate test run.',
valueHelp: '/Users/me/Downloads/baseline.json',
)
..addOption(
_averageOfOption,
defaultsTo: '1',
help: 'The number of times to run the benchmark. The returned results '
'will be the average of all the benchmark runs when this value is '
'greater than 1.',
valueHelp: '5',
);
}
}

BenchmarkResults averageBenchmarkResults(List<BenchmarkResults> results) {
if (results.isEmpty) {
throw Exception('Cannot take average of empty list.');
}

var totalSum = results.first;
for (int i = 1; i < results.length; i++) {
final current = results[i];
totalSum = totalSum.sumWith(current);
}

final average = totalSum.toJson();
for (final benchmark in totalSum.scores.keys) {
final scoresForBenchmark = totalSum.scores[benchmark]!;
for (int i = 0; i < scoresForBenchmark.length; i++) {
final score = scoresForBenchmark[i];
final averageScore = score.value / results.length;
average[benchmark]![i][score.metric] = averageScore;
}
}
return BenchmarkResults.parse(average);
}
46 changes: 46 additions & 0 deletions packages/devtools_app/benchmark/scripts/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

import 'dart:io';

import 'package:collection/collection.dart';
import 'package:web_benchmarks/server.dart';

File? checkFileExists(String path) {
final testFile = File.fromUri(Uri.parse(path));
if (!testFile.existsSync()) {
Expand All @@ -12,3 +15,46 @@ File? checkFileExists(String path) {
}
return testFile;
}

extension BenchmarkResultsExtension on BenchmarkResults {
BenchmarkResults sumWith(
BenchmarkResults other, {
bool throwExceptionOnMismatch = true,
}) {
final sum = toJson();
for (final benchmark in scores.keys) {
// Look up this benchmark in [other].
final matchingBenchmark = other.scores[benchmark];
if (matchingBenchmark == null) {
if (throwExceptionOnMismatch) {
throw Exception(
'Cannot sum benchmarks because [other] is missing an entry for '
'benchmark "$benchmark".',
);
}
continue;
}

final scoresForBenchmark = scores[benchmark]!;
for (int i = 0; i < scoresForBenchmark.length; i++) {
final score = scoresForBenchmark[i];
// Look up this score in the [matchingBenchmark] from [other].
final matchingScore =
matchingBenchmark.firstWhereOrNull((s) => s.metric == score.metric);
if (matchingScore == null) {
if (throwExceptionOnMismatch) {
throw Exception(
'Cannot sum benchmarks because benchmark "$benchmark" is missing '
'a score for metric ${score.metric}.',
);
}
continue;
}

final sumScore = score.value + matchingScore.value;
sum[benchmark]![i][score.metric] = sumScore;
}
}
return BenchmarkResults.parse(sum);
}
}

0 comments on commit aa16136

Please sign in to comment.