| // 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) { | |
| var stddev = 0.0; | |
| for (final 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, | |
| }) { | |
| var 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 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 jsonStart = '================ RESULTS ================'; | |
| const jsonEnd = '================ FORMATTED =============='; | |
| const jsonPrefix = ':::JSON:::'; | |
| print(jsonStart); | |
| print('$jsonPrefix ${_printJson()}'); | |
| print(jsonEnd); | |
| print(_printPlainText()); | |
| } | |
| String _printJson() { | |
| final results = <String, double>{}; | |
| for (final _BenchmarkResult result in _results) { | |
| results[result.name] = result.value; | |
| } | |
| return json.encode(results); | |
| } | |
| String _printPlainText() { | |
| final 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; | |
| } |