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