| # Copyright 2013 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Define a type that wraps a Benchmark instance.""" |
| |
| |
| import math |
| import statistics |
| from typing import Any |
| |
| import numpy as np |
| |
| |
| # See crbug.com/673558 for how these are estimated. |
| _estimated_stddev = { |
| "octane": 0.015, |
| "kraken": 0.019, |
| "speedometer": 0.007, |
| "speedometer2": 0.006, |
| "dromaeo.domcoreattr": 0.023, |
| "dromaeo.domcoremodify": 0.011, |
| "graphics_WebGLAquarium": 0.008, |
| "page_cycler_v2.typical_25": 0.021, |
| "loading.desktop": 0.021, # Copied from page_cycler initially |
| } |
| |
| # Numpy makes it hard to know the real type of some inputs |
| # and outputs, so this type alias is just for docs. |
| FloatLike = Any |
| |
| |
| def isf(x: FloatLike, mu=0.0, sigma=1.0, pitch=0.01) -> FloatLike: |
| """Compute the inverse survival function for value x. |
| |
| In the abscence of using scipy.stats.norm's isf(), this function |
| attempts to re-implement the inverse survival function by calculating |
| the numerical inverse of the survival function, interpolating between |
| table values. See bug b/284489250 for details. |
| |
| Survival function as defined by: |
| https://en.wikipedia.org/wiki/Survival_function |
| |
| Examples: |
| >>> -2.0e-16 < isf(0.5) < 2.0e-16 |
| True |
| |
| Args: |
| x: float or numpy array-like to compute the ISF for. |
| mu: Center of the underlying normal distribution. |
| sigma: Spread of the underlying normal distribution. |
| pitch: Absolute spacing between y-value interpolation points. |
| |
| Returns: |
| float or numpy array-like representing the ISF of `x`. |
| """ |
| norm = statistics.NormalDist(mu, sigma) |
| # np.interp requires a monotonically increasing x table. |
| # Because the survival table is monotonically decreasing, we have to |
| # reverse the y_vals too. |
| y_vals = np.flip(np.arange(-4.0, 4.0, pitch)) |
| survival_table = np.fromiter( |
| (1.0 - norm.cdf(y) for y in y_vals), y_vals.dtype |
| ) |
| return np.interp(x, survival_table, y_vals) |
| |
| |
| # Get #samples needed to guarantee a given confidence interval, assuming the |
| # samples follow normal distribution. |
| def _samples(b: str) -> int: |
| # TODO: Make this an option |
| # CI = (0.9, 0.02), i.e., 90% chance that |sample mean - true mean| < 2%. |
| p = 0.9 |
| e = 0.02 |
| if b not in _estimated_stddev: |
| return 1 |
| d = _estimated_stddev[b] |
| # Get at least 2 samples so as to calculate standard deviation, which is |
| # needed in T-test for p-value. |
| n = int(math.ceil((isf((1 - p) / 2) * d / e) ** 2)) |
| return n if n > 1 else 2 |
| |
| |
| class Benchmark(object): |
| """Class representing a benchmark to be run. |
| |
| Contains details of the benchmark suite, arguments to pass to the suite, |
| iterations to run the benchmark suite and so on. Note that the benchmark name |
| can be different to the test suite name. For example, you may want to have |
| two different benchmarks which run the same test_name with different |
| arguments. |
| """ |
| |
| def __init__( |
| self, |
| name, |
| test_name, |
| test_args, |
| iterations, |
| rm_chroot_tmp, |
| perf_args, |
| suite="", |
| show_all_results=False, |
| retries=0, |
| run_local=False, |
| cwp_dso="", |
| weight=0, |
| ): |
| self.name = name |
| # For telemetry, this is the benchmark name. |
| self.test_name = test_name |
| # For telemetry, this is the data. |
| self.test_args = test_args |
| self.iterations = iterations if iterations > 0 else _samples(name) |
| self.perf_args = perf_args |
| self.rm_chroot_tmp = rm_chroot_tmp |
| self.iteration_adjusted = False |
| self.suite = suite |
| self.show_all_results = show_all_results |
| self.retries = retries |
| if self.suite == "telemetry": |
| self.show_all_results = True |
| if run_local and self.suite != "telemetry_Crosperf": |
| raise RuntimeError( |
| "run_local is only supported by telemetry_Crosperf." |
| ) |
| self.run_local = run_local |
| self.cwp_dso = cwp_dso |
| self.weight = weight |