results_report.py revision 9b852cfd9a602c5f80c8e621c696b796ce5177fd
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 7import datetime 8import json 9import os 10 11from utils.tabulator import * 12 13from update_telemetry_defaults import TelemetryDefaults 14from column_chart import ColumnChart 15from results_organizer import ResultOrganizer 16from perf_table import PerfTable 17 18 19def ParseChromeosImage(chromeos_image): 20 """Parse the chromeos_image string for the image and version. 21 22 The chromeos_image string will probably be in one of two formats: 23 1: <path-to-chroot>/src/build/images/<board>/<ChromeOS-version>.<datetime>/ \ 24 chromiumos_test_image.bin 25 2: <path-to-chroot>/chroot/tmp/<buildbot-build>/<ChromeOS-version>/ \ 26 chromiumos_test_image.bin 27 28 We parse these strings to find the 'chromeos_version' to store in the 29 json archive (without the .datatime bit in the first case); and also 30 the 'chromeos_image', which would be all of the first case, but only the 31 part after '/chroot/tmp' in the second case. 32 33 Args: 34 chromeos_image: String containing the path to the chromeos_image that 35 crosperf used for the test. 36 37 Returns: 38 version, image: The results of parsing the input string, as explained 39 above. 40 """ 41 version = '' 42 real_file = os.path.realpath(os.path.expanduser(chromeos_image)) 43 pieces = real_file.split('/') 44 # Find the Chromeos Version, e.g. R45-2345.0.0..... 45 # chromeos_image should have been something like: 46 # <path>/<board-trybot-release>/<chromeos-version>/chromiumos_test_image.bin" 47 num_pieces = len(pieces) 48 if pieces[num_pieces-1] == "chromiumos_test_image.bin": 49 version = pieces[num_pieces-2] 50 # Find last '.' in the version and chop it off (removing the .datatime 51 # piece from local builds). 52 loc = version.rfind('.') 53 version = version[:loc] 54 # Find the chromeos image. If it's somewhere in .../chroot/tmp/..., then 55 # it's an official image that got downloaded, so chop off the download path 56 # to make the official image name more clear. 57 loc = real_file.find('/chroot/tmp') 58 if loc != -1: 59 loc += len('/chroot/tmp') 60 real_file = real_file[loc:] 61 image = real_file 62 return version,image 63 64class ResultsReport(object): 65 MAX_COLOR_CODE = 255 66 PERF_ROWS = 5 67 68 def __init__(self, experiment): 69 self.experiment = experiment 70 self.benchmark_runs = experiment.benchmark_runs 71 self.labels = experiment.labels 72 self.benchmarks = experiment.benchmarks 73 self.baseline = self.labels[0] 74 75 def _SortByLabel(self, runs): 76 labels = {} 77 for benchmark_run in runs: 78 if benchmark_run.label_name not in labels: 79 labels[benchmark_run.label_name] = [] 80 labels[benchmark_run.label_name].append(benchmark_run) 81 return labels 82 83 def GetFullTables(self, perf=False): 84 columns = [Column(RawResult(), 85 Format()), 86 Column(MinResult(), 87 Format()), 88 Column(MaxResult(), 89 Format()), 90 Column(AmeanResult(), 91 Format()), 92 Column(StdResult(), 93 Format(), "StdDev"), 94 Column(CoeffVarResult(), 95 CoeffVarFormat(), "StdDev/Mean"), 96 Column(GmeanRatioResult(), 97 RatioFormat(), "GmeanSpeedup"), 98 Column(PValueResult(), 99 PValueFormat(), "p-value") 100 ] 101 if not perf: 102 return self._GetTables(self.labels, self.benchmark_runs, columns, 103 "full") 104 return self._GetPerfTables(self.labels, columns, "full") 105 106 def GetSummaryTables(self, perf=False): 107 columns = [Column(AmeanResult(), 108 Format()), 109 Column(StdResult(), 110 Format(), "StdDev"), 111 Column(CoeffVarResult(), 112 CoeffVarFormat(), "StdDev/Mean"), 113 Column(GmeanRatioResult(), 114 RatioFormat(), "GmeanSpeedup"), 115 Column(PValueResult(), 116 PValueFormat(), "p-value") 117 ] 118 if not perf: 119 return self._GetTables(self.labels, self.benchmark_runs, columns, 120 "summary") 121 return self._GetPerfTables(self.labels, columns, "summary") 122 123 def _ParseColumn(self, columns, iteration): 124 new_column = [] 125 for column in columns: 126 if column.result.__class__.__name__ != "RawResult": 127 #TODO(asharif): tabulator should support full table natively. 128 new_column.append(column) 129 else: 130 for i in range(iteration): 131 cc = Column(LiteralResult(i), Format(), str(i+1)) 132 new_column.append(cc) 133 return new_column 134 135 def _AreAllRunsEmpty(self, runs): 136 for label in runs: 137 for dictionary in label: 138 if dictionary: 139 return False 140 return True 141 142 def _GetTableHeader(self, benchmark): 143 benchmark_info = ("Benchmark: {0}; Iterations: {1}" 144 .format(benchmark.name, benchmark.iterations)) 145 cell = Cell() 146 cell.string_value = benchmark_info 147 cell.header = True 148 return [[cell]] 149 150 def _FixFalsePositiveTests(self, result, table_type): 151 # Occasionally Telemetry tests will not fail but they will not return a 152 # result, either. Look for those cases, and force them to be a fail. 153 # (This can happen if, for example, the test has been disabled.) 154 for k in result: 155 for run in result[k]: 156 run_dict = run[0] 157 if len(run_dict) != 1 or run_dict['retval'] != 0: 158 continue 159 run_dict['retval'] = 1 160 if table_type == 'summary': 161 print ("WARNING: Test '%s' appears to have succeeded but returned" 162 " no results." % k) 163 164 def _GetTables(self, labels, benchmark_runs, columns, table_type): 165 tables = [] 166 ro = ResultOrganizer(benchmark_runs, labels, self.benchmarks) 167 result = ro.result 168 label_name = ro.labels 169 self._FixFalsePositiveTests(result, table_type) 170 for item in result: 171 runs = result[item] 172 for benchmark in self.benchmarks: 173 if benchmark.name == item: 174 break 175 ben_table = self._GetTableHeader(benchmark) 176 177 if self._AreAllRunsEmpty(runs): 178 cell = Cell() 179 cell.string_value = ("This benchmark contains no result." 180 " Is the benchmark name valid?") 181 cell_table = [[cell]] 182 else: 183 tg = TableGenerator(runs, label_name) 184 table = tg.GetTable() 185 parsed_columns = self._ParseColumn(columns, benchmark.iterations) 186 tf = TableFormatter(table, parsed_columns) 187 cell_table = tf.GetCellTable(table_type) 188 tables.append(ben_table) 189 tables.append(cell_table) 190 return tables 191 192 def _GetPerfTables(self, labels, columns, table_type): 193 tables = [] 194 label_names = [label.name for label in labels] 195 p_table = PerfTable(self.experiment, label_names) 196 197 if not p_table.perf_data: 198 return tables 199 200 for benchmark in p_table.perf_data: 201 ben = None 202 for ben in self.benchmarks: 203 if ben.name == benchmark: 204 break 205 206 ben_table = self._GetTableHeader(ben) 207 tables.append(ben_table) 208 benchmark_data = p_table.perf_data[benchmark] 209 row_info = p_table.row_info[benchmark] 210 table = [] 211 for event in benchmark_data: 212 tg = TableGenerator(benchmark_data[event], label_names, 213 sort=TableGenerator.SORT_BY_VALUES_DESC) 214 table = tg.GetTable(max(self.PERF_ROWS, row_info[event])) 215 parsed_columns = self._ParseColumn(columns, ben.iterations) 216 tf = TableFormatter(table, parsed_columns) 217 tf.GenerateCellTable() 218 tf.AddColumnName() 219 tf.AddLabelName() 220 tf.AddHeader(str(event)) 221 table = tf.GetCellTable(table_type, headers=False) 222 tables.append(table) 223 return tables 224 225 def PrintTables(self, tables, out_to): 226 output = "" 227 if not tables: 228 return output 229 for table in tables: 230 if out_to == "HTML": 231 tp = TablePrinter(table, TablePrinter.HTML) 232 elif out_to == "PLAIN": 233 tp = TablePrinter(table, TablePrinter.PLAIN) 234 elif out_to == "CONSOLE": 235 tp = TablePrinter(table, TablePrinter.CONSOLE) 236 elif out_to == "TSV": 237 tp = TablePrinter(table, TablePrinter.TSV) 238 elif out_to == "EMAIL": 239 tp = TablePrinter(table, TablePrinter.EMAIL) 240 else: 241 pass 242 output += tp.Print() 243 return output 244 245 246class TextResultsReport(ResultsReport): 247 TEXT = """ 248=========================================== 249Results report for: '%s' 250=========================================== 251 252------------------------------------------- 253Summary 254------------------------------------------- 255%s 256 257 258Number re-images: %s 259 260------------------------------------------- 261Benchmark Run Status 262------------------------------------------- 263%s 264 265 266------------------------------------------- 267Perf Data 268------------------------------------------- 269%s 270 271 272 273Experiment File 274------------------------------------------- 275%s 276 277 278CPUInfo 279------------------------------------------- 280%s 281=========================================== 282""" 283 284 def __init__(self, experiment, email=False): 285 super(TextResultsReport, self).__init__(experiment) 286 self.email = email 287 288 def GetStatusTable(self): 289 """Generate the status table by the tabulator.""" 290 table = [["", ""]] 291 columns = [Column(LiteralResult(iteration=0), Format(), "Status"), 292 Column(LiteralResult(iteration=1), Format(), "Failing Reason")] 293 294 for benchmark_run in self.benchmark_runs: 295 status = [benchmark_run.name, [benchmark_run.timeline.GetLastEvent(), 296 benchmark_run.failure_reason]] 297 table.append(status) 298 tf = TableFormatter(table, columns) 299 cell_table = tf.GetCellTable("status") 300 return [cell_table] 301 302 def GetReport(self): 303 """Generate the report for email and console.""" 304 status_table = self.GetStatusTable() 305 summary_table = self.GetSummaryTables() 306 full_table = self.GetFullTables() 307 perf_table = self.GetSummaryTables(perf=True) 308 if not perf_table: 309 perf_table = None 310 if not self.email: 311 return self.TEXT % (self.experiment.name, 312 self.PrintTables(summary_table, "CONSOLE"), 313 self.experiment.machine_manager.num_reimages, 314 self.PrintTables(status_table, "CONSOLE"), 315 self.PrintTables(perf_table, "CONSOLE"), 316 self.experiment.experiment_file, 317 self.experiment.machine_manager.GetAllCPUInfo( 318 self.experiment.labels)) 319 320 return self.TEXT % (self.experiment.name, 321 self.PrintTables(summary_table, "EMAIL"), 322 self.experiment.machine_manager.num_reimages, 323 self.PrintTables(status_table, "EMAIL"), 324 self.PrintTables(perf_table, "EMAIL"), 325 self.experiment.experiment_file, 326 self.experiment.machine_manager.GetAllCPUInfo( 327 self.experiment.labels)) 328 329 330class HTMLResultsReport(ResultsReport): 331 332 HTML = """ 333<html> 334 <head> 335 <style type="text/css"> 336 337body { 338 font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif; 339 font-size: 12px; 340} 341 342pre { 343 margin: 10px; 344 color: #039; 345 font-size: 14px; 346} 347 348.chart { 349 display: inline; 350} 351 352.hidden { 353 visibility: hidden; 354} 355 356.results-section { 357 border: 1px solid #b9c9fe; 358 margin: 10px; 359} 360 361.results-section-title { 362 background-color: #b9c9fe; 363 color: #039; 364 padding: 7px; 365 font-size: 14px; 366 width: 200px; 367} 368 369.results-section-content { 370 margin: 10px; 371 padding: 10px; 372 overflow:auto; 373} 374 375#box-table-a { 376 font-size: 12px; 377 width: 480px; 378 text-align: left; 379 border-collapse: collapse; 380} 381 382#box-table-a th { 383 padding: 6px; 384 background: #b9c9fe; 385 border-right: 1px solid #fff; 386 border-bottom: 1px solid #fff; 387 color: #039; 388 text-align: center; 389} 390 391#box-table-a td { 392 padding: 4px; 393 background: #e8edff; 394 border-bottom: 1px solid #fff; 395 border-right: 1px solid #fff; 396 color: #669; 397 border-top: 1px solid transparent; 398} 399 400#box-table-a tr:hover td { 401 background: #d0dafd; 402 color: #339; 403} 404 405 </style> 406 <script type='text/javascript' src='https://www.google.com/jsapi'></script> 407 <script type='text/javascript'> 408 google.load('visualization', '1', {packages:['corechart']}); 409 google.setOnLoadCallback(init); 410 function init() { 411 switchTab('summary', 'html'); 412 %s 413 switchTab('full', 'html'); 414 drawTable(); 415 } 416 function drawTable() { 417 %s 418 } 419 function switchTab(table, tab) { 420 document.getElementById(table + '-html').style.display = 'none'; 421 document.getElementById(table + '-text').style.display = 'none'; 422 document.getElementById(table + '-tsv').style.display = 'none'; 423 document.getElementById(table + '-' + tab).style.display = 'block'; 424 } 425 </script> 426 </head> 427 428 <body> 429 <div class='results-section'> 430 <div class='results-section-title'>Summary Table</div> 431 <div class='results-section-content'> 432 <div id='summary-html'>%s</div> 433 <div id='summary-text'><pre>%s</pre></div> 434 <div id='summary-tsv'><pre>%s</pre></div> 435 </div> 436 %s 437 </div> 438 %s 439 <div class='results-section'> 440 <div class='results-section-title'>Charts</div> 441 <div class='results-section-content'>%s</div> 442 </div> 443 <div class='results-section'> 444 <div class='results-section-title'>Full Table</div> 445 <div class='results-section-content'> 446 <div id='full-html'>%s</div> 447 <div id='full-text'><pre>%s</pre></div> 448 <div id='full-tsv'><pre>%s</pre></div> 449 </div> 450 %s 451 </div> 452 <div class='results-section'> 453 <div class='results-section-title'>Experiment File</div> 454 <div class='results-section-content'> 455 <pre>%s</pre> 456 </div> 457 </div> 458 </body> 459</html> 460""" 461 462 PERF_HTML = """ 463 <div class='results-section'> 464 <div class='results-section-title'>Perf Table</div> 465 <div class='results-section-content'> 466 <div id='perf-html'>%s</div> 467 <div id='perf-text'><pre>%s</pre></div> 468 <div id='perf-tsv'><pre>%s</pre></div> 469 </div> 470 %s 471 </div> 472""" 473 474 def __init__(self, experiment): 475 super(HTMLResultsReport, self).__init__(experiment) 476 477 def _GetTabMenuHTML(self, table): 478 return """ 479<div class='tab-menu'> 480 <a href="javascript:switchTab('%s', 'html')">HTML</a> 481 <a href="javascript:switchTab('%s', 'text')">Text</a> 482 <a href="javascript:switchTab('%s', 'tsv')">TSV</a> 483</div>""" % (table, table, table) 484 485 def GetReport(self): 486 chart_javascript = "" 487 charts = self._GetCharts(self.labels, self.benchmark_runs) 488 for chart in charts: 489 chart_javascript += chart.GetJavascript() 490 chart_divs = "" 491 for chart in charts: 492 chart_divs += chart.GetDiv() 493 494 summary_table = self.GetSummaryTables() 495 full_table = self.GetFullTables() 496 perf_table = self.GetSummaryTables(perf=True) 497 if perf_table: 498 perf_html = self.PERF_HTML % ( 499 self.PrintTables(perf_table, "HTML"), 500 self.PrintTables(perf_table, "PLAIN"), 501 self.PrintTables(perf_table, "TSV"), 502 self._GetTabMenuHTML("perf") 503 ) 504 perf_init = "switchTab('perf', 'html');" 505 else: 506 perf_html = "" 507 perf_init = "" 508 509 return self.HTML % (perf_init, 510 chart_javascript, 511 self.PrintTables(summary_table, "HTML"), 512 self.PrintTables(summary_table, "PLAIN"), 513 self.PrintTables(summary_table, "TSV"), 514 self._GetTabMenuHTML("summary"), 515 perf_html, 516 chart_divs, 517 self.PrintTables(full_table, "HTML"), 518 self.PrintTables(full_table, "PLAIN"), 519 self.PrintTables(full_table, "TSV"), 520 self._GetTabMenuHTML("full"), 521 self.experiment.experiment_file) 522 523 def _GetCharts(self, labels, benchmark_runs): 524 charts = [] 525 ro = ResultOrganizer(benchmark_runs, labels) 526 result = ro.result 527 for item in result: 528 runs = result[item] 529 tg = TableGenerator(runs, ro.labels) 530 table = tg.GetTable() 531 columns = [Column(AmeanResult(), 532 Format()), 533 Column(MinResult(), 534 Format()), 535 Column(MaxResult(), 536 Format()) 537 ] 538 tf = TableFormatter(table, columns) 539 data_table = tf.GetCellTable("full") 540 541 for i in range(2, len(data_table)): 542 cur_row_data = data_table[i] 543 test_key = cur_row_data[0].string_value 544 title = "{0}: {1}".format(item, test_key.replace("/", "")) 545 chart = ColumnChart(title, 300, 200) 546 chart.AddColumn("Label", "string") 547 chart.AddColumn("Average", "number") 548 chart.AddColumn("Min", "number") 549 chart.AddColumn("Max", "number") 550 chart.AddSeries("Min", "line", "black") 551 chart.AddSeries("Max", "line", "black") 552 cur_index = 1 553 for label in ro.labels: 554 chart.AddRow([label, cur_row_data[cur_index].value, 555 cur_row_data[cur_index + 1].value, 556 cur_row_data[cur_index + 2].value]) 557 if isinstance(cur_row_data[cur_index].value, str): 558 chart = None 559 break 560 cur_index += 3 561 if chart: 562 charts.append(chart) 563 return charts 564 565class JSONResultsReport(ResultsReport): 566 567 def __init__(self, experiment, date=None, time=None): 568 super(JSONResultsReport, self).__init__(experiment) 569 self.ro = ResultOrganizer(experiment.benchmark_runs, 570 experiment.labels, 571 experiment.benchmarks, 572 json_report=True) 573 self.date = date 574 self.time = time 575 self.defaults = TelemetryDefaults() 576 if not self.date: 577 timestamp = datetime.datetime.strftime(datetime.datetime.now(), 578 "%Y-%m-%d %H:%M:%S") 579 date, time = timestamp.split(" ") 580 self.date = date 581 self.time = time 582 583 def GetReport(self, results_dir): 584 self.defaults.ReadDefaultsFile() 585 final_results = [] 586 board = self.experiment.labels[0].board 587 for test, test_results in self.ro.result.iteritems(): 588 for i, label in enumerate(self.ro.labels): 589 label_results = test_results[i] 590 for j, iter_Results in enumerate(label_results): 591 iter_results = label_results[j] 592 json_results = dict() 593 json_results['date'] = self.date 594 json_results['time'] = self.time 595 json_results['board'] = board 596 json_results['label'] = label 597 common_checksum = '' 598 common_string = '' 599 for l in self.experiment.labels: 600 if l.name == label: 601 ver, img = ParseChromeosImage(l.chromeos_image) 602 json_results['chromeos_image'] = img 603 json_results['chromeos_version'] = ver 604 common_checksum = \ 605 self.experiment.machine_manager.machine_checksum[l.name] 606 common_string = \ 607 self.experiment.machine_manager.machine_checksum_string[l.name] 608 break 609 json_results['test_name'] = test 610 if not iter_results or iter_results['retval'] != 0: 611 json_results['pass'] = False 612 else: 613 json_results['pass'] = True 614 # Get overall results. 615 if test in self.defaults._defaults: 616 default_result_fields = self.defaults._defaults[test] 617 value = [] 618 for f in default_result_fields: 619 v = iter_results[f] 620 if type(v) == list: 621 v = v[0] 622 item = (f, float(v)) 623 value.append(item) 624 json_results['overall_result'] = value 625 # Get detailed results. 626 detail_results = dict() 627 for k in iter_results.keys(): 628 if k != 'retval': 629 v = iter_results[k] 630 if type(v) == list: 631 v = v[0] 632 if v != 'PASS': 633 if k.find('machine') == -1: 634 detail_results[k] = float(v) 635 else: 636 json_results[k] = v 637 if 'machine_checksum' not in json_results.keys(): 638 json_results['machine_checksum'] = common_checksum 639 if 'machine_string' not in json_results.keys(): 640 json_results['machine_string'] = common_string 641 json_results['detailed_results'] = detail_results 642 final_results.append(json_results) 643 644 filename = "report_%s_%s_%s.json" % (board, self.date, 645 self.time.replace(':','.')) 646 fullname = os.path.join(results_dir, filename) 647 with open(fullname, "w") as fp: 648 json.dump(final_results, fp, indent=2) 649