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