From 2124be5caee6803d5bfe6f7cdc8e3367cb375807 Mon Sep 17 00:00:00 2001 From: George Burgess IV Date: Thu, 21 Apr 2022 10:27:37 -0700 Subject: toolchain_utils: s/Cr OS/CrOS/g Result of running `sed -ri 's/Chrom(ium|e) OS/Chrom\1OS/g' $(find -type f)`. BUG=None TEST=None Change-Id: I59be92537aa19bc989f52b585e307e76dbde401b Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/3600147 Reviewed-by: Manoj Gupta Commit-Queue: George Burgess Tested-by: George Burgess --- cros_utils/tabulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cros_utils/tabulator.py') diff --git a/cros_utils/tabulator.py b/cros_utils/tabulator.py index 1a3fd4a7..1a46cca2 100644 --- a/cros_utils/tabulator.py +++ b/cros_utils/tabulator.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. +# Copyright (c) 2013 The ChromiumOS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -- cgit v1.2.3 From 74bd380a27f4f0e8e90ff2dc1cef0b502d74961b Mon Sep 17 00:00:00 2001 From: George Burgess IV Date: Fri, 2 Sep 2022 16:59:27 -0700 Subject: Autoformat all Python code This autoformats all Python code with our new Python formatter, `black`. BUG=b:244644217 TEST=None Change-Id: I15ee49233d98fb6295c0c53c129bbf8e78e0d9ff Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/3877337 Tested-by: George Burgess Reviewed-by: Jordan Abrahams-Whitehead Commit-Queue: George Burgess --- cros_utils/tabulator.py | 2699 ++++++++++++++++++++++++----------------------- 1 file changed, 1392 insertions(+), 1307 deletions(-) (limited to 'cros_utils/tabulator.py') diff --git a/cros_utils/tabulator.py b/cros_utils/tabulator.py index 1a46cca2..7b51bf86 100644 --- a/cros_utils/tabulator.py +++ b/cros_utils/tabulator.py @@ -69,958 +69,1018 @@ import getpass import math import statistics import sys + +from cros_utils import misc +from cros_utils.email_sender import EmailSender + # TODO(crbug.com/980719): Drop scipy in the future. # pylint: disable=import-error import scipy -from cros_utils.email_sender import EmailSender -from cros_utils import misc - def _AllFloat(values): - return all([misc.IsFloat(v) for v in values]) + return all([misc.IsFloat(v) for v in values]) def _GetFloats(values): - return [float(v) for v in values] + return [float(v) for v in values] def _StripNone(results): - res = [] - for result in results: - if result is not None: - res.append(result) - return res + res = [] + for result in results: + if result is not None: + res.append(result) + return res def _RemoveMinMax(cell, values): - if len(values) < 3: - print('WARNING: Values count is less than 3, not ignoring min/max values') - print('WARNING: Cell name:', cell.name, 'Values:', values) - return values + if len(values) < 3: + print( + "WARNING: Values count is less than 3, not ignoring min/max values" + ) + print("WARNING: Cell name:", cell.name, "Values:", values) + return values - values.remove(min(values)) - values.remove(max(values)) - return values + values.remove(min(values)) + values.remove(max(values)) + return values class TableGenerator(object): - """Creates a table from a list of list of dicts. - - The main public function is called GetTable(). - """ - SORT_BY_KEYS = 0 - SORT_BY_KEYS_DESC = 1 - SORT_BY_VALUES = 2 - SORT_BY_VALUES_DESC = 3 - NO_SORT = 4 - - MISSING_VALUE = 'x' - - def __init__(self, d, l, sort=NO_SORT, key_name='keys'): - self._runs = d - self._labels = l - self._sort = sort - self._key_name = key_name - - def _AggregateKeys(self): - keys = collections.OrderedDict() - for run_list in self._runs: - for run in run_list: - keys.update(dict.fromkeys(run.keys())) - return list(keys.keys()) - - def _GetHighestValue(self, key): - values = [] - for run_list in self._runs: - for run in run_list: - if key in run: - values.append(run[key]) - values = _StripNone(values) - if _AllFloat(values): - values = _GetFloats(values) - return max(values) - - def _GetLowestValue(self, key): - values = [] - for run_list in self._runs: - for run in run_list: - if key in run: - values.append(run[key]) - values = _StripNone(values) - if _AllFloat(values): - values = _GetFloats(values) - return min(values) - - def _SortKeys(self, keys): - if self._sort == self.SORT_BY_KEYS: - return sorted(keys) - elif self._sort == self.SORT_BY_VALUES: - # pylint: disable=unnecessary-lambda - return sorted(keys, key=lambda x: self._GetLowestValue(x)) - elif self._sort == self.SORT_BY_VALUES_DESC: - # pylint: disable=unnecessary-lambda - return sorted(keys, key=lambda x: self._GetHighestValue(x), reverse=True) - elif self._sort == self.NO_SORT: - return keys - else: - assert 0, 'Unimplemented sort %s' % self._sort - - def _GetKeys(self): - keys = self._AggregateKeys() - return self._SortKeys(keys) - - def GetTable(self, number_of_rows=sys.maxsize): - """Returns a table from a list of list of dicts. + """Creates a table from a list of list of dicts. - Examples: - We have the following runs: - [[{"k1": "v1", "k2": "v2"}, {"k1": "v3"}], - [{"k1": "v4", "k4": "v5"}]] - and the following labels: - ["vanilla", "modified"] - it will return: - [["Key", "vanilla", "modified"] - ["k1", ["v1", "v3"], ["v4"]] - ["k2", ["v2"], []] - ["k4", [], ["v5"]]] - The returned table can then be processed further by other classes in this - module. - - The list of list of dicts is passed into the constructor of TableGenerator. - This method converts that into a canonical list of lists which represents a - table of values. - - Args: - number_of_rows: Maximum number of rows to return from the table. - - Returns: - A list of lists which is the table. + The main public function is called GetTable(). """ - keys = self._GetKeys() - header = [self._key_name] + self._labels - table = [header] - rows = 0 - for k in keys: - row = [k] - unit = None - for run_list in self._runs: - v = [] - for run in run_list: - if k in run: - if isinstance(run[k], list): - val = run[k][0] - unit = run[k][1] - else: - val = run[k] - v.append(val) - else: - v.append(None) - row.append(v) - # If we got a 'unit' value, append the units name to the key name. - if unit: - keyname = row[0] + ' (%s) ' % unit - row[0] = keyname - table.append(row) - rows += 1 - if rows == number_of_rows: - break - return table - - -class SamplesTableGenerator(TableGenerator): - """Creates a table with only samples from the results - - The main public function is called GetTable(). - - Different than TableGenerator, self._runs is now a dict of {benchmark: runs} - We are expecting there is 'samples' in `runs`. - """ - - def __init__(self, run_keyvals, label_list, iter_counts, weights): - TableGenerator.__init__( - self, run_keyvals, label_list, key_name='Benchmarks') - self._iter_counts = iter_counts - self._weights = weights - - def _GetKeys(self): - keys = self._runs.keys() - return self._SortKeys(keys) - def GetTable(self, number_of_rows=sys.maxsize): - """Returns a tuple, which contains three args: - - 1) a table from a list of list of dicts. - 2) updated benchmark_results run_keyvals with composite benchmark - 3) updated benchmark_results iter_count with composite benchmark + SORT_BY_KEYS = 0 + SORT_BY_KEYS_DESC = 1 + SORT_BY_VALUES = 2 + SORT_BY_VALUES_DESC = 3 + NO_SORT = 4 + + MISSING_VALUE = "x" + + def __init__(self, d, l, sort=NO_SORT, key_name="keys"): + self._runs = d + self._labels = l + self._sort = sort + self._key_name = key_name + + def _AggregateKeys(self): + keys = collections.OrderedDict() + for run_list in self._runs: + for run in run_list: + keys.update(dict.fromkeys(run.keys())) + return list(keys.keys()) + + def _GetHighestValue(self, key): + values = [] + for run_list in self._runs: + for run in run_list: + if key in run: + values.append(run[key]) + values = _StripNone(values) + if _AllFloat(values): + values = _GetFloats(values) + return max(values) + + def _GetLowestValue(self, key): + values = [] + for run_list in self._runs: + for run in run_list: + if key in run: + values.append(run[key]) + values = _StripNone(values) + if _AllFloat(values): + values = _GetFloats(values) + return min(values) + + def _SortKeys(self, keys): + if self._sort == self.SORT_BY_KEYS: + return sorted(keys) + elif self._sort == self.SORT_BY_VALUES: + # pylint: disable=unnecessary-lambda + return sorted(keys, key=lambda x: self._GetLowestValue(x)) + elif self._sort == self.SORT_BY_VALUES_DESC: + # pylint: disable=unnecessary-lambda + return sorted( + keys, key=lambda x: self._GetHighestValue(x), reverse=True + ) + elif self._sort == self.NO_SORT: + return keys + else: + assert 0, "Unimplemented sort %s" % self._sort + + def _GetKeys(self): + keys = self._AggregateKeys() + return self._SortKeys(keys) + + def GetTable(self, number_of_rows=sys.maxsize): + """Returns a table from a list of list of dicts. + + Examples: + We have the following runs: + [[{"k1": "v1", "k2": "v2"}, {"k1": "v3"}], + [{"k1": "v4", "k4": "v5"}]] + and the following labels: + ["vanilla", "modified"] + it will return: + [["Key", "vanilla", "modified"] + ["k1", ["v1", "v3"], ["v4"]] + ["k2", ["v2"], []] + ["k4", [], ["v5"]]] + The returned table can then be processed further by other classes in this + module. + + The list of list of dicts is passed into the constructor of TableGenerator. + This method converts that into a canonical list of lists which represents a + table of values. + + Args: + number_of_rows: Maximum number of rows to return from the table. + + Returns: + A list of lists which is the table. + """ + keys = self._GetKeys() + header = [self._key_name] + self._labels + table = [header] + rows = 0 + for k in keys: + row = [k] + unit = None + for run_list in self._runs: + v = [] + for run in run_list: + if k in run: + if isinstance(run[k], list): + val = run[k][0] + unit = run[k][1] + else: + val = run[k] + v.append(val) + else: + v.append(None) + row.append(v) + # If we got a 'unit' value, append the units name to the key name. + if unit: + keyname = row[0] + " (%s) " % unit + row[0] = keyname + table.append(row) + rows += 1 + if rows == number_of_rows: + break + return table - The dict of list of list of dicts is passed into the constructor of - SamplesTableGenerator. - This method converts that into a canonical list of lists which - represents a table of values. - Examples: - We have the following runs: - {bench1: [[{"samples": "v1"}, {"samples": "v2"}], - [{"samples": "v3"}, {"samples": "v4"}]] - bench2: [[{"samples": "v21"}, None], - [{"samples": "v22"}, {"samples": "v23"}]]} - and weights of benchmarks: - {bench1: w1, bench2: w2} - and the following labels: - ["vanilla", "modified"] - it will return: - [["Benchmark", "Weights", "vanilla", "modified"] - ["bench1", w1, - ((2, 0), ["v1*w1", "v2*w1"]), ((2, 0), ["v3*w1", "v4*w1"])] - ["bench2", w2, - ((1, 1), ["v21*w2", None]), ((2, 0), ["v22*w2", "v23*w2"])] - ["Composite Benchmark", N/A, - ((1, 1), ["v1*w1+v21*w2", None]), - ((2, 0), ["v3*w1+v22*w2", "v4*w1+ v23*w2"])]] - The returned table can then be processed further by other classes in this - module. +class SamplesTableGenerator(TableGenerator): + """Creates a table with only samples from the results - Args: - number_of_rows: Maximum number of rows to return from the table. + The main public function is called GetTable(). - Returns: - A list of lists which is the table. + Different than TableGenerator, self._runs is now a dict of {benchmark: runs} + We are expecting there is 'samples' in `runs`. """ - keys = self._GetKeys() - header = [self._key_name, 'Weights'] + self._labels - table = [header] - rows = 0 - iterations = 0 - - for k in keys: - bench_runs = self._runs[k] - unit = None - all_runs_empty = all(not dict for label in bench_runs for dict in label) - if all_runs_empty: - cell = Cell() - cell.string_value = ('Benchmark %s contains no result.' - ' Is the benchmark name valid?' % k) - table.append([cell]) - else: - row = [k] - row.append(self._weights[k]) - for run_list in bench_runs: - run_pass = 0 - run_fail = 0 - v = [] - for run in run_list: - if 'samples' in run: - if isinstance(run['samples'], list): - val = run['samples'][0] * self._weights[k] - unit = run['samples'][1] - else: - val = run['samples'] * self._weights[k] - v.append(val) - run_pass += 1 + + def __init__(self, run_keyvals, label_list, iter_counts, weights): + TableGenerator.__init__( + self, run_keyvals, label_list, key_name="Benchmarks" + ) + self._iter_counts = iter_counts + self._weights = weights + + def _GetKeys(self): + keys = self._runs.keys() + return self._SortKeys(keys) + + def GetTable(self, number_of_rows=sys.maxsize): + """Returns a tuple, which contains three args: + + 1) a table from a list of list of dicts. + 2) updated benchmark_results run_keyvals with composite benchmark + 3) updated benchmark_results iter_count with composite benchmark + + The dict of list of list of dicts is passed into the constructor of + SamplesTableGenerator. + This method converts that into a canonical list of lists which + represents a table of values. + + Examples: + We have the following runs: + {bench1: [[{"samples": "v1"}, {"samples": "v2"}], + [{"samples": "v3"}, {"samples": "v4"}]] + bench2: [[{"samples": "v21"}, None], + [{"samples": "v22"}, {"samples": "v23"}]]} + and weights of benchmarks: + {bench1: w1, bench2: w2} + and the following labels: + ["vanilla", "modified"] + it will return: + [["Benchmark", "Weights", "vanilla", "modified"] + ["bench1", w1, + ((2, 0), ["v1*w1", "v2*w1"]), ((2, 0), ["v3*w1", "v4*w1"])] + ["bench2", w2, + ((1, 1), ["v21*w2", None]), ((2, 0), ["v22*w2", "v23*w2"])] + ["Composite Benchmark", N/A, + ((1, 1), ["v1*w1+v21*w2", None]), + ((2, 0), ["v3*w1+v22*w2", "v4*w1+ v23*w2"])]] + The returned table can then be processed further by other classes in this + module. + + Args: + number_of_rows: Maximum number of rows to return from the table. + + Returns: + A list of lists which is the table. + """ + keys = self._GetKeys() + header = [self._key_name, "Weights"] + self._labels + table = [header] + rows = 0 + iterations = 0 + + for k in keys: + bench_runs = self._runs[k] + unit = None + all_runs_empty = all( + not dict for label in bench_runs for dict in label + ) + if all_runs_empty: + cell = Cell() + cell.string_value = ( + "Benchmark %s contains no result." + " Is the benchmark name valid?" % k + ) + table.append([cell]) else: - v.append(None) - run_fail += 1 - one_tuple = ((run_pass, run_fail), v) - if iterations not in (0, run_pass + run_fail): - raise ValueError('Iterations of each benchmark run ' \ - 'are not the same') - iterations = run_pass + run_fail - row.append(one_tuple) - if unit: - keyname = row[0] + ' (%s) ' % unit - row[0] = keyname - table.append(row) - rows += 1 - if rows == number_of_rows: - break - - k = 'Composite Benchmark' - if k in keys: - raise RuntimeError('Composite benchmark already exists in results') - - # Create a new composite benchmark row at the bottom of the summary table - # The new row will be like the format in example: - # ["Composite Benchmark", N/A, - # ((1, 1), ["v1*w1+v21*w2", None]), - # ((2, 0), ["v3*w1+v22*w2", "v4*w1+ v23*w2"])]] - # First we will create a row of [key, weight, [[0] * iterations] * labels] - row = [None] * len(header) - row[0] = '%s (samples)' % k - row[1] = 'N/A' - for label_index in range(2, len(row)): - row[label_index] = [0] * iterations - - for cur_row in table[1:]: - # Iterate through each benchmark - if len(cur_row) > 1: - for label_index in range(2, len(cur_row)): - # Iterate through each run in a single benchmark - # each result should look like ((pass, fail), [values_list]) - bench_runs = cur_row[label_index][1] - for index in range(iterations): - # Accumulate each run result to composite benchmark run - # If any run fails, then we set this run for composite benchmark - # to None so that we know it fails. - if bench_runs[index] and row[label_index][index] is not None: - row[label_index][index] += bench_runs[index] + row = [k] + row.append(self._weights[k]) + for run_list in bench_runs: + run_pass = 0 + run_fail = 0 + v = [] + for run in run_list: + if "samples" in run: + if isinstance(run["samples"], list): + val = run["samples"][0] * self._weights[k] + unit = run["samples"][1] + else: + val = run["samples"] * self._weights[k] + v.append(val) + run_pass += 1 + else: + v.append(None) + run_fail += 1 + one_tuple = ((run_pass, run_fail), v) + if iterations not in (0, run_pass + run_fail): + raise ValueError( + "Iterations of each benchmark run " + "are not the same" + ) + iterations = run_pass + run_fail + row.append(one_tuple) + if unit: + keyname = row[0] + " (%s) " % unit + row[0] = keyname + table.append(row) + rows += 1 + if rows == number_of_rows: + break + + k = "Composite Benchmark" + if k in keys: + raise RuntimeError("Composite benchmark already exists in results") + + # Create a new composite benchmark row at the bottom of the summary table + # The new row will be like the format in example: + # ["Composite Benchmark", N/A, + # ((1, 1), ["v1*w1+v21*w2", None]), + # ((2, 0), ["v3*w1+v22*w2", "v4*w1+ v23*w2"])]] + # First we will create a row of [key, weight, [[0] * iterations] * labels] + row = [None] * len(header) + row[0] = "%s (samples)" % k + row[1] = "N/A" + for label_index in range(2, len(row)): + row[label_index] = [0] * iterations + + for cur_row in table[1:]: + # Iterate through each benchmark + if len(cur_row) > 1: + for label_index in range(2, len(cur_row)): + # Iterate through each run in a single benchmark + # each result should look like ((pass, fail), [values_list]) + bench_runs = cur_row[label_index][1] + for index in range(iterations): + # Accumulate each run result to composite benchmark run + # If any run fails, then we set this run for composite benchmark + # to None so that we know it fails. + if ( + bench_runs[index] + and row[label_index][index] is not None + ): + row[label_index][index] += bench_runs[index] + else: + row[label_index][index] = None else: - row[label_index][index] = None - else: - # One benchmark totally fails, no valid data will be in final result + # One benchmark totally fails, no valid data will be in final result + for label_index in range(2, len(row)): + row[label_index] = [None] * iterations + break + # Calculate pass and fail count for composite benchmark for label_index in range(2, len(row)): - row[label_index] = [None] * iterations - break - # Calculate pass and fail count for composite benchmark - for label_index in range(2, len(row)): - run_pass = 0 - run_fail = 0 - for run in row[label_index]: - if run: - run_pass += 1 - else: - run_fail += 1 - row[label_index] = ((run_pass, run_fail), row[label_index]) - table.append(row) - - # Now that we have the table genearted, we want to store this new composite - # benchmark into the benchmark_result in ResultReport object. - # This will be used to generate a full table which contains our composite - # benchmark. - # We need to create composite benchmark result and add it to keyvals in - # benchmark_results. - v = [] - for label in row[2:]: - # each label's result looks like ((pass, fail), [values]) - benchmark_runs = label[1] - # List of values of each label - single_run_list = [] - for run in benchmark_runs: - # Result of each run under the same label is a dict of keys. - # Here the only key we will add for composite benchmark is the - # weighted_samples we added up. - one_dict = {} - if run: - one_dict[u'weighted_samples'] = [run, u'samples'] - one_dict['retval'] = 0 - else: - one_dict['retval'] = 1 - single_run_list.append(one_dict) - v.append(single_run_list) - - self._runs[k] = v - self._iter_counts[k] = iterations + run_pass = 0 + run_fail = 0 + for run in row[label_index]: + if run: + run_pass += 1 + else: + run_fail += 1 + row[label_index] = ((run_pass, run_fail), row[label_index]) + table.append(row) - return (table, self._runs, self._iter_counts) + # Now that we have the table genearted, we want to store this new composite + # benchmark into the benchmark_result in ResultReport object. + # This will be used to generate a full table which contains our composite + # benchmark. + # We need to create composite benchmark result and add it to keyvals in + # benchmark_results. + v = [] + for label in row[2:]: + # each label's result looks like ((pass, fail), [values]) + benchmark_runs = label[1] + # List of values of each label + single_run_list = [] + for run in benchmark_runs: + # Result of each run under the same label is a dict of keys. + # Here the only key we will add for composite benchmark is the + # weighted_samples we added up. + one_dict = {} + if run: + one_dict[u"weighted_samples"] = [run, u"samples"] + one_dict["retval"] = 0 + else: + one_dict["retval"] = 1 + single_run_list.append(one_dict) + v.append(single_run_list) + + self._runs[k] = v + self._iter_counts[k] = iterations + + return (table, self._runs, self._iter_counts) class Result(object): - """A class that respresents a single result. - - This single result is obtained by condensing the information from a list of - runs and a list of baseline runs. - """ - - def __init__(self): - pass - - def _AllStringsSame(self, values): - values_set = set(values) - return len(values_set) == 1 - - def NeedsBaseline(self): - return False - - # pylint: disable=unused-argument - def _Literal(self, cell, values, baseline_values): - cell.value = ' '.join([str(v) for v in values]) - - def _ComputeFloat(self, cell, values, baseline_values): - self._Literal(cell, values, baseline_values) - - def _ComputeString(self, cell, values, baseline_values): - self._Literal(cell, values, baseline_values) - - def _InvertIfLowerIsBetter(self, cell): - pass + """A class that respresents a single result. - def _GetGmean(self, values): - if not values: - return float('nan') - if any([v < 0 for v in values]): - return float('nan') - if any([v == 0 for v in values]): - return 0.0 - log_list = [math.log(v) for v in values] - gmean_log = sum(log_list) / len(log_list) - return math.exp(gmean_log) - - def Compute(self, cell, values, baseline_values): - """Compute the result given a list of values and baseline values. - - Args: - cell: A cell data structure to populate. - values: List of values. - baseline_values: List of baseline values. Can be none if this is the - baseline itself. + This single result is obtained by condensing the information from a list of + runs and a list of baseline runs. """ - all_floats = True - values = _StripNone(values) - if not values: - cell.value = '' - return - if _AllFloat(values): - float_values = _GetFloats(values) - else: - all_floats = False - if baseline_values: - baseline_values = _StripNone(baseline_values) - if baseline_values: - if _AllFloat(baseline_values): - float_baseline_values = _GetFloats(baseline_values) - else: - all_floats = False - else: - if self.NeedsBaseline(): - cell.value = '' - return - float_baseline_values = None - if all_floats: - self._ComputeFloat(cell, float_values, float_baseline_values) - self._InvertIfLowerIsBetter(cell) - else: - self._ComputeString(cell, values, baseline_values) + + def __init__(self): + pass + + def _AllStringsSame(self, values): + values_set = set(values) + return len(values_set) == 1 + + def NeedsBaseline(self): + return False + + # pylint: disable=unused-argument + def _Literal(self, cell, values, baseline_values): + cell.value = " ".join([str(v) for v in values]) + + def _ComputeFloat(self, cell, values, baseline_values): + self._Literal(cell, values, baseline_values) + + def _ComputeString(self, cell, values, baseline_values): + self._Literal(cell, values, baseline_values) + + def _InvertIfLowerIsBetter(self, cell): + pass + + def _GetGmean(self, values): + if not values: + return float("nan") + if any([v < 0 for v in values]): + return float("nan") + if any([v == 0 for v in values]): + return 0.0 + log_list = [math.log(v) for v in values] + gmean_log = sum(log_list) / len(log_list) + return math.exp(gmean_log) + + def Compute(self, cell, values, baseline_values): + """Compute the result given a list of values and baseline values. + + Args: + cell: A cell data structure to populate. + values: List of values. + baseline_values: List of baseline values. Can be none if this is the + baseline itself. + """ + all_floats = True + values = _StripNone(values) + if not values: + cell.value = "" + return + if _AllFloat(values): + float_values = _GetFloats(values) + else: + all_floats = False + if baseline_values: + baseline_values = _StripNone(baseline_values) + if baseline_values: + if _AllFloat(baseline_values): + float_baseline_values = _GetFloats(baseline_values) + else: + all_floats = False + else: + if self.NeedsBaseline(): + cell.value = "" + return + float_baseline_values = None + if all_floats: + self._ComputeFloat(cell, float_values, float_baseline_values) + self._InvertIfLowerIsBetter(cell) + else: + self._ComputeString(cell, values, baseline_values) class LiteralResult(Result): - """A literal result.""" + """A literal result.""" - def __init__(self, iteration=0): - super(LiteralResult, self).__init__() - self.iteration = iteration + def __init__(self, iteration=0): + super(LiteralResult, self).__init__() + self.iteration = iteration - def Compute(self, cell, values, baseline_values): - try: - cell.value = values[self.iteration] - except IndexError: - cell.value = '-' + def Compute(self, cell, values, baseline_values): + try: + cell.value = values[self.iteration] + except IndexError: + cell.value = "-" class NonEmptyCountResult(Result): - """A class that counts the number of non-empty results. - - The number of non-empty values will be stored in the cell. - """ + """A class that counts the number of non-empty results. - def Compute(self, cell, values, baseline_values): - """Put the number of non-empty values in the cell result. - - Args: - cell: Put the result in cell.value. - values: A list of values for the row. - baseline_values: A list of baseline values for the row. + The number of non-empty values will be stored in the cell. """ - cell.value = len(_StripNone(values)) - if not baseline_values: - return - base_value = len(_StripNone(baseline_values)) - if cell.value == base_value: - return - f = ColorBoxFormat() - len_values = len(values) - len_baseline_values = len(baseline_values) - tmp_cell = Cell() - tmp_cell.value = 1.0 + ( - float(cell.value - base_value) / (max(len_values, len_baseline_values))) - f.Compute(tmp_cell) - cell.bgcolor = tmp_cell.bgcolor + + def Compute(self, cell, values, baseline_values): + """Put the number of non-empty values in the cell result. + + Args: + cell: Put the result in cell.value. + values: A list of values for the row. + baseline_values: A list of baseline values for the row. + """ + cell.value = len(_StripNone(values)) + if not baseline_values: + return + base_value = len(_StripNone(baseline_values)) + if cell.value == base_value: + return + f = ColorBoxFormat() + len_values = len(values) + len_baseline_values = len(baseline_values) + tmp_cell = Cell() + tmp_cell.value = 1.0 + ( + float(cell.value - base_value) + / (max(len_values, len_baseline_values)) + ) + f.Compute(tmp_cell) + cell.bgcolor = tmp_cell.bgcolor class StringMeanResult(Result): - """Mean of string values.""" + """Mean of string values.""" - def _ComputeString(self, cell, values, baseline_values): - if self._AllStringsSame(values): - cell.value = str(values[0]) - else: - cell.value = '?' + def _ComputeString(self, cell, values, baseline_values): + if self._AllStringsSame(values): + cell.value = str(values[0]) + else: + cell.value = "?" class AmeanResult(StringMeanResult): - """Arithmetic mean.""" + """Arithmetic mean.""" - def __init__(self, ignore_min_max=False): - super(AmeanResult, self).__init__() - self.ignore_min_max = ignore_min_max + def __init__(self, ignore_min_max=False): + super(AmeanResult, self).__init__() + self.ignore_min_max = ignore_min_max - def _ComputeFloat(self, cell, values, baseline_values): - if self.ignore_min_max: - values = _RemoveMinMax(cell, values) - cell.value = statistics.mean(values) + def _ComputeFloat(self, cell, values, baseline_values): + if self.ignore_min_max: + values = _RemoveMinMax(cell, values) + cell.value = statistics.mean(values) class RawResult(Result): - """Raw result.""" + """Raw result.""" class IterationResult(Result): - """Iteration result.""" + """Iteration result.""" class MinResult(Result): - """Minimum.""" + """Minimum.""" - def _ComputeFloat(self, cell, values, baseline_values): - cell.value = min(values) + def _ComputeFloat(self, cell, values, baseline_values): + cell.value = min(values) - def _ComputeString(self, cell, values, baseline_values): - if values: - cell.value = min(values) - else: - cell.value = '' + def _ComputeString(self, cell, values, baseline_values): + if values: + cell.value = min(values) + else: + cell.value = "" class MaxResult(Result): - """Maximum.""" + """Maximum.""" - def _ComputeFloat(self, cell, values, baseline_values): - cell.value = max(values) + def _ComputeFloat(self, cell, values, baseline_values): + cell.value = max(values) - def _ComputeString(self, cell, values, baseline_values): - if values: - cell.value = max(values) - else: - cell.value = '' + def _ComputeString(self, cell, values, baseline_values): + if values: + cell.value = max(values) + else: + cell.value = "" class NumericalResult(Result): - """Numerical result.""" + """Numerical result.""" - def _ComputeString(self, cell, values, baseline_values): - cell.value = '?' + def _ComputeString(self, cell, values, baseline_values): + cell.value = "?" class StdResult(NumericalResult): - """Standard deviation.""" + """Standard deviation.""" - def __init__(self, ignore_min_max=False): - super(StdResult, self).__init__() - self.ignore_min_max = ignore_min_max + def __init__(self, ignore_min_max=False): + super(StdResult, self).__init__() + self.ignore_min_max = ignore_min_max - def _ComputeFloat(self, cell, values, baseline_values): - if self.ignore_min_max: - values = _RemoveMinMax(cell, values) - cell.value = statistics.pstdev(values) + def _ComputeFloat(self, cell, values, baseline_values): + if self.ignore_min_max: + values = _RemoveMinMax(cell, values) + cell.value = statistics.pstdev(values) class CoeffVarResult(NumericalResult): - """Standard deviation / Mean""" + """Standard deviation / Mean""" - def __init__(self, ignore_min_max=False): - super(CoeffVarResult, self).__init__() - self.ignore_min_max = ignore_min_max + def __init__(self, ignore_min_max=False): + super(CoeffVarResult, self).__init__() + self.ignore_min_max = ignore_min_max - def _ComputeFloat(self, cell, values, baseline_values): - if self.ignore_min_max: - values = _RemoveMinMax(cell, values) - if statistics.mean(values) != 0.0: - noise = abs(statistics.pstdev(values) / statistics.mean(values)) - else: - noise = 0.0 - cell.value = noise + def _ComputeFloat(self, cell, values, baseline_values): + if self.ignore_min_max: + values = _RemoveMinMax(cell, values) + if statistics.mean(values) != 0.0: + noise = abs(statistics.pstdev(values) / statistics.mean(values)) + else: + noise = 0.0 + cell.value = noise class ComparisonResult(Result): - """Same or Different.""" - - def NeedsBaseline(self): - return True - - def _ComputeString(self, cell, values, baseline_values): - value = None - baseline_value = None - if self._AllStringsSame(values): - value = values[0] - if self._AllStringsSame(baseline_values): - baseline_value = baseline_values[0] - if value is not None and baseline_value is not None: - if value == baseline_value: - cell.value = 'SAME' - else: - cell.value = 'DIFFERENT' - else: - cell.value = '?' + """Same or Different.""" + + def NeedsBaseline(self): + return True + + def _ComputeString(self, cell, values, baseline_values): + value = None + baseline_value = None + if self._AllStringsSame(values): + value = values[0] + if self._AllStringsSame(baseline_values): + baseline_value = baseline_values[0] + if value is not None and baseline_value is not None: + if value == baseline_value: + cell.value = "SAME" + else: + cell.value = "DIFFERENT" + else: + cell.value = "?" class PValueResult(ComparisonResult): - """P-value.""" + """P-value.""" - def __init__(self, ignore_min_max=False): - super(PValueResult, self).__init__() - self.ignore_min_max = ignore_min_max + def __init__(self, ignore_min_max=False): + super(PValueResult, self).__init__() + self.ignore_min_max = ignore_min_max - def _ComputeFloat(self, cell, values, baseline_values): - if self.ignore_min_max: - values = _RemoveMinMax(cell, values) - baseline_values = _RemoveMinMax(cell, baseline_values) - if len(values) < 2 or len(baseline_values) < 2: - cell.value = float('nan') - return - _, cell.value = scipy.stats.ttest_ind(values, baseline_values) + def _ComputeFloat(self, cell, values, baseline_values): + if self.ignore_min_max: + values = _RemoveMinMax(cell, values) + baseline_values = _RemoveMinMax(cell, baseline_values) + if len(values) < 2 or len(baseline_values) < 2: + cell.value = float("nan") + return + _, cell.value = scipy.stats.ttest_ind(values, baseline_values) - def _ComputeString(self, cell, values, baseline_values): - return float('nan') + def _ComputeString(self, cell, values, baseline_values): + return float("nan") class KeyAwareComparisonResult(ComparisonResult): - """Automatic key aware comparison.""" - - def _IsLowerBetter(self, key): - # Units in histograms should include directions - if 'smallerIsBetter' in key: - return True - if 'biggerIsBetter' in key: - return False - - # For units in chartjson: - # TODO(llozano): Trying to guess direction by looking at the name of the - # test does not seem like a good idea. Test frameworks should provide this - # info explicitly. I believe Telemetry has this info. Need to find it out. - # - # Below are some test names for which we are not sure what the - # direction is. - # - # For these we dont know what the direction is. But, since we dont - # specify anything, crosperf will assume higher is better: - # --percent_impl_scrolled--percent_impl_scrolled--percent - # --solid_color_tiles_analyzed--solid_color_tiles_analyzed--count - # --total_image_cache_hit_count--total_image_cache_hit_count--count - # --total_texture_upload_time_by_url - # - # About these we are doubtful but we made a guess: - # --average_num_missing_tiles_by_url--*--units (low is good) - # --experimental_mean_frame_time_by_url--*--units (low is good) - # --experimental_median_frame_time_by_url--*--units (low is good) - # --texture_upload_count--texture_upload_count--count (high is good) - # --total_deferred_image_decode_count--count (low is good) - # --total_tiles_analyzed--total_tiles_analyzed--count (high is good) - lower_is_better_keys = [ - 'milliseconds', 'ms_', 'seconds_', 'KB', 'rdbytes', 'wrbytes', - 'dropped_percent', '(ms)', '(seconds)', '--ms', - '--average_num_missing_tiles', '--experimental_jank', - '--experimental_mean_frame', '--experimental_median_frame_time', - '--total_deferred_image_decode_count', '--seconds', 'samples', 'bytes' - ] - - return any([l in key for l in lower_is_better_keys]) - - def _InvertIfLowerIsBetter(self, cell): - if self._IsLowerBetter(cell.name): - if cell.value: - cell.value = 1.0 / cell.value + """Automatic key aware comparison.""" + + def _IsLowerBetter(self, key): + # Units in histograms should include directions + if "smallerIsBetter" in key: + return True + if "biggerIsBetter" in key: + return False + + # For units in chartjson: + # TODO(llozano): Trying to guess direction by looking at the name of the + # test does not seem like a good idea. Test frameworks should provide this + # info explicitly. I believe Telemetry has this info. Need to find it out. + # + # Below are some test names for which we are not sure what the + # direction is. + # + # For these we dont know what the direction is. But, since we dont + # specify anything, crosperf will assume higher is better: + # --percent_impl_scrolled--percent_impl_scrolled--percent + # --solid_color_tiles_analyzed--solid_color_tiles_analyzed--count + # --total_image_cache_hit_count--total_image_cache_hit_count--count + # --total_texture_upload_time_by_url + # + # About these we are doubtful but we made a guess: + # --average_num_missing_tiles_by_url--*--units (low is good) + # --experimental_mean_frame_time_by_url--*--units (low is good) + # --experimental_median_frame_time_by_url--*--units (low is good) + # --texture_upload_count--texture_upload_count--count (high is good) + # --total_deferred_image_decode_count--count (low is good) + # --total_tiles_analyzed--total_tiles_analyzed--count (high is good) + lower_is_better_keys = [ + "milliseconds", + "ms_", + "seconds_", + "KB", + "rdbytes", + "wrbytes", + "dropped_percent", + "(ms)", + "(seconds)", + "--ms", + "--average_num_missing_tiles", + "--experimental_jank", + "--experimental_mean_frame", + "--experimental_median_frame_time", + "--total_deferred_image_decode_count", + "--seconds", + "samples", + "bytes", + ] + + return any([l in key for l in lower_is_better_keys]) + + def _InvertIfLowerIsBetter(self, cell): + if self._IsLowerBetter(cell.name): + if cell.value: + cell.value = 1.0 / cell.value class AmeanRatioResult(KeyAwareComparisonResult): - """Ratio of arithmetic means of values vs. baseline values.""" - - def __init__(self, ignore_min_max=False): - super(AmeanRatioResult, self).__init__() - self.ignore_min_max = ignore_min_max - - def _ComputeFloat(self, cell, values, baseline_values): - if self.ignore_min_max: - values = _RemoveMinMax(cell, values) - baseline_values = _RemoveMinMax(cell, baseline_values) - - baseline_mean = statistics.mean(baseline_values) - values_mean = statistics.mean(values) - if baseline_mean != 0: - cell.value = values_mean / baseline_mean - elif values_mean != 0: - cell.value = 0.00 - # cell.value = 0 means the values and baseline_values have big difference - else: - cell.value = 1.00 - # no difference if both values and baseline_values are 0 + """Ratio of arithmetic means of values vs. baseline values.""" + + def __init__(self, ignore_min_max=False): + super(AmeanRatioResult, self).__init__() + self.ignore_min_max = ignore_min_max + + def _ComputeFloat(self, cell, values, baseline_values): + if self.ignore_min_max: + values = _RemoveMinMax(cell, values) + baseline_values = _RemoveMinMax(cell, baseline_values) + + baseline_mean = statistics.mean(baseline_values) + values_mean = statistics.mean(values) + if baseline_mean != 0: + cell.value = values_mean / baseline_mean + elif values_mean != 0: + cell.value = 0.00 + # cell.value = 0 means the values and baseline_values have big difference + else: + cell.value = 1.00 + # no difference if both values and baseline_values are 0 class GmeanRatioResult(KeyAwareComparisonResult): - """Ratio of geometric means of values vs. baseline values.""" - - def __init__(self, ignore_min_max=False): - super(GmeanRatioResult, self).__init__() - self.ignore_min_max = ignore_min_max - - def _ComputeFloat(self, cell, values, baseline_values): - if self.ignore_min_max: - values = _RemoveMinMax(cell, values) - baseline_values = _RemoveMinMax(cell, baseline_values) - if self._GetGmean(baseline_values) != 0: - cell.value = self._GetGmean(values) / self._GetGmean(baseline_values) - elif self._GetGmean(values) != 0: - cell.value = 0.00 - else: - cell.value = 1.00 + """Ratio of geometric means of values vs. baseline values.""" + + def __init__(self, ignore_min_max=False): + super(GmeanRatioResult, self).__init__() + self.ignore_min_max = ignore_min_max + + def _ComputeFloat(self, cell, values, baseline_values): + if self.ignore_min_max: + values = _RemoveMinMax(cell, values) + baseline_values = _RemoveMinMax(cell, baseline_values) + if self._GetGmean(baseline_values) != 0: + cell.value = self._GetGmean(values) / self._GetGmean( + baseline_values + ) + elif self._GetGmean(values) != 0: + cell.value = 0.00 + else: + cell.value = 1.00 class Color(object): - """Class that represents color in RGBA format.""" - - def __init__(self, r=0, g=0, b=0, a=0): - self.r = r - self.g = g - self.b = b - self.a = a - - def __str__(self): - return 'r: %s g: %s: b: %s: a: %s' % (self.r, self.g, self.b, self.a) + """Class that represents color in RGBA format.""" + + def __init__(self, r=0, g=0, b=0, a=0): + self.r = r + self.g = g + self.b = b + self.a = a + + def __str__(self): + return "r: %s g: %s: b: %s: a: %s" % (self.r, self.g, self.b, self.a) + + def Round(self): + """Round RGBA values to the nearest integer.""" + self.r = int(self.r) + self.g = int(self.g) + self.b = int(self.b) + self.a = int(self.a) + + def GetRGB(self): + """Get a hex representation of the color.""" + return "%02x%02x%02x" % (self.r, self.g, self.b) + + @classmethod + def Lerp(cls, ratio, a, b): + """Perform linear interpolation between two colors. + + Args: + ratio: The ratio to use for linear polation. + a: The first color object (used when ratio is 0). + b: The second color object (used when ratio is 1). + + Returns: + Linearly interpolated color. + """ + ret = cls() + ret.r = (b.r - a.r) * ratio + a.r + ret.g = (b.g - a.g) * ratio + a.g + ret.b = (b.b - a.b) * ratio + a.b + ret.a = (b.a - a.a) * ratio + a.a + return ret - def Round(self): - """Round RGBA values to the nearest integer.""" - self.r = int(self.r) - self.g = int(self.g) - self.b = int(self.b) - self.a = int(self.a) - def GetRGB(self): - """Get a hex representation of the color.""" - return '%02x%02x%02x' % (self.r, self.g, self.b) +class Format(object): + """A class that represents the format of a column.""" - @classmethod - def Lerp(cls, ratio, a, b): - """Perform linear interpolation between two colors. + def __init__(self): + pass - Args: - ratio: The ratio to use for linear polation. - a: The first color object (used when ratio is 0). - b: The second color object (used when ratio is 1). + def Compute(self, cell): + """Computes the attributes of a cell based on its value. - Returns: - Linearly interpolated color. - """ - ret = cls() - ret.r = (b.r - a.r) * ratio + a.r - ret.g = (b.g - a.g) * ratio + a.g - ret.b = (b.b - a.b) * ratio + a.b - ret.a = (b.a - a.a) * ratio + a.a - return ret + Attributes typically are color, width, etc. + Args: + cell: The cell whose attributes are to be populated. + """ + if cell.value is None: + cell.string_value = "" + if isinstance(cell.value, float): + self._ComputeFloat(cell) + else: + self._ComputeString(cell) -class Format(object): - """A class that represents the format of a column.""" + def _ComputeFloat(self, cell): + cell.string_value = "{0:.2f}".format(cell.value) - def __init__(self): - pass + def _ComputeString(self, cell): + cell.string_value = str(cell.value) - def Compute(self, cell): - """Computes the attributes of a cell based on its value. + def _GetColor(self, value, low, mid, high, power=6, mid_value=1.0): + min_value = 0.0 + max_value = 2.0 + if math.isnan(value): + return mid + if value > mid_value: + value = max_value - mid_value / value - Attributes typically are color, width, etc. + return self._GetColorBetweenRange( + value, min_value, mid_value, max_value, low, mid, high, power + ) - Args: - cell: The cell whose attributes are to be populated. - """ - if cell.value is None: - cell.string_value = '' - if isinstance(cell.value, float): - self._ComputeFloat(cell) - else: - self._ComputeString(cell) - - def _ComputeFloat(self, cell): - cell.string_value = '{0:.2f}'.format(cell.value) - - def _ComputeString(self, cell): - cell.string_value = str(cell.value) - - def _GetColor(self, value, low, mid, high, power=6, mid_value=1.0): - min_value = 0.0 - max_value = 2.0 - if math.isnan(value): - return mid - if value > mid_value: - value = max_value - mid_value / value - - return self._GetColorBetweenRange(value, min_value, mid_value, max_value, - low, mid, high, power) - - def _GetColorBetweenRange(self, value, min_value, mid_value, max_value, - low_color, mid_color, high_color, power): - assert value <= max_value - assert value >= min_value - if value > mid_value: - value = (max_value - value) / (max_value - mid_value) - value **= power - ret = Color.Lerp(value, high_color, mid_color) - else: - value = (value - min_value) / (mid_value - min_value) - value **= power - ret = Color.Lerp(value, low_color, mid_color) - ret.Round() - return ret + def _GetColorBetweenRange( + self, + value, + min_value, + mid_value, + max_value, + low_color, + mid_color, + high_color, + power, + ): + assert value <= max_value + assert value >= min_value + if value > mid_value: + value = (max_value - value) / (max_value - mid_value) + value **= power + ret = Color.Lerp(value, high_color, mid_color) + else: + value = (value - min_value) / (mid_value - min_value) + value **= power + ret = Color.Lerp(value, low_color, mid_color) + ret.Round() + return ret class PValueFormat(Format): - """Formatting for p-value.""" + """Formatting for p-value.""" - def _ComputeFloat(self, cell): - cell.string_value = '%0.2f' % float(cell.value) - if float(cell.value) < 0.05: - cell.bgcolor = self._GetColor( - cell.value, - Color(255, 255, 0, 0), - Color(255, 255, 255, 0), - Color(255, 255, 255, 0), - mid_value=0.05, - power=1) + def _ComputeFloat(self, cell): + cell.string_value = "%0.2f" % float(cell.value) + if float(cell.value) < 0.05: + cell.bgcolor = self._GetColor( + cell.value, + Color(255, 255, 0, 0), + Color(255, 255, 255, 0), + Color(255, 255, 255, 0), + mid_value=0.05, + power=1, + ) class WeightFormat(Format): - """Formatting for weight in cwp mode.""" + """Formatting for weight in cwp mode.""" - def _ComputeFloat(self, cell): - cell.string_value = '%0.4f' % float(cell.value) + def _ComputeFloat(self, cell): + cell.string_value = "%0.4f" % float(cell.value) class StorageFormat(Format): - """Format the cell as a storage number. + """Format the cell as a storage number. - Examples: - If the cell contains a value of 1024, the string_value will be 1.0K. - """ - - def _ComputeFloat(self, cell): - base = 1024 - suffices = ['K', 'M', 'G'] - v = float(cell.value) - current = 0 - while v >= base**(current + 1) and current < len(suffices): - current += 1 + Examples: + If the cell contains a value of 1024, the string_value will be 1.0K. + """ - if current: - divisor = base**current - cell.string_value = '%1.1f%s' % ((v / divisor), suffices[current - 1]) - else: - cell.string_value = str(cell.value) + def _ComputeFloat(self, cell): + base = 1024 + suffices = ["K", "M", "G"] + v = float(cell.value) + current = 0 + while v >= base ** (current + 1) and current < len(suffices): + current += 1 + + if current: + divisor = base ** current + cell.string_value = "%1.1f%s" % ( + (v / divisor), + suffices[current - 1], + ) + else: + cell.string_value = str(cell.value) class CoeffVarFormat(Format): - """Format the cell as a percent. + """Format the cell as a percent. - Examples: - If the cell contains a value of 1.5, the string_value will be +150%. - """ + Examples: + If the cell contains a value of 1.5, the string_value will be +150%. + """ - def _ComputeFloat(self, cell): - cell.string_value = '%1.1f%%' % (float(cell.value) * 100) - cell.color = self._GetColor( - cell.value, - Color(0, 255, 0, 0), - Color(0, 0, 0, 0), - Color(255, 0, 0, 0), - mid_value=0.02, - power=1) + def _ComputeFloat(self, cell): + cell.string_value = "%1.1f%%" % (float(cell.value) * 100) + cell.color = self._GetColor( + cell.value, + Color(0, 255, 0, 0), + Color(0, 0, 0, 0), + Color(255, 0, 0, 0), + mid_value=0.02, + power=1, + ) class PercentFormat(Format): - """Format the cell as a percent. + """Format the cell as a percent. - Examples: - If the cell contains a value of 1.5, the string_value will be +50%. - """ + Examples: + If the cell contains a value of 1.5, the string_value will be +50%. + """ - def _ComputeFloat(self, cell): - cell.string_value = '%+1.1f%%' % ((float(cell.value) - 1) * 100) - cell.color = self._GetColor(cell.value, Color(255, 0, 0, 0), - Color(0, 0, 0, 0), Color(0, 255, 0, 0)) + def _ComputeFloat(self, cell): + cell.string_value = "%+1.1f%%" % ((float(cell.value) - 1) * 100) + cell.color = self._GetColor( + cell.value, + Color(255, 0, 0, 0), + Color(0, 0, 0, 0), + Color(0, 255, 0, 0), + ) class RatioFormat(Format): - """Format the cell as a ratio. + """Format the cell as a ratio. - Examples: - If the cell contains a value of 1.5642, the string_value will be 1.56. - """ + Examples: + If the cell contains a value of 1.5642, the string_value will be 1.56. + """ - def _ComputeFloat(self, cell): - cell.string_value = '%+1.1f%%' % ((cell.value - 1) * 100) - cell.color = self._GetColor(cell.value, Color(255, 0, 0, 0), - Color(0, 0, 0, 0), Color(0, 255, 0, 0)) + def _ComputeFloat(self, cell): + cell.string_value = "%+1.1f%%" % ((cell.value - 1) * 100) + cell.color = self._GetColor( + cell.value, + Color(255, 0, 0, 0), + Color(0, 0, 0, 0), + Color(0, 255, 0, 0), + ) class ColorBoxFormat(Format): - """Format the cell as a color box. + """Format the cell as a color box. - Examples: - If the cell contains a value of 1.5, it will get a green color. - If the cell contains a value of 0.5, it will get a red color. - The intensity of the green/red will be determined by how much above or below - 1.0 the value is. - """ + Examples: + If the cell contains a value of 1.5, it will get a green color. + If the cell contains a value of 0.5, it will get a red color. + The intensity of the green/red will be determined by how much above or below + 1.0 the value is. + """ - def _ComputeFloat(self, cell): - cell.string_value = '--' - bgcolor = self._GetColor(cell.value, Color(255, 0, 0, 0), - Color(255, 255, 255, 0), Color(0, 255, 0, 0)) - cell.bgcolor = bgcolor - cell.color = bgcolor + def _ComputeFloat(self, cell): + cell.string_value = "--" + bgcolor = self._GetColor( + cell.value, + Color(255, 0, 0, 0), + Color(255, 255, 255, 0), + Color(0, 255, 0, 0), + ) + cell.bgcolor = bgcolor + cell.color = bgcolor class Cell(object): - """A class to represent a cell in a table. - - Attributes: - value: The raw value of the cell. - color: The color of the cell. - bgcolor: The background color of the cell. - string_value: The string value of the cell. - suffix: A string suffix to be attached to the value when displaying. - prefix: A string prefix to be attached to the value when displaying. - color_row: Indicates whether the whole row is to inherit this cell's color. - bgcolor_row: Indicates whether the whole row is to inherit this cell's - bgcolor. - width: Optional specifier to make a column narrower than the usual width. - The usual width of a column is the max of all its cells widths. - colspan: Set the colspan of the cell in the HTML table, this is used for - table headers. Default value is 1. - name: the test name of the cell. - header: Whether this is a header in html. - """ - - def __init__(self): - self.value = None - self.color = None - self.bgcolor = None - self.string_value = None - self.suffix = None - self.prefix = None - # Entire row inherits this color. - self.color_row = False - self.bgcolor_row = False - self.width = 0 - self.colspan = 1 - self.name = None - self.header = False - - def __str__(self): - l = [] - l.append('value: %s' % self.value) - l.append('string_value: %s' % self.string_value) - return ' '.join(l) + """A class to represent a cell in a table. + + Attributes: + value: The raw value of the cell. + color: The color of the cell. + bgcolor: The background color of the cell. + string_value: The string value of the cell. + suffix: A string suffix to be attached to the value when displaying. + prefix: A string prefix to be attached to the value when displaying. + color_row: Indicates whether the whole row is to inherit this cell's color. + bgcolor_row: Indicates whether the whole row is to inherit this cell's + bgcolor. + width: Optional specifier to make a column narrower than the usual width. + The usual width of a column is the max of all its cells widths. + colspan: Set the colspan of the cell in the HTML table, this is used for + table headers. Default value is 1. + name: the test name of the cell. + header: Whether this is a header in html. + """ + + def __init__(self): + self.value = None + self.color = None + self.bgcolor = None + self.string_value = None + self.suffix = None + self.prefix = None + # Entire row inherits this color. + self.color_row = False + self.bgcolor_row = False + self.width = 0 + self.colspan = 1 + self.name = None + self.header = False + + def __str__(self): + l = [] + l.append("value: %s" % self.value) + l.append("string_value: %s" % self.string_value) + return " ".join(l) class Column(object): - """Class representing a column in a table. + """Class representing a column in a table. - Attributes: - result: an object of the Result class. - fmt: an object of the Format class. - """ + Attributes: + result: an object of the Result class. + fmt: an object of the Format class. + """ - def __init__(self, result, fmt, name=''): - self.result = result - self.fmt = fmt - self.name = name + def __init__(self, result, fmt, name=""): + self.result = result + self.fmt = fmt + self.name = name # Takes in: @@ -1033,536 +1093,561 @@ class Column(object): # ["k", avg("v", "v2"), stddev("v", "v2"), etc.]] # according to format string class TableFormatter(object): - """Class to convert a plain table into a cell-table. + """Class to convert a plain table into a cell-table. - This class takes in a table generated by TableGenerator and a list of column - formats to apply to the table and returns a table of cells. - """ + This class takes in a table generated by TableGenerator and a list of column + formats to apply to the table and returns a table of cells. + """ - def __init__(self, table, columns, samples_table=False): - """The constructor takes in a table and a list of columns. + def __init__(self, table, columns, samples_table=False): + """The constructor takes in a table and a list of columns. + + Args: + table: A list of lists of values. + columns: A list of column containing what to produce and how to format + it. + samples_table: A flag to check whether we are generating a table of + samples in CWP apporximation mode. + """ + self._table = table + self._columns = columns + self._samples_table = samples_table + self._table_columns = [] + self._out_table = [] + + def GenerateCellTable(self, table_type): + row_index = 0 + all_failed = False + + for row in self._table[1:]: + # If we are generating samples_table, the second value will be weight + # rather than values. + start_col = 2 if self._samples_table else 1 + # It does not make sense to put retval in the summary table. + if str(row[0]) == "retval" and table_type == "summary": + # Check to see if any runs passed, and update all_failed. + all_failed = True + for values in row[start_col:]: + if 0 in values: + all_failed = False + continue + key = Cell() + key.string_value = str(row[0]) + out_row = [key] + if self._samples_table: + # Add one column for weight if in samples_table mode + weight = Cell() + weight.value = row[1] + f = WeightFormat() + f.Compute(weight) + out_row.append(weight) + baseline = None + for results in row[start_col:]: + column_start = 0 + values = None + # If generating sample table, we will split a tuple of iterations info + # from the results + if isinstance(results, tuple): + it, values = results + column_start = 1 + cell = Cell() + cell.string_value = "[%d: %d]" % (it[0], it[1]) + out_row.append(cell) + if not row_index: + self._table_columns.append(self._columns[0]) + else: + values = results + # Parse each column + for column in self._columns[column_start:]: + cell = Cell() + cell.name = key.string_value + if ( + not column.result.NeedsBaseline() + or baseline is not None + ): + column.result.Compute(cell, values, baseline) + column.fmt.Compute(cell) + out_row.append(cell) + if not row_index: + self._table_columns.append(column) + + if baseline is None: + baseline = values + self._out_table.append(out_row) + row_index += 1 + + # If this is a summary table, and the only row in it is 'retval', and + # all the test runs failed, we need to a 'Results' row to the output + # table. + if table_type == "summary" and all_failed and len(self._table) == 2: + labels_row = self._table[0] + key = Cell() + key.string_value = "Results" + out_row = [key] + baseline = None + for _ in labels_row[1:]: + for column in self._columns: + cell = Cell() + cell.name = key.string_value + column.result.Compute(cell, ["Fail"], baseline) + column.fmt.Compute(cell) + out_row.append(cell) + if not row_index: + self._table_columns.append(column) + self._out_table.append(out_row) + + def AddColumnName(self): + """Generate Column name at the top of table.""" + key = Cell() + key.header = True + key.string_value = "Keys" if not self._samples_table else "Benchmarks" + header = [key] + if self._samples_table: + weight = Cell() + weight.header = True + weight.string_value = "Weights" + header.append(weight) + for column in self._table_columns: + cell = Cell() + cell.header = True + if column.name: + cell.string_value = column.name + else: + result_name = column.result.__class__.__name__ + format_name = column.fmt.__class__.__name__ - Args: - table: A list of lists of values. - columns: A list of column containing what to produce and how to format - it. - samples_table: A flag to check whether we are generating a table of - samples in CWP apporximation mode. - """ - self._table = table - self._columns = columns - self._samples_table = samples_table - self._table_columns = [] - self._out_table = [] - - def GenerateCellTable(self, table_type): - row_index = 0 - all_failed = False - - for row in self._table[1:]: - # If we are generating samples_table, the second value will be weight - # rather than values. - start_col = 2 if self._samples_table else 1 - # It does not make sense to put retval in the summary table. - if str(row[0]) == 'retval' and table_type == 'summary': - # Check to see if any runs passed, and update all_failed. - all_failed = True - for values in row[start_col:]: - if 0 in values: - all_failed = False - continue - key = Cell() - key.string_value = str(row[0]) - out_row = [key] - if self._samples_table: - # Add one column for weight if in samples_table mode - weight = Cell() - weight.value = row[1] - f = WeightFormat() - f.Compute(weight) - out_row.append(weight) - baseline = None - for results in row[start_col:]: - column_start = 0 - values = None - # If generating sample table, we will split a tuple of iterations info - # from the results - if isinstance(results, tuple): - it, values = results - column_start = 1 - cell = Cell() - cell.string_value = '[%d: %d]' % (it[0], it[1]) - out_row.append(cell) - if not row_index: - self._table_columns.append(self._columns[0]) - else: - values = results - # Parse each column - for column in self._columns[column_start:]: - cell = Cell() - cell.name = key.string_value - if not column.result.NeedsBaseline() or baseline is not None: - column.result.Compute(cell, values, baseline) - column.fmt.Compute(cell) - out_row.append(cell) - if not row_index: - self._table_columns.append(column) - - if baseline is None: - baseline = values - self._out_table.append(out_row) - row_index += 1 - - # If this is a summary table, and the only row in it is 'retval', and - # all the test runs failed, we need to a 'Results' row to the output - # table. - if table_type == 'summary' and all_failed and len(self._table) == 2: - labels_row = self._table[0] - key = Cell() - key.string_value = 'Results' - out_row = [key] - baseline = None - for _ in labels_row[1:]: - for column in self._columns: - cell = Cell() - cell.name = key.string_value - column.result.Compute(cell, ['Fail'], baseline) - column.fmt.Compute(cell) - out_row.append(cell) - if not row_index: - self._table_columns.append(column) - self._out_table.append(out_row) - - def AddColumnName(self): - """Generate Column name at the top of table.""" - key = Cell() - key.header = True - key.string_value = 'Keys' if not self._samples_table else 'Benchmarks' - header = [key] - if self._samples_table: - weight = Cell() - weight.header = True - weight.string_value = 'Weights' - header.append(weight) - for column in self._table_columns: - cell = Cell() - cell.header = True - if column.name: - cell.string_value = column.name - else: - result_name = column.result.__class__.__name__ - format_name = column.fmt.__class__.__name__ - - cell.string_value = '%s %s' % ( - result_name.replace('Result', ''), - format_name.replace('Format', ''), - ) + cell.string_value = "%s %s" % ( + result_name.replace("Result", ""), + format_name.replace("Format", ""), + ) - header.append(cell) - - self._out_table = [header] + self._out_table - - def AddHeader(self, s): - """Put additional string on the top of the table.""" - cell = Cell() - cell.header = True - cell.string_value = str(s) - header = [cell] - colspan = max(1, max(len(row) for row in self._table)) - cell.colspan = colspan - self._out_table = [header] + self._out_table - - def GetPassesAndFails(self, values): - passes = 0 - fails = 0 - for val in values: - if val == 0: - passes = passes + 1 - else: - fails = fails + 1 - return passes, fails - - def AddLabelName(self): - """Put label on the top of the table.""" - top_header = [] - base_colspan = len( - [c for c in self._columns if not c.result.NeedsBaseline()]) - compare_colspan = len(self._columns) - # Find the row with the key 'retval', if it exists. This - # will be used to calculate the number of iterations that passed and - # failed for each image label. - retval_row = None - for row in self._table: - if row[0] == 'retval': - retval_row = row - # The label is organized as follows - # "keys" label_base, label_comparison1, label_comparison2 - # The first cell has colspan 1, the second is base_colspan - # The others are compare_colspan - column_position = 0 - for label in self._table[0]: - cell = Cell() - cell.header = True - # Put the number of pass/fail iterations in the image label header. - if column_position > 0 and retval_row: - retval_values = retval_row[column_position] - if isinstance(retval_values, list): - passes, fails = self.GetPassesAndFails(retval_values) - cell.string_value = str(label) + ' (pass:%d fail:%d)' % (passes, - fails) - else: - cell.string_value = str(label) - else: - cell.string_value = str(label) - if top_header: - if not self._samples_table or (self._samples_table and - len(top_header) == 2): - cell.colspan = base_colspan - if len(top_header) > 1: - if not self._samples_table or (self._samples_table and - len(top_header) > 2): - cell.colspan = compare_colspan - top_header.append(cell) - column_position = column_position + 1 - self._out_table = [top_header] + self._out_table - - def _PrintOutTable(self): - o = '' - for row in self._out_table: - for cell in row: - o += str(cell) + ' ' - o += '\n' - print(o) - - def GetCellTable(self, table_type='full', headers=True): - """Function to return a table of cells. - - The table (list of lists) is converted into a table of cells by this - function. + header.append(cell) - Args: - table_type: Can be 'full' or 'summary' - headers: A boolean saying whether we want default headers + self._out_table = [header] + self._out_table - Returns: - A table of cells with each cell having the properties and string values as - requiested by the columns passed in the constructor. - """ - # Generate the cell table, creating a list of dynamic columns on the fly. - if not self._out_table: - self.GenerateCellTable(table_type) - if headers: - self.AddColumnName() - self.AddLabelName() - return self._out_table + def AddHeader(self, s): + """Put additional string on the top of the table.""" + cell = Cell() + cell.header = True + cell.string_value = str(s) + header = [cell] + colspan = max(1, max(len(row) for row in self._table)) + cell.colspan = colspan + self._out_table = [header] + self._out_table + + def GetPassesAndFails(self, values): + passes = 0 + fails = 0 + for val in values: + if val == 0: + passes = passes + 1 + else: + fails = fails + 1 + return passes, fails + + def AddLabelName(self): + """Put label on the top of the table.""" + top_header = [] + base_colspan = len( + [c for c in self._columns if not c.result.NeedsBaseline()] + ) + compare_colspan = len(self._columns) + # Find the row with the key 'retval', if it exists. This + # will be used to calculate the number of iterations that passed and + # failed for each image label. + retval_row = None + for row in self._table: + if row[0] == "retval": + retval_row = row + # The label is organized as follows + # "keys" label_base, label_comparison1, label_comparison2 + # The first cell has colspan 1, the second is base_colspan + # The others are compare_colspan + column_position = 0 + for label in self._table[0]: + cell = Cell() + cell.header = True + # Put the number of pass/fail iterations in the image label header. + if column_position > 0 and retval_row: + retval_values = retval_row[column_position] + if isinstance(retval_values, list): + passes, fails = self.GetPassesAndFails(retval_values) + cell.string_value = str(label) + " (pass:%d fail:%d)" % ( + passes, + fails, + ) + else: + cell.string_value = str(label) + else: + cell.string_value = str(label) + if top_header: + if not self._samples_table or ( + self._samples_table and len(top_header) == 2 + ): + cell.colspan = base_colspan + if len(top_header) > 1: + if not self._samples_table or ( + self._samples_table and len(top_header) > 2 + ): + cell.colspan = compare_colspan + top_header.append(cell) + column_position = column_position + 1 + self._out_table = [top_header] + self._out_table + + def _PrintOutTable(self): + o = "" + for row in self._out_table: + for cell in row: + o += str(cell) + " " + o += "\n" + print(o) + + def GetCellTable(self, table_type="full", headers=True): + """Function to return a table of cells. + + The table (list of lists) is converted into a table of cells by this + function. + + Args: + table_type: Can be 'full' or 'summary' + headers: A boolean saying whether we want default headers + + Returns: + A table of cells with each cell having the properties and string values as + requiested by the columns passed in the constructor. + """ + # Generate the cell table, creating a list of dynamic columns on the fly. + if not self._out_table: + self.GenerateCellTable(table_type) + if headers: + self.AddColumnName() + self.AddLabelName() + return self._out_table class TablePrinter(object): - """Class to print a cell table to the console, file or html.""" - PLAIN = 0 - CONSOLE = 1 - HTML = 2 - TSV = 3 - EMAIL = 4 - - def __init__(self, table, output_type): - """Constructor that stores the cell table and output type.""" - self._table = table - self._output_type = output_type - self._row_styles = [] - self._column_styles = [] - - # Compute whole-table properties like max-size, etc. - def _ComputeStyle(self): - self._row_styles = [] - for row in self._table: - row_style = Cell() - for cell in row: - if cell.color_row: - assert cell.color, 'Cell color not set but color_row set!' - assert not row_style.color, 'Multiple row_style.colors found!' - row_style.color = cell.color - if cell.bgcolor_row: - assert cell.bgcolor, 'Cell bgcolor not set but bgcolor_row set!' - assert not row_style.bgcolor, 'Multiple row_style.bgcolors found!' - row_style.bgcolor = cell.bgcolor - self._row_styles.append(row_style) - - self._column_styles = [] - if len(self._table) < 2: - return - - for i in range(max(len(row) for row in self._table)): - column_style = Cell() - for row in self._table: - if not any([cell.colspan != 1 for cell in row]): - column_style.width = max(column_style.width, len(row[i].string_value)) - self._column_styles.append(column_style) - - def _GetBGColorFix(self, color): - if self._output_type == self.CONSOLE: - prefix = misc.rgb2short(color.r, color.g, color.b) - # pylint: disable=anomalous-backslash-in-string - prefix = '\033[48;5;%sm' % prefix - suffix = '\033[0m' - elif self._output_type in [self.EMAIL, self.HTML]: - rgb = color.GetRGB() - prefix = (''.format(rgb)) - suffix = '' - elif self._output_type in [self.PLAIN, self.TSV]: - prefix = '' - suffix = '' - return prefix, suffix - - def _GetColorFix(self, color): - if self._output_type == self.CONSOLE: - prefix = misc.rgb2short(color.r, color.g, color.b) - # pylint: disable=anomalous-backslash-in-string - prefix = '\033[38;5;%sm' % prefix - suffix = '\033[0m' - elif self._output_type in [self.EMAIL, self.HTML]: - rgb = color.GetRGB() - prefix = ''.format(rgb) - suffix = '' - elif self._output_type in [self.PLAIN, self.TSV]: - prefix = '' - suffix = '' - return prefix, suffix - - def Print(self): - """Print the table to a console, html, etc. - - Returns: - A string that contains the desired representation of the table. - """ - self._ComputeStyle() - return self._GetStringValue() - - def _GetCellValue(self, i, j): - cell = self._table[i][j] - out = cell.string_value - raw_width = len(out) - - if cell.color: - p, s = self._GetColorFix(cell.color) - out = '%s%s%s' % (p, out, s) - - if cell.bgcolor: - p, s = self._GetBGColorFix(cell.bgcolor) - out = '%s%s%s' % (p, out, s) - - if self._output_type in [self.PLAIN, self.CONSOLE, self.EMAIL]: - if cell.width: - width = cell.width - else: - if self._column_styles: - width = self._column_styles[j].width - else: - width = len(cell.string_value) - if cell.colspan > 1: - width = 0 - start = 0 - for k in range(j): - start += self._table[i][k].colspan - for k in range(cell.colspan): - width += self._column_styles[start + k].width - if width > raw_width: - padding = ('%' + str(width - raw_width) + 's') % '' - out = padding + out - - if self._output_type == self.HTML: - if cell.header: - tag = 'th' - else: - tag = 'td' - out = '<{0} colspan = "{2}"> {1} '.format(tag, out, cell.colspan) - - return out - - def _GetHorizontalSeparator(self): - if self._output_type in [self.CONSOLE, self.PLAIN, self.EMAIL]: - return ' ' - if self._output_type == self.HTML: - return '' - if self._output_type == self.TSV: - return '\t' - - def _GetVerticalSeparator(self): - if self._output_type in [self.PLAIN, self.CONSOLE, self.TSV, self.EMAIL]: - return '\n' - if self._output_type == self.HTML: - return '\n' - - def _GetPrefix(self): - if self._output_type in [self.PLAIN, self.CONSOLE, self.TSV, self.EMAIL]: - return '' - if self._output_type == self.HTML: - return '

