// 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; }