results_report.py revision 04dc5dc8547dbfbe524cf35ac39537346ad749bb
1#!/usr/bin/python
2
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7from utils.tabulator import *
8
9from column_chart import ColumnChart
10from results_organizer import ResultOrganizer
11from perf_table import PerfTable
12
13
14class ResultsReport(object):
15  MAX_COLOR_CODE = 255
16  PERF_ROWS = 5
17
18  def __init__(self, experiment):
19    self.experiment = experiment
20    self.benchmark_runs = experiment.benchmark_runs
21    self.labels = experiment.labels
22    self.benchmarks = experiment.benchmarks
23    self.baseline = self.labels[0]
24
25  def _SortByLabel(self, runs):
26    labels = {}
27    for benchmark_run in runs:
28      if benchmark_run.label_name not in labels:
29        labels[benchmark_run.label_name] = []
30      labels[benchmark_run.label_name].append(benchmark_run)
31    return labels
32
33  def GetFullTables(self, perf=False):
34    columns = [Column(NonEmptyCountResult(),
35                      Format(),
36                      "Completed"),
37               Column(RawResult(),
38                      Format()),
39               Column(MinResult(),
40                      Format()),
41               Column(MaxResult(),
42                      Format()),
43               Column(AmeanResult(),
44                      Format()),
45               Column(StdResult(),
46                      Format(), "StdDev"),
47               Column(CoeffVarResult(),
48                      CoeffVarFormat(), "StdDev/Mean"),
49               Column(GmeanRatioResult(),
50                      RatioFormat(), "GmeanSpeedup"),
51               Column(PValueResult(),
52                      PValueFormat(), "p-value")
53              ]
54    if not perf:
55      return self._GetTables(self.labels, self.benchmark_runs, columns)
56    return self. _GetPerfTables(self.labels, columns)
57
58  def GetSummaryTables(self, perf=False):
59    columns = [Column(NonEmptyCountResult(),
60                      Format(),
61                      "Completed"),
62               Column(AmeanResult(),
63                      Format()),
64               Column(StdResult(),
65                      Format(), "StdDev"),
66               Column(CoeffVarResult(),
67                      CoeffVarFormat(), "StdDev/Mean"),
68               Column(GmeanRatioResult(),
69                      RatioFormat(), "GmeanSpeedup"),
70               Column(GmeanRatioResult(),
71                      ColorBoxFormat(), " "),
72               Column(PValueResult(),
73                      PValueFormat(), "p-value")
74              ]
75    if not perf:
76      return self._GetTables(self.labels, self.benchmark_runs, columns)
77    return self. _GetPerfTables(self.labels, columns)
78
79  def _ParseColumn(self, columns, iteration):
80    new_column = []
81    for column in columns:
82      if column.result.__class__.__name__ != "RawResult":
83      #TODO(asharif): tabulator should support full table natively.
84        new_column.append(column)
85      else:
86        for i in range(iteration):
87          cc = Column(LiteralResult(i), Format(), str(i+1))
88          new_column.append(cc)
89    return new_column
90
91  def _AreAllRunsEmpty(self, runs):
92    for label in runs:
93      for dictionary in label:
94        if dictionary:
95          return False
96    return True
97
98  def _GetTableHeader(self, benchmark):
99    benchmark_info = ("Benchmark:  {0};  Iterations: {1}"
100                      .format(benchmark.name, benchmark.iterations))
101    cell = Cell()
102    cell.string_value = benchmark_info
103    cell.header = True
104    return  [[cell]]
105
106  def _GetTables(self, labels, benchmark_runs, columns):
107    tables = []
108    ro = ResultOrganizer(benchmark_runs, labels, self.benchmarks)
109    result = ro.result
110    label_name = ro.labels
111    for item in result:
112      runs = result[item]
113      for benchmark in self.benchmarks:
114        if benchmark.name == item:
115          break
116      ben_table = self._GetTableHeader(benchmark)
117
118      if  self._AreAllRunsEmpty(runs):
119        cell = Cell()
120        cell.string_value = ("This benchmark contains no result."
121                             " Is the benchmark name valid?")
122        cell_table = [[cell]]
123      else:
124        tg = TableGenerator(runs, label_name)
125        table = tg.GetTable()
126        parsed_columns = self._ParseColumn(columns, benchmark.iterations)
127        tf = TableFormatter(table, parsed_columns)
128        cell_table = tf.GetCellTable()
129      tables.append(ben_table)
130      tables.append(cell_table)
131    return tables
132
133  def _GetPerfTables(self, labels, columns):
134    tables = []
135    label_names = [label.name for label in labels]
136    p_table = PerfTable(self.experiment, label_names)
137
138    if not p_table.perf_data:
139      return tables
140
141    for benchmark in p_table.perf_data:
142      ben = None
143      for ben in self.benchmarks:
144        if ben.name == benchmark:
145          break
146
147      ben_table = self._GetTableHeader(ben)
148      tables.append(ben_table)
149      benchmark_data = p_table.perf_data[benchmark]
150      row_info = p_table.row_info[benchmark]
151      table = []
152      for event in benchmark_data:
153        tg = TableGenerator(benchmark_data[event], label_names,
154                            sort=TableGenerator.SORT_BY_VALUES_DESC)
155        table = tg.GetTable(max(self.PERF_ROWS, row_info[event]))
156        parsed_columns = self._ParseColumn(columns, ben.iterations)
157        tf = TableFormatter(table, parsed_columns)
158        tf.GenerateCellTable()
159        tf.AddColumnName()
160        tf.AddLabelName()
161        tf.AddHeader(str(event))
162        table = tf.GetCellTable(headers=False)
163        tables.append(table)
164    return tables
165
166  def PrintTables(self, tables, out_to):
167    output = ""
168    if not tables:
169      return output
170    for table in tables:
171      if out_to == "HTML":
172        tp = TablePrinter(table, TablePrinter.HTML)
173      elif out_to == "PLAIN":
174        tp = TablePrinter(table, TablePrinter.PLAIN)
175      elif out_to == "CONSOLE":
176        tp = TablePrinter(table, TablePrinter.CONSOLE)
177      elif out_to == "TSV":
178        tp = TablePrinter(table, TablePrinter.TSV)
179      elif out_to == "EMAIL":
180        tp = TablePrinter(table, TablePrinter.EMAIL)
181      else:
182        pass
183      output += tp.Print()
184    return output
185
186
187class TextResultsReport(ResultsReport):
188  TEXT = """
189===========================================
190Results report for: '%s'
191===========================================
192
193-------------------------------------------
194Summary
195-------------------------------------------
196%s
197
198
199Number re-images: %s
200
201-------------------------------------------
202Benchmark Run Status
203-------------------------------------------
204%s
205
206
207-------------------------------------------
208Perf Data
209-------------------------------------------
210%s
211
212
213
214Experiment File
215-------------------------------------------
216%s
217
218
219CPUInfo
220-------------------------------------------
221%s
222===========================================
223"""
224
225  def __init__(self, experiment, email=False):
226    super(TextResultsReport, self).__init__(experiment)
227    self.email = email
228
229  def GetStatusTable(self):
230    """Generate the status table by the tabulator."""
231    table = [["", ""]]
232    columns = [Column(LiteralResult(iteration=0), Format(), "Status"),
233               Column(LiteralResult(iteration=1), Format(), "Failing Reason")]
234
235    for benchmark_run in self.benchmark_runs:
236      status = [benchmark_run.name, [benchmark_run.timeline.GetLastEvent(),
237                                     benchmark_run.failure_reason]]
238      table.append(status)
239    tf = TableFormatter(table, columns)
240    cell_table = tf.GetCellTable()
241    return [cell_table]
242
243  def GetReport(self):
244    """Generate the report for email and console."""
245    status_table = self.GetStatusTable()
246    summary_table = self.GetSummaryTables()
247    full_table = self.GetFullTables()
248    perf_table = self.GetSummaryTables(perf=True)
249    if not perf_table:
250      perf_table = None
251    if not self.email:
252      return self.TEXT % (self.experiment.name,
253                          self.PrintTables(summary_table, "CONSOLE"),
254                          self.experiment.machine_manager.num_reimages,
255                          self.PrintTables(status_table, "CONSOLE"),
256                          self.PrintTables(perf_table, "CONSOLE"),
257                          self.experiment.experiment_file,
258                          self.experiment.machine_manager.GetAllCPUInfo(
259                              self.experiment.labels))
260
261    return self.TEXT % (self.experiment.name,
262                        self.PrintTables(summary_table, "EMAIL"),
263                        self.experiment.machine_manager.num_reimages,
264                        self.PrintTables(status_table, "EMAIL"),
265                        self.PrintTables(perf_table, "EMAIL"),
266                        self.experiment.experiment_file,
267                        self.experiment.machine_manager.GetAllCPUInfo(
268                            self.experiment.labels))
269
270
271class HTMLResultsReport(ResultsReport):
272
273  HTML = """
274<html>
275  <head>
276    <style type="text/css">
277
278body {
279  font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
280  font-size: 12px;
281}
282
283pre {
284  margin: 10px;
285  color: #039;
286  font-size: 14px;
287}
288
289.chart {
290  display: inline;
291}
292
293.hidden {
294  visibility: hidden;
295}
296
297.results-section {
298  border: 1px solid #b9c9fe;
299  margin: 10px;
300}
301
302.results-section-title {
303  background-color: #b9c9fe;
304  color: #039;
305  padding: 7px;
306  font-size: 14px;
307  width: 200px;
308}
309
310.results-section-content {
311  margin: 10px;
312  padding: 10px;
313  overflow:auto;
314}
315
316#box-table-a {
317  font-size: 12px;
318  width: 480px;
319  text-align: left;
320  border-collapse: collapse;
321}
322
323#box-table-a th {
324  padding: 6px;
325  background: #b9c9fe;
326  border-right: 1px solid #fff;
327  border-bottom: 1px solid #fff;
328  color: #039;
329  text-align: center;
330}
331
332#box-table-a td {
333  padding: 4px;
334  background: #e8edff;
335  border-bottom: 1px solid #fff;
336  border-right: 1px solid #fff;
337  color: #669;
338  border-top: 1px solid transparent;
339}
340
341#box-table-a tr:hover td {
342  background: #d0dafd;
343  color: #339;
344}
345
346    </style>
347    <script type='text/javascript' src='https://www.google.com/jsapi'></script>
348    <script type='text/javascript'>
349      google.load('visualization', '1', {packages:['corechart']});
350      google.setOnLoadCallback(init);
351      function init() {
352        switchTab('summary', 'html');
353        %s
354        switchTab('full', 'html');
355        drawTable();
356      }
357      function drawTable() {
358        %s
359      }
360      function switchTab(table, tab) {
361        document.getElementById(table + '-html').style.display = 'none';
362        document.getElementById(table + '-text').style.display = 'none';
363        document.getElementById(table + '-tsv').style.display = 'none';
364        document.getElementById(table + '-' + tab).style.display = 'block';
365      }
366    </script>
367  </head>
368
369  <body>
370    <div class='results-section'>
371      <div class='results-section-title'>Summary Table</div>
372      <div class='results-section-content'>
373        <div id='summary-html'>%s</div>
374        <div id='summary-text'><pre>%s</pre></div>
375        <div id='summary-tsv'><pre>%s</pre></div>
376      </div>
377      %s
378    </div>
379    %s
380    <div class='results-section'>
381      <div class='results-section-title'>Charts</div>
382      <div class='results-section-content'>%s</div>
383    </div>
384    <div class='results-section'>
385      <div class='results-section-title'>Full Table</div>
386      <div class='results-section-content'>
387        <div id='full-html'>%s</div>
388        <div id='full-text'><pre>%s</pre></div>
389        <div id='full-tsv'><pre>%s</pre></div>
390      </div>
391      %s
392    </div>
393    <div class='results-section'>
394      <div class='results-section-title'>Experiment File</div>
395      <div class='results-section-content'>
396        <pre>%s</pre>
397    </div>
398    </div>
399  </body>
400</html>
401"""
402
403  PERF_HTML = """
404    <div class='results-section'>
405      <div class='results-section-title'>Perf Table</div>
406      <div class='results-section-content'>
407        <div id='perf-html'>%s</div>
408        <div id='perf-text'><pre>%s</pre></div>
409        <div id='perf-tsv'><pre>%s</pre></div>
410      </div>
411      %s
412    </div>
413"""
414
415  def __init__(self, experiment):
416    super(HTMLResultsReport, self).__init__(experiment)
417
418  def _GetTabMenuHTML(self, table):
419    return """
420<div class='tab-menu'>
421  <a href="javascript:switchTab('%s', 'html')">HTML</a>
422  <a href="javascript:switchTab('%s', 'text')">Text</a>
423  <a href="javascript:switchTab('%s', 'tsv')">TSV</a>
424</div>""" % (table, table, table)
425
426  def GetReport(self):
427    chart_javascript = ""
428    charts = self._GetCharts(self.labels, self.benchmark_runs)
429    for chart in charts:
430      chart_javascript += chart.GetJavascript()
431    chart_divs = ""
432    for chart in charts:
433      chart_divs += chart.GetDiv()
434
435    summary_table = self.GetSummaryTables()
436    full_table = self.GetFullTables()
437    perf_table = self.GetSummaryTables(perf=True)
438    if perf_table:
439      perf_html = self.PERF_HTML % (
440          self.PrintTables(perf_table, "HTML"),
441          self.PrintTables(perf_table, "PLAIN"),
442          self.PrintTables(perf_table, "TSV"),
443          self._GetTabMenuHTML("perf")
444          )
445      perf_init = "switchTab('perf', 'html');"
446    else:
447      perf_html = ""
448      perf_init = ""
449
450    return self.HTML % (perf_init,
451                        chart_javascript,
452                        self.PrintTables(summary_table, "HTML"),
453                        self.PrintTables(summary_table, "PLAIN"),
454                        self.PrintTables(summary_table, "TSV"),
455                        self._GetTabMenuHTML("summary"),
456                        perf_html,
457                        chart_divs,
458                        self.PrintTables(full_table, "HTML"),
459                        self.PrintTables(full_table, "PLAIN"),
460                        self.PrintTables(full_table, "TSV"),
461                        self._GetTabMenuHTML("full"),
462                        self.experiment.experiment_file)
463
464  def _GetCharts(self, labels, benchmark_runs):
465    charts = []
466    ro = ResultOrganizer(benchmark_runs, labels)
467    result = ro.result
468    for item in result:
469      runs = result[item]
470      tg = TableGenerator(runs, ro.labels)
471      table = tg.GetTable()
472      columns = [Column(AmeanResult(),
473                        Format()),
474                 Column(MinResult(),
475                        Format()),
476                 Column(MaxResult(),
477                        Format())
478                ]
479      tf = TableFormatter(table, columns)
480      data_table = tf.GetCellTable()
481
482      for i in range(2, len(data_table)):
483        cur_row_data = data_table[i]
484        test_key = cur_row_data[0].string_value
485        title = "{0}: {1}".format(item, test_key.replace("/", ""))
486        chart = ColumnChart(title, 300, 200)
487        chart.AddColumn("Label", "string")
488        chart.AddColumn("Average", "number")
489        chart.AddColumn("Min", "number")
490        chart.AddColumn("Max", "number")
491        chart.AddSeries("Min", "line", "black")
492        chart.AddSeries("Max", "line", "black")
493        cur_index = 1
494        for label in ro.labels:
495          chart.AddRow([label, cur_row_data[cur_index].value,
496                        cur_row_data[cur_index + 1].value,
497                        cur_row_data[cur_index + 2].value])
498          if isinstance(cur_row_data[cur_index].value, str):
499            chart = None
500            break
501          cur_index += 3
502        if chart:
503          charts.append(chart)
504    return charts
505