asharif | 265cda3 | 2013-02-15 21:57:02 +0000 | [diff] [blame] | 1 | import numpy |
| 2 | import re |
| 3 | |
| 4 | def IsFloat(text): |
| 5 | if text is None: |
| 6 | return False |
| 7 | try: |
| 8 | float(text) |
| 9 | return True |
| 10 | except ValueError: |
| 11 | return False |
| 12 | |
| 13 | |
| 14 | def RemoveTrailingZeros(x): |
| 15 | ret = x |
| 16 | ret = re.sub("\.0*$", "", ret) |
| 17 | ret = re.sub("(\.[1-9]*)0+$", "\\1", ret) |
| 18 | return ret |
| 19 | |
| 20 | |
| 21 | def HumanizeFloat(x, n=2): |
| 22 | if not IsFloat(x): |
| 23 | return x |
| 24 | digits = re.findall("[0-9.]", str(x)) |
| 25 | decimal_found = False |
| 26 | ret = "" |
| 27 | sig_figs = 0 |
| 28 | for digit in digits: |
| 29 | if digit == ".": |
| 30 | decimal_found = True |
| 31 | elif sig_figs != 0 or digit != "0": |
| 32 | sig_figs += 1 |
| 33 | if decimal_found and sig_figs >= n: |
| 34 | break |
| 35 | ret += digit |
| 36 | return ret |
| 37 | |
| 38 | |
| 39 | def GetNSigFigs(x, n=2): |
| 40 | if not IsFloat(x): |
| 41 | return x |
| 42 | my_fmt = "%." + str(n-1) + "e" |
| 43 | x_string = my_fmt % x |
| 44 | f = float(x_string) |
| 45 | return f |
| 46 | |
| 47 | |
| 48 | def GetFormattedPercent(baseline, other, bad_result="--"): |
| 49 | result = "%8s" % GetPercent(baseline, other, bad_result) |
| 50 | return result |
| 51 | |
| 52 | |
| 53 | def GetPercent(baseline, other, bad_result="--"): |
| 54 | result = bad_result |
| 55 | if IsFloat(baseline) and IsFloat(other): |
| 56 | try: |
| 57 | pct = (float(other)/float(baseline) - 1) * 100 |
| 58 | result = "%+1.1f" % pct |
| 59 | except ZeroDivisionError: |
| 60 | pass |
| 61 | return result |
| 62 | |
| 63 | |
| 64 | def FitString(text, length): |
| 65 | if len(text) == length: |
| 66 | return text |
| 67 | elif len(text) > length: |
| 68 | return text[-length:] |
| 69 | else: |
| 70 | fmt = "%%%ds" % length |
| 71 | return fmt % text |
| 72 | |
| 73 | |
| 74 | class TableFormatter(object): |
| 75 | def __init__(self): |
| 76 | self.d = "\t" |
| 77 | self.bad_result = "x" |
| 78 | |
| 79 | def GetTablePercents(self, table): |
| 80 | # Assumes table is not transposed. |
| 81 | pct_table = [] |
| 82 | |
| 83 | pct_table.append(table[0]) |
| 84 | for i in range(1, len(table)): |
| 85 | row = [] |
| 86 | row.append(table[i][0]) |
| 87 | for j in range (1, len(table[0])): |
| 88 | c = table[i][j] |
| 89 | b = table[i][1] |
| 90 | p = GetPercent(b, c, self.bad_result) |
| 91 | row.append(p) |
| 92 | pct_table.append(row) |
| 93 | return pct_table |
| 94 | |
| 95 | def FormatFloat(self, c, max_length=8): |
| 96 | if not IsFloat(c): |
| 97 | return c |
| 98 | f = float(c) |
| 99 | ret = HumanizeFloat(f, 4) |
| 100 | ret = RemoveTrailingZeros(ret) |
| 101 | if len(ret) > max_length: |
| 102 | ret = "%1.1ef" % f |
| 103 | return ret |
| 104 | |
| 105 | def TransposeTable(self, table): |
| 106 | transposed_table = [] |
| 107 | for i in range(len(table[0])): |
| 108 | row = [] |
| 109 | for j in range(len(table)): |
| 110 | row.append(table[j][i]) |
| 111 | transposed_table.append(row) |
| 112 | return transposed_table |
| 113 | |
| 114 | def GetTableLabels(self, table): |
| 115 | ret = "" |
| 116 | header = table[0] |
| 117 | for i in range(1, len(header)): |
| 118 | ret += "%d: %s\n" % (i, header[i]) |
| 119 | return ret |
| 120 | |
| 121 | def GetFormattedTable(self, table, transposed=False, |
| 122 | first_column_width=30, column_width=14, |
| 123 | percents_only=True, |
| 124 | fit_string=True): |
| 125 | o = "" |
| 126 | pct_table = self.GetTablePercents(table) |
| 127 | if transposed == True: |
| 128 | table = self.TransposeTable(table) |
| 129 | pct_table = self.TransposeTable(table) |
| 130 | |
| 131 | for i in range(0, len(table)): |
| 132 | for j in range(len(table[0])): |
| 133 | if j == 0: |
| 134 | width = first_column_width |
| 135 | else: |
| 136 | width = column_width |
| 137 | |
| 138 | c = table[i][j] |
| 139 | p = pct_table[i][j] |
| 140 | |
| 141 | # Replace labels with numbers: 0... n |
| 142 | if IsFloat(c): |
| 143 | c = self.FormatFloat(c) |
| 144 | |
| 145 | if IsFloat(p) and not percents_only: |
| 146 | p = "%s%%" % p |
| 147 | |
| 148 | # Print percent values side by side. |
| 149 | if j != 0: |
| 150 | if percents_only: |
| 151 | c = "%s" % p |
| 152 | else: |
| 153 | c = "%s (%s)" % (c, p) |
| 154 | |
| 155 | if i == 0 and j != 0: |
| 156 | c = str(j) |
| 157 | |
| 158 | if fit_string: |
| 159 | o += FitString(c, width) + self.d |
| 160 | else: |
| 161 | o += c + self.d |
| 162 | o += "\n" |
| 163 | return o |
| 164 | |
| 165 | def GetGroups(self, table): |
| 166 | labels = table[0] |
| 167 | groups = [] |
| 168 | group_dict = {} |
| 169 | for i in range(1, len(labels)): |
| 170 | label = labels[i] |
| 171 | stripped_label = self.GetStrippedLabel(label) |
| 172 | if stripped_label not in group_dict: |
| 173 | group_dict[stripped_label] = len(groups) |
| 174 | groups.append([]) |
| 175 | groups[group_dict[stripped_label]].append(i) |
| 176 | return groups |
| 177 | |
| 178 | def GetSummaryTableValues(self, table): |
| 179 | # First get the groups |
| 180 | groups = self.GetGroups(table) |
| 181 | |
| 182 | summary_table = [] |
| 183 | |
| 184 | labels = table[0] |
| 185 | |
| 186 | summary_labels = ["Summary Table"] |
| 187 | for group in groups: |
| 188 | label = labels[group[0]] |
| 189 | stripped_label = self.GetStrippedLabel(label) |
| 190 | group_label = "%s (%d runs)" % (stripped_label, len(group)) |
| 191 | summary_labels.append(group_label) |
| 192 | summary_table.append(summary_labels) |
| 193 | |
| 194 | for i in range(1, len(table)): |
| 195 | row = table[i] |
| 196 | summary_row = [row[0]] |
| 197 | for group in groups: |
| 198 | group_runs = [] |
| 199 | for index in group: |
| 200 | group_runs.append(row[index]) |
| 201 | group_run = self.AggregateResults(group_runs) |
| 202 | summary_row.append(group_run) |
| 203 | summary_table.append(summary_row) |
| 204 | |
| 205 | return summary_table |
| 206 | |
| 207 | # Drop N% slowest and M% fastest numbers, and return arithmean of |
| 208 | # the remaining. |
| 209 | @staticmethod |
| 210 | def AverageWithDrops(numbers, slow_percent=20, fast_percent=20): |
| 211 | sorted_numbers = list(numbers) |
| 212 | sorted_numbers.sort() |
| 213 | num_slow = int(slow_percent/100.0 * len(sorted_numbers)) |
| 214 | num_fast = int(fast_percent/100.0 * len(sorted_numbers)) |
| 215 | sorted_numbers = sorted_numbers[num_slow:] |
| 216 | if num_fast: |
| 217 | sorted_numbers = sorted_numbers[:-num_fast] |
| 218 | return numpy.average(sorted_numbers) |
| 219 | |
| 220 | @staticmethod |
| 221 | def AggregateResults(group_results): |
| 222 | ret = "" |
| 223 | if not group_results: |
| 224 | return ret |
| 225 | all_floats = True |
| 226 | all_passes = True |
| 227 | all_fails = True |
| 228 | for group_result in group_results: |
| 229 | if not IsFloat(group_result): |
| 230 | all_floats = False |
asharif | 0a5843b | 2013-02-15 21:59:10 +0000 | [diff] [blame] | 231 | if group_result != "PASSED": |
asharif | 265cda3 | 2013-02-15 21:57:02 +0000 | [diff] [blame] | 232 | all_passes = False |
asharif | 0a5843b | 2013-02-15 21:59:10 +0000 | [diff] [blame] | 233 | if group_result != "FAILED": |
asharif | 265cda3 | 2013-02-15 21:57:02 +0000 | [diff] [blame] | 234 | all_fails = False |
| 235 | if all_floats == True: |
| 236 | float_results = [float(v) for v in group_results] |
| 237 | ret = "%f" % TableFormatter.AverageWithDrops(float_results) |
| 238 | # Add this line for standard deviation. |
| 239 | ### ret += " %f" % numpy.std(float_results) |
| 240 | elif all_passes == True: |
| 241 | ret = "ALL_PASS" |
| 242 | elif all_fails == True: |
| 243 | ret = "ALL_FAILS" |
| 244 | return ret |
| 245 | |
| 246 | @staticmethod |
| 247 | def GetStrippedLabel(label): |
| 248 | return re.sub("\s*\S+:\S+\s*", "", label) |
| 249 | ### return re.sub("\s*remote:\S*\s*i:\d+$", "", label) |
| 250 | |
| 251 | @staticmethod |
| 252 | def GetLabelWithIteration(label, iteration): |
| 253 | return "%s i:%d" % (label, iteration) |