\n' - - def _GetSuffix(self): - if self._output_type in [self.PLAIN, self.CONSOLE, self.TSV, self.EMAIL]: - return '' - if self._output_type == self.HTML: - return '\n
' - - def _GetStringValue(self): - o = '' - o += self._GetPrefix() - for i in range(len(self._table)): - row = self._table[i] - # Apply row color and bgcolor. - p = s = bgp = bgs = '' - if self._row_styles[i].bgcolor: - bgp, bgs = self._GetBGColorFix(self._row_styles[i].bgcolor) - if self._row_styles[i].color: - p, s = self._GetColorFix(self._row_styles[i].color) - o += p + bgp - for j in range(len(row)): - out = self._GetCellValue(i, j) - o += out + self._GetHorizontalSeparator() - o += s + bgs - o += self._GetVerticalSeparator() - o += self._GetSuffix() - return o + """Class to print a cell table to the console, file or html.""" + + PLAIN = 0 + CONSOLE = 1 + HTML = 2 + TSV = 3 + EMAIL = 4 + + def __init__(self, table, output_type): + """Constructor that stores the cell table and output type.""" + self._table = table + self._output_type = output_type + self._row_styles = [] + self._column_styles = [] + + # Compute whole-table properties like max-size, etc. + def _ComputeStyle(self): + self._row_styles = [] + for row in self._table: + row_style = Cell() + for cell in row: + if cell.color_row: + assert cell.color, "Cell color not set but color_row set!" + assert ( + not row_style.color + ), "Multiple row_style.colors found!" + row_style.color = cell.color + if cell.bgcolor_row: + assert ( + cell.bgcolor + ), "Cell bgcolor not set but bgcolor_row set!" + assert ( + not row_style.bgcolor + ), "Multiple row_style.bgcolors found!" + row_style.bgcolor = cell.bgcolor + self._row_styles.append(row_style) + + self._column_styles = [] + if len(self._table) < 2: + return + + for i in range(max(len(row) for row in self._table)): + column_style = Cell() + for row in self._table: + if not any([cell.colspan != 1 for cell in row]): + column_style.width = max( + column_style.width, len(row[i].string_value) + ) + self._column_styles.append(column_style) + + def _GetBGColorFix(self, color): + if self._output_type == self.CONSOLE: + prefix = misc.rgb2short(color.r, color.g, color.b) + # pylint: disable=anomalous-backslash-in-string + prefix = "\033[48;5;%sm" % prefix + suffix = "\033[0m" + elif self._output_type in [self.EMAIL, self.HTML]: + rgb = color.GetRGB() + prefix = ''.format(rgb) + suffix = "" + elif self._output_type in [self.PLAIN, self.TSV]: + prefix = "" + suffix = "" + return prefix, suffix + + def _GetColorFix(self, color): + if self._output_type == self.CONSOLE: + prefix = misc.rgb2short(color.r, color.g, color.b) + # pylint: disable=anomalous-backslash-in-string + prefix = "\033[38;5;%sm" % prefix + suffix = "\033[0m" + elif self._output_type in [self.EMAIL, self.HTML]: + rgb = color.GetRGB() + prefix = "".format(rgb) + suffix = "" + elif self._output_type in [self.PLAIN, self.TSV]: + prefix = "" + suffix = "" + return prefix, suffix + + def Print(self): + """Print the table to a console, html, etc. + + Returns: + A string that contains the desired representation of the table. + """ + self._ComputeStyle() + return self._GetStringValue() + + def _GetCellValue(self, i, j): + cell = self._table[i][j] + out = cell.string_value + raw_width = len(out) + + if cell.color: + p, s = self._GetColorFix(cell.color) + out = "%s%s%s" % (p, out, s) + + if cell.bgcolor: + p, s = self._GetBGColorFix(cell.bgcolor) + out = "%s%s%s" % (p, out, s) + + if self._output_type in [self.PLAIN, self.CONSOLE, self.EMAIL]: + if cell.width: + width = cell.width + else: + if self._column_styles: + width = self._column_styles[j].width + else: + width = len(cell.string_value) + if cell.colspan > 1: + width = 0 + start = 0 + for k in range(j): + start += self._table[i][k].colspan + for k in range(cell.colspan): + width += self._column_styles[start + k].width + if width > raw_width: + padding = ("%" + str(width - raw_width) + "s") % "" + out = padding + out + + if self._output_type == self.HTML: + if cell.header: + tag = "th" + else: + tag = "td" + out = '<{0} colspan = "{2}"> {1} '.format( + tag, out, cell.colspan + ) + + return out + + def _GetHorizontalSeparator(self): + if self._output_type in [self.CONSOLE, self.PLAIN, self.EMAIL]: + return " " + if self._output_type == self.HTML: + return "" + if self._output_type == self.TSV: + return "\t" + + def _GetVerticalSeparator(self): + if self._output_type in [ + self.PLAIN, + self.CONSOLE, + self.TSV, + self.EMAIL, + ]: + return "\n" + if self._output_type == self.HTML: + return "\n" + + def _GetPrefix(self): + if self._output_type in [ + self.PLAIN, + self.CONSOLE, + self.TSV, + self.EMAIL, + ]: + return "" + if self._output_type == self.HTML: + return '

\n' + + def _GetSuffix(self): + if self._output_type in [ + self.PLAIN, + self.CONSOLE, + self.TSV, + self.EMAIL, + ]: + return "" + if self._output_type == self.HTML: + return "\n
" + + def _GetStringValue(self): + o = "" + o += self._GetPrefix() + for i in range(len(self._table)): + row = self._table[i] + # Apply row color and bgcolor. + p = s = bgp = bgs = "" + if self._row_styles[i].bgcolor: + bgp, bgs = self._GetBGColorFix(self._row_styles[i].bgcolor) + if self._row_styles[i].color: + p, s = self._GetColorFix(self._row_styles[i].color) + o += p + bgp + for j in range(len(row)): + out = self._GetCellValue(i, j) + o += out + self._GetHorizontalSeparator() + o += s + bgs + o += self._GetVerticalSeparator() + o += self._GetSuffix() + return o # Some common drivers def GetSimpleTable(table, out_to=TablePrinter.CONSOLE): - """Prints a simple table. - - This is used by code that has a very simple list-of-lists and wants to - produce a table with ameans, a percentage ratio of ameans and a colorbox. - - Examples: - GetSimpleConsoleTable([["binary", "b1", "b2"],["size", "300", "400"]]) - will produce a colored table that can be printed to the console. - - Args: - table: a list of lists. - out_to: specify the fomat of output. Currently it supports HTML and CONSOLE. - - Returns: - A string version of the table that can be printed to the console. - """ - columns = [ - Column(AmeanResult(), Format()), - Column(AmeanRatioResult(), PercentFormat()), - Column(AmeanRatioResult(), ColorBoxFormat()), - ] - our_table = [table[0]] - for row in table[1:]: - our_row = [row[0]] - for v in row[1:]: - our_row.append([v]) - our_table.append(our_row) - - tf = TableFormatter(our_table, columns) - cell_table = tf.GetCellTable() - tp = TablePrinter(cell_table, out_to) - return tp.Print() + """Prints a simple table. + + This is used by code that has a very simple list-of-lists and wants to + produce a table with ameans, a percentage ratio of ameans and a colorbox. + + Examples: + GetSimpleConsoleTable([["binary", "b1", "b2"],["size", "300", "400"]]) + will produce a colored table that can be printed to the console. + + Args: + table: a list of lists. + out_to: specify the fomat of output. Currently it supports HTML and CONSOLE. + + Returns: + A string version of the table that can be printed to the console. + """ + columns = [ + Column(AmeanResult(), Format()), + Column(AmeanRatioResult(), PercentFormat()), + Column(AmeanRatioResult(), ColorBoxFormat()), + ] + our_table = [table[0]] + for row in table[1:]: + our_row = [row[0]] + for v in row[1:]: + our_row.append([v]) + our_table.append(our_row) + + tf = TableFormatter(our_table, columns) + cell_table = tf.GetCellTable() + tp = TablePrinter(cell_table, out_to) + return tp.Print() # pylint: disable=redefined-outer-name def GetComplexTable(runs, labels, out_to=TablePrinter.CONSOLE): - """Prints a complex table. + """Prints a complex table. - This can be used to generate a table with arithmetic mean, standard deviation, - coefficient of variation, p-values, etc. + This can be used to generate a table with arithmetic mean, standard deviation, + coefficient of variation, p-values, etc. - Args: - runs: A list of lists with data to tabulate. - labels: A list of labels that correspond to the runs. - out_to: specifies the format of the table (example CONSOLE or HTML). + Args: + runs: A list of lists with data to tabulate. + labels: A list of labels that correspond to the runs. + out_to: specifies the format of the table (example CONSOLE or HTML). - Returns: - A string table that can be printed to the console or put in an HTML file. - """ - tg = TableGenerator(runs, labels, TableGenerator.SORT_BY_VALUES_DESC) - table = tg.GetTable() - columns = [ - Column(LiteralResult(), Format(), 'Literal'), - Column(AmeanResult(), Format()), - Column(StdResult(), Format()), - Column(CoeffVarResult(), CoeffVarFormat()), - Column(NonEmptyCountResult(), Format()), - Column(AmeanRatioResult(), PercentFormat()), - Column(AmeanRatioResult(), RatioFormat()), - Column(GmeanRatioResult(), RatioFormat()), - Column(PValueResult(), PValueFormat()) - ] - tf = TableFormatter(table, columns) - cell_table = tf.GetCellTable() - tp = TablePrinter(cell_table, out_to) - return tp.Print() - - -if __name__ == '__main__': - # Run a few small tests here. - run1 = { - 'k1': '10', - 'k2': '12', - 'k5': '40', - 'k6': '40', - 'ms_1': '20', - 'k7': 'FAIL', - 'k8': 'PASS', - 'k9': 'PASS', - 'k10': '0' - } - run2 = { - 'k1': '13', - 'k2': '14', - 'k3': '15', - 'ms_1': '10', - 'k8': 'PASS', - 'k9': 'FAIL', - 'k10': '0' - } - run3 = { - 'k1': '50', - 'k2': '51', - 'k3': '52', - 'k4': '53', - 'k5': '35', - 'k6': '45', - 'ms_1': '200', - 'ms_2': '20', - 'k7': 'FAIL', - 'k8': 'PASS', - 'k9': 'PASS' - } - runs = [[run1, run2], [run3]] - labels = ['vanilla', 'modified'] - t = GetComplexTable(runs, labels, TablePrinter.CONSOLE) - print(t) - email = GetComplexTable(runs, labels, TablePrinter.EMAIL) - - runs = [[{ - 'k1': '1' - }, { - 'k1': '1.1' - }, { - 'k1': '1.2' - }], [{ - 'k1': '5' - }, { - 'k1': '5.1' - }, { - 'k1': '5.2' - }]] - t = GetComplexTable(runs, labels, TablePrinter.CONSOLE) - print(t) - - simple_table = [ - ['binary', 'b1', 'b2', 'b3'], - ['size', 100, 105, 108], - ['rodata', 100, 80, 70], - ['data', 100, 100, 100], - ['debug', 100, 140, 60], - ] - t = GetSimpleTable(simple_table) - print(t) - email += GetSimpleTable(simple_table, TablePrinter.HTML) - email_to = [getpass.getuser()] - email = "
%s
" % email - EmailSender().SendEmail(email_to, 'SimpleTableTest', email, msg_type='html') + Returns: + A string table that can be printed to the console or put in an HTML file. + """ + tg = TableGenerator(runs, labels, TableGenerator.SORT_BY_VALUES_DESC) + table = tg.GetTable() + columns = [ + Column(LiteralResult(), Format(), "Literal"), + Column(AmeanResult(), Format()), + Column(StdResult(), Format()), + Column(CoeffVarResult(), CoeffVarFormat()), + Column(NonEmptyCountResult(), Format()), + Column(AmeanRatioResult(), PercentFormat()), + Column(AmeanRatioResult(), RatioFormat()), + Column(GmeanRatioResult(), RatioFormat()), + Column(PValueResult(), PValueFormat()), + ] + tf = TableFormatter(table, columns) + cell_table = tf.GetCellTable() + tp = TablePrinter(cell_table, out_to) + return tp.Print() + + +if __name__ == "__main__": + # Run a few small tests here. + run1 = { + "k1": "10", + "k2": "12", + "k5": "40", + "k6": "40", + "ms_1": "20", + "k7": "FAIL", + "k8": "PASS", + "k9": "PASS", + "k10": "0", + } + run2 = { + "k1": "13", + "k2": "14", + "k3": "15", + "ms_1": "10", + "k8": "PASS", + "k9": "FAIL", + "k10": "0", + } + run3 = { + "k1": "50", + "k2": "51", + "k3": "52", + "k4": "53", + "k5": "35", + "k6": "45", + "ms_1": "200", + "ms_2": "20", + "k7": "FAIL", + "k8": "PASS", + "k9": "PASS", + } + runs = [[run1, run2], [run3]] + labels = ["vanilla", "modified"] + t = GetComplexTable(runs, labels, TablePrinter.CONSOLE) + print(t) + email = GetComplexTable(runs, labels, TablePrinter.EMAIL) + + runs = [ + [{"k1": "1"}, {"k1": "1.1"}, {"k1": "1.2"}], + [{"k1": "5"}, {"k1": "5.1"}, {"k1": "5.2"}], + ] + t = GetComplexTable(runs, labels, TablePrinter.CONSOLE) + print(t) + + simple_table = [ + ["binary", "b1", "b2", "b3"], + ["size", 100, 105, 108], + ["rodata", 100, 80, 70], + ["data", 100, 100, 100], + ["debug", 100, 140, 60], + ] + t = GetSimpleTable(simple_table) + print(t) + email += GetSimpleTable(simple_table, TablePrinter.HTML) + email_to = [getpass.getuser()] + email = "
%s
" % email + EmailSender().SendEmail(email_to, "SimpleTableTest", email, msg_type="html") -- cgit v1.2.3 From c0041a9550814e402f661a560855ff99863cffb2 Mon Sep 17 00:00:00 2001 From: George Burgess IV Date: Tue, 6 Sep 2022 12:12:02 -0700 Subject: remove `from __future__ import ...` directives These are only useful when we're running code in a Python 2.7 interpreter. Since we no longer support python2, drop these. BUG=b:244644217 TEST=run_tests_for.py shows no new failures Change-Id: Ief9a12b87a560ab38ca71668636874bcb434a0b3 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/3877339 Reviewed-by: Ryan Beltran Commit-Queue: George Burgess Reviewed-by: Jordan Abrahams-Whitehead Tested-by: George Burgess --- cros_utils/tabulator.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'cros_utils/tabulator.py') diff --git a/cros_utils/tabulator.py b/cros_utils/tabulator.py index 7b51bf86..65d0cd42 100644 --- a/cros_utils/tabulator.py +++ b/cros_utils/tabulator.py @@ -61,8 +61,6 @@ table: print tp.Print() """ -from __future__ import division -from __future__ import print_function import collections import getpass -- cgit v1.2.3 From fdcd39d5de4bd61cee94cf1e26416838d23092b8 Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Tue, 13 Sep 2022 14:19:58 -0400 Subject: Update license boilerplate text in source code files Normally we don't do this, but enough changes have accumulated that we're doing a tree-wide one-off update of the name & style. BUG=chromium:1098010 TEST=`repo upload` works Change-Id: Icb42e5012a87920c2cd13b666fb3e55e7e4fb3b8 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/3891080 Auto-Submit: Mike Frysinger Tested-by: Mike Frysinger Commit-Queue: George Burgess Reviewed-by: George Burgess --- cros_utils/tabulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cros_utils/tabulator.py') diff --git a/cros_utils/tabulator.py b/cros_utils/tabulator.py index 65d0cd42..d079ea22 100644 --- a/cros_utils/tabulator.py +++ b/cros_utils/tabulator.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2013 The ChromiumOS Authors. All rights reserved. +# 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. -- cgit v1.2.3