table_formatter.py revision f81680c018729fd4499e1e200d04b48c4b90127c
1import numpy
2import re
3
4def 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
14def 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
21def 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
39def 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
48def GetFormattedPercent(baseline, other, bad_result="--"):
49  result = "%8s" % GetPercent(baseline, other, bad_result)
50  return result
51
52
53def 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
64def 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
74class 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
231      if group_result != "PASSED":
232        all_passes = False
233      if group_result != "FAILED":
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)
254