| import numpy |
| import re |
| |
| |
| def IsFloat(text): |
| if text is None: |
| return False |
| try: |
| float(text) |
| return True |
| except ValueError: |
| return False |
| |
| |
| def RemoveTrailingZeros(x): |
| ret = x |
| ret = re.sub('\.0*$', '', ret) |
| ret = re.sub('(\.[1-9]*)0+$', '\\1', ret) |
| return ret |
| |
| |
| def HumanizeFloat(x, n=2): |
| if not IsFloat(x): |
| return x |
| digits = re.findall('[0-9.]', str(x)) |
| decimal_found = False |
| ret = '' |
| sig_figs = 0 |
| for digit in digits: |
| if digit == '.': |
| decimal_found = True |
| elif sig_figs != 0 or digit != '0': |
| sig_figs += 1 |
| if decimal_found and sig_figs >= n: |
| break |
| ret += digit |
| return ret |
| |
| |
| def GetNSigFigs(x, n=2): |
| if not IsFloat(x): |
| return x |
| my_fmt = '%.' + str(n - 1) + 'e' |
| x_string = my_fmt % x |
| f = float(x_string) |
| return f |
| |
| |
| def GetFormattedPercent(baseline, other, bad_result='--'): |
| result = '%8s' % GetPercent(baseline, other, bad_result) |
| return result |
| |
| |
| def GetPercent(baseline, other, bad_result='--'): |
| result = bad_result |
| if IsFloat(baseline) and IsFloat(other): |
| try: |
| pct = (float(other) / float(baseline) - 1) * 100 |
| result = '%+1.1f' % pct |
| except ZeroDivisionError: |
| pass |
| return result |
| |
| |
| def FitString(text, length): |
| if len(text) == length: |
| return text |
| elif len(text) > length: |
| return text[-length:] |
| else: |
| fmt = '%%%ds' % length |
| return fmt % text |
| |
| |
| class TableFormatter(object): |
| |
| def __init__(self): |
| self.d = '\t' |
| self.bad_result = 'x' |
| |
| def GetTablePercents(self, table): |
| # Assumes table is not transposed. |
| pct_table = [] |
| |
| pct_table.append(table[0]) |
| for i in range(1, len(table)): |
| row = [] |
| row.append(table[i][0]) |
| for j in range(1, len(table[0])): |
| c = table[i][j] |
| b = table[i][1] |
| p = GetPercent(b, c, self.bad_result) |
| row.append(p) |
| pct_table.append(row) |
| return pct_table |
| |
| def FormatFloat(self, c, max_length=8): |
| if not IsFloat(c): |
| return c |
| f = float(c) |
| ret = HumanizeFloat(f, 4) |
| ret = RemoveTrailingZeros(ret) |
| if len(ret) > max_length: |
| ret = '%1.1ef' % f |
| return ret |
| |
| def TransposeTable(self, table): |
| transposed_table = [] |
| for i in range(len(table[0])): |
| row = [] |
| for j in range(len(table)): |
| row.append(table[j][i]) |
| transposed_table.append(row) |
| return transposed_table |
| |
| def GetTableLabels(self, table): |
| ret = '' |
| header = table[0] |
| for i in range(1, len(header)): |
| ret += '%d: %s\n' % (i, header[i]) |
| return ret |
| |
| def GetFormattedTable(self, |
| table, |
| transposed=False, |
| first_column_width=30, |
| column_width=14, |
| percents_only=True, |
| fit_string=True): |
| o = '' |
| pct_table = self.GetTablePercents(table) |
| if transposed == True: |
| table = self.TransposeTable(table) |
| pct_table = self.TransposeTable(table) |
| |
| for i in range(0, len(table)): |
| for j in range(len(table[0])): |
| if j == 0: |
| width = first_column_width |
| else: |
| width = column_width |
| |
| c = table[i][j] |
| p = pct_table[i][j] |
| |
| # Replace labels with numbers: 0... n |
| if IsFloat(c): |
| c = self.FormatFloat(c) |
| |
| if IsFloat(p) and not percents_only: |
| p = '%s%%' % p |
| |
| # Print percent values side by side. |
| if j != 0: |
| if percents_only: |
| c = '%s' % p |
| else: |
| c = '%s (%s)' % (c, p) |
| |
| if i == 0 and j != 0: |
| c = str(j) |
| |
| if fit_string: |
| o += FitString(c, width) + self.d |
| else: |
| o += c + self.d |
| o += '\n' |
| return o |
| |
| def GetGroups(self, table): |
| labels = table[0] |
| groups = [] |
| group_dict = {} |
| for i in range(1, len(labels)): |
| label = labels[i] |
| stripped_label = self.GetStrippedLabel(label) |
| if stripped_label not in group_dict: |
| group_dict[stripped_label] = len(groups) |
| groups.append([]) |
| groups[group_dict[stripped_label]].append(i) |
| return groups |
| |
| def GetSummaryTableValues(self, table): |
| # First get the groups |
| groups = self.GetGroups(table) |
| |
| summary_table = [] |
| |
| labels = table[0] |
| |
| summary_labels = ['Summary Table'] |
| for group in groups: |
| label = labels[group[0]] |
| stripped_label = self.GetStrippedLabel(label) |
| group_label = '%s (%d runs)' % (stripped_label, len(group)) |
| summary_labels.append(group_label) |
| summary_table.append(summary_labels) |
| |
| for i in range(1, len(table)): |
| row = table[i] |
| summary_row = [row[0]] |
| for group in groups: |
| group_runs = [] |
| for index in group: |
| group_runs.append(row[index]) |
| group_run = self.AggregateResults(group_runs) |
| summary_row.append(group_run) |
| summary_table.append(summary_row) |
| |
| return summary_table |
| |
| # Drop N% slowest and M% fastest numbers, and return arithmean of |
| # the remaining. |
| @staticmethod |
| def AverageWithDrops(numbers, slow_percent=20, fast_percent=20): |
| sorted_numbers = list(numbers) |
| sorted_numbers.sort() |
| num_slow = int(slow_percent / 100.0 * len(sorted_numbers)) |
| num_fast = int(fast_percent / 100.0 * len(sorted_numbers)) |
| sorted_numbers = sorted_numbers[num_slow:] |
| if num_fast: |
| sorted_numbers = sorted_numbers[:-num_fast] |
| return numpy.average(sorted_numbers) |
| |
| @staticmethod |
| def AggregateResults(group_results): |
| ret = '' |
| if not group_results: |
| return ret |
| all_floats = True |
| all_passes = True |
| all_fails = True |
| for group_result in group_results: |
| if not IsFloat(group_result): |
| all_floats = False |
| if group_result != 'PASSED': |
| all_passes = False |
| if group_result != 'FAILED': |
| all_fails = False |
| if all_floats == True: |
| float_results = [float(v) for v in group_results] |
| ret = '%f' % TableFormatter.AverageWithDrops(float_results) |
| # Add this line for standard deviation. |
| ### ret += " %f" % numpy.std(float_results) |
| elif all_passes == True: |
| ret = 'ALL_PASS' |
| elif all_fails == True: |
| ret = 'ALL_FAILS' |
| return ret |
| |
| @staticmethod |
| def GetStrippedLabel(label): |
| return re.sub('\s*\S+:\S+\s*', '', label) |
| ### return re.sub("\s*remote:\S*\s*i:\d+$", "", label) |
| |
| @staticmethod |
| def GetLabelWithIteration(label, iteration): |
| return '%s i:%d' % (label, iteration) |