| // Copyright 2014 The Flutter Authors. 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' show json; |
| import 'dart:math' as math; |
| |
| double _doNormal( |
| {required double mean, required double stddev, required double x}) { |
| return (1.0 / (stddev * math.sqrt(2.0 * math.pi))) * |
| math.pow(math.e, -0.5 * math.pow((x - mean) / stddev, 2.0)); |
| } |
| |
| double _doMean(List<double> values) => |
| values.reduce((double x, double y) => x + y) / values.length; |
| |
| double _doStddev(List<double> values, double mean) { |
| double stddev = 0.0; |
| for (final double value in values) { |
| stddev += (value - mean) * (value - mean); |
| } |
| return math.sqrt(stddev / values.length); |
| } |
| |
| double _doIntegral({ |
| required double Function(double) func, |
| required double start, |
| required double stop, |
| required double resolution, |
| }) { |
| double result = 0.0; |
| while (start < stop) { |
| final double value = func(start); |
| result += resolution * value; |
| start += resolution; |
| } |
| return result; |
| } |
| |
| /// Probability is defined as the probability that the mean is within the |
| /// [margin] of the true value. |
| double _doProbability({required double mean, required double stddev, required double margin}) { |
| return _doIntegral( |
| func: (double x) => _doNormal(mean: mean, stddev: stddev, x: x), |
| start: (1.0 - margin) * mean, |
| stop: (1.0 + margin) * mean, |
| resolution: 0.001, |
| ); |
| } |
| |
| /// This class knows how to format benchmark results for machine and human |
| /// consumption. |
| /// |
| |
| /// Example: |
| /// |
| /// BenchmarkResultPrinter printer = BenchmarkResultPrinter(); |
| /// printer.add( |
| /// description: 'Average frame time', |
| /// value: averageFrameTime, |
| /// unit: 'ms', |
| /// name: 'average_frame_time', |
| /// ); |
| /// printer.printToStdout(); |
| /// |
| class BenchmarkResultPrinter { |
| |
| final List<_BenchmarkResult> _results = <_BenchmarkResult>[]; |
| |
| /// Adds a benchmark result to the list of results. |
| /// |
| /// [description] is a human-readable description of the result. [value] is a |
| /// result value. [unit] is the unit of measurement, such as "ms", "km", "h". |
| /// [name] is a computer-readable name of the result used as a key in the JSON |
| /// serialization of the results. |
| void addResult({ required String description, required double value, required String unit, required String name }) { |
| _results.add(_BenchmarkResult(description, value, unit, name)); |
| } |
| |
| /// Adds a benchmark result to the list of results and a probability of that |
| /// result. |
| /// |
| /// The probability is calculated as the probability that the mean is +- 5% of |
| /// the true value. |
| /// |
| /// See also [addResult]. |
| void addResultStatistics({ |
| required String description, |
| required List<double> values, |
| required String unit, |
| required String name, |
| }) { |
| final double mean = _doMean(values); |
| final double stddev = _doStddev(values, mean); |
| const double margin = 0.05; |
| final double probability = _doProbability(mean: mean, stddev: stddev, margin: margin); |
| _results.add(_BenchmarkResult(description, mean, unit, name)); |
| _results.add(_BenchmarkResult('$description - probability margin of error $margin', probability, |
| 'percent', '${name}_probability_5pct')); |
| } |
| |
| /// Prints the results added via [addResult] to standard output, once as JSON |
| /// for computer consumption and once formatted as plain text for humans. |
| void printToStdout() { |
| // IMPORTANT: keep these values in sync with dev/devicelab/bin/tasks/microbenchmarks.dart |
| const String jsonStart = '================ RESULTS ================'; |
| const String jsonEnd = '================ FORMATTED =============='; |
| const String jsonPrefix = ':::JSON:::'; |
| |
| print(jsonStart); |
| print('$jsonPrefix ${_printJson()}'); |
| print(jsonEnd); |
| print(_printPlainText()); |
| } |
| |
| String _printJson() { |
| final Map<String, double> results = <String, double>{}; |
| for (final _BenchmarkResult result in _results) { |
| results[result.name] = result.value; |
| } |
| return json.encode(results); |
| } |
| |
| String _printPlainText() { |
| final StringBuffer buf = StringBuffer(); |
| for (final _BenchmarkResult result in _results) { |
| buf.writeln('${result.description}: ${result.value.toStringAsFixed(1)} ${result.unit}'); |
| } |
| return buf.toString(); |
| } |
| } |
| |
| class _BenchmarkResult { |
| _BenchmarkResult(this.description, this.value, this.unit, this.name); |
| |
| /// Human-readable description of the result, e.g. "Average frame time". |
| final String description; |
| |
| /// Result value that in agreement with [unit]. |
| final double value; |
| |
| /// Unit of measurement that is in agreement with [value]. |
| final String unit; |
| |
| /// Computer-readable name of the result. |
| final String name; |
| } |