| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package dart |
| |
| import ( |
| "bytes" |
| "fmt" |
| "strings" |
| "text/template" |
| |
| gidlconfig "go.fuchsia.dev/fuchsia/tools/fidl/gidl/config" |
| gidlir "go.fuchsia.dev/fuchsia/tools/fidl/gidl/ir" |
| gidlmixer "go.fuchsia.dev/fuchsia/tools/fidl/gidl/mixer" |
| "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen" |
| ) |
| |
| var benchmarkTmpl = template.Must(template.New("benchmarkTmpls").Parse(` |
| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // @dart = 2.8 |
| |
| import 'dart:convert'; |
| import 'dart:io' hide exit; |
| import 'dart:typed_data'; |
| |
| import 'package:fidl/fidl.dart'; |
| import 'package:fidl_benchmarkfidl/fidl_async.dart'; |
| import 'package:fuchsia/fuchsia.dart'; |
| import 'package:sl4f/sl4f.dart'; |
| import 'package:test/test.dart'; |
| {{- if .UsesHandles }} |
| import 'package:sdk.dart.lib.gidl/handles.dart'; |
| {{- end }} |
| |
| typedef _CallbackSetter = void Function(void Function()); |
| typedef _DefinitionBlock = void Function( |
| _CallbackSetter run, _CallbackSetter teardown); |
| |
| class _Definition { |
| final String name; |
| final _DefinitionBlock block; |
| |
| void Function() _run; |
| void Function() _teardown; |
| |
| _Definition(this.name, this.block); |
| |
| double execute() { |
| block((void Function() run) => _run = run, |
| (void Function() teardown) => _teardown = teardown); |
| if (_run == null) { |
| throw Exception("Benchmark $name doesn't declare what to run."); |
| } |
| |
| // Warmup for at least 100ms. Discard result. |
| _measure(100); |
| |
| // Run the benchmark for at least 1s. |
| double result = _measure(1000); |
| |
| if (_teardown != null) { |
| _teardown(); |
| } |
| |
| print('$name $result ms\n'); |
| return result; |
| } |
| |
| // Measures the score for this benchmark by executing it repeateldy until |
| // time minimum has been reached. |
| double _measure(int minimumMillis) { |
| int minimumMicros = minimumMillis * 1000; |
| int iter = 0; |
| Stopwatch watch = Stopwatch()..start(); |
| int elapsed = 0; |
| while (elapsed < minimumMicros) { |
| _run(); |
| elapsed = watch.elapsedMicroseconds; |
| iter++; |
| } |
| return elapsed / iter; |
| } |
| } |
| |
| class BenchmarkGroup { |
| final List<_Definition> definitions; |
| final String name; |
| |
| BenchmarkGroup(this.name) : definitions = []; |
| |
| void benchmark(final String name, final _DefinitionBlock block) { |
| definitions.add(_Definition(name, block)); |
| } |
| |
| List<TestCaseResults> run() { |
| final List<TestCaseResults> results = []; |
| for (final def in definitions) { |
| final result = def.execute(); |
| results.add(TestCaseResults(def.name, Unit.nanoseconds, [result * 1000])); |
| } |
| return results; |
| } |
| } |
| |
| {{ range .Benchmarks }} |
| void encode{{ .Name }}Benchmark(run, teardown) { |
| {{- if .HandleDefs }} |
| final handles = createHandles({{ .HandleDefs }}); |
| teardown(() { |
| closeHandles(handles); |
| }); |
| {{- end }} |
| final value = {{ .Value }}; |
| run(() { |
| final Encoder encoder = Encoder(kWireFormatDefault) |
| ..alloc({{ .ValueType}}.inlineSize(kWireFormatDefault), 0); |
| {{ .ValueType }}.encode(encoder, value, 0, 1); |
| }); |
| } |
| void decode{{ .Name }}Benchmark(run, teardown) { |
| {{- if .HandleDefs }} |
| final handles = createHandles({{ .HandleDefs }}); |
| teardown(() { |
| closeHandles(handles); |
| }); |
| {{- end }} |
| final value = {{ .Value }}; |
| final Encoder encoder = Encoder(kWireFormatDefault) |
| ..alloc({{ .ValueType}}.inlineSize(kWireFormatDefault), 0); |
| {{ .ValueType }}.encode(encoder, value, 0, 1); |
| run(() { |
| final Decoder decoder = Decoder(IncomingMessage.fromOutgoingMessage(encoder.message)) |
| ..claimMemory({{ .ValueType}}.inlineSize(kWireFormatDefault), 0); |
| {{ .ValueType }}.decode(decoder, 0, 1); |
| }); |
| } |
| {{ end }} |
| |
| List<Map<String, dynamic>> resultsToJson( |
| List<TestCaseResults> results, String suiteName) { |
| return results.map((result) => result.toJson(testSuite: suiteName)).toList(); |
| } |
| |
| void main() async { |
| test('main', () async { |
| final benchmarks = BenchmarkGroup('fuchsia.fidl_microbenchmarks') |
| {{ range .Benchmarks }} |
| ..benchmark('Dart/Encode/{{ .ChromeperfPath }}/WallTime', encode{{.Name}}Benchmark) |
| ..benchmark('Dart/Decode/{{ .ChromeperfPath }}/WallTime', decode{{.Name}}Benchmark) |
| {{ end }}; |
| |
| final results = benchmarks.run(); |
| if (results.isEmpty) { |
| exit(1); |
| } |
| |
| var encoder = JsonEncoder.withIndent(' '); |
| await File('/data/results.json').writeAsString( |
| encoder.convert(resultsToJson(results, benchmarks.name)), |
| mode: FileMode.write); |
| }, timeout: Timeout(Duration(minutes: 10))); |
| } |
| `)) |
| |
| type benchmarkTmplInput struct { |
| UsesHandles bool |
| Benchmarks []benchmark |
| } |
| |
| type benchmark struct { |
| Name, ChromeperfPath, Value, ValueType, HandleDefs string |
| } |
| |
| // GenerateBenchmarks generates Dart benchmarks. |
| func GenerateBenchmarks(gidl gidlir.All, fidl fidlgen.Root, config gidlconfig.GeneratorConfig) ([]byte, error) { |
| schema := gidlmixer.BuildSchema(fidl) |
| var benchmarks []benchmark |
| usesHandles := false |
| for _, gidlBenchmark := range gidl.Benchmark { |
| decl, err := schema.ExtractDeclaration(gidlBenchmark.Value, gidlBenchmark.HandleDefs) |
| if err != nil { |
| return nil, fmt.Errorf("benchmark %s: %s", gidlBenchmark.Name, err) |
| } |
| value := visit(gidlBenchmark.Value, decl) |
| valueType := typeName(decl) |
| benchmarks = append(benchmarks, benchmark{ |
| Name: benchmarkName(gidlBenchmark.Name), |
| ChromeperfPath: gidlBenchmark.Name, |
| Value: value, |
| ValueType: valueType, |
| HandleDefs: buildHandleDefs(gidlBenchmark.HandleDefs), |
| }) |
| if len(gidlBenchmark.HandleDefs) > 0 { |
| usesHandles = true |
| } |
| } |
| input := benchmarkTmplInput{ |
| UsesHandles: usesHandles, |
| Benchmarks: benchmarks, |
| } |
| var buf bytes.Buffer |
| err := benchmarkTmpl.Execute(&buf, input) |
| return buf.Bytes(), err |
| } |
| |
| func benchmarkName(gidlName string) string { |
| return fidlgen.ToSnakeCase(strings.ReplaceAll(gidlName, "/", "_")) |
| } |