generate-waterfall-reports.py revision faa3c555dd0b636cdd19a42399b152fc77b8cbe3
1#!/usr/bin/env python2 2"""Generate summary report for ChromeOS toolchain waterfalls.""" 3 4# Desired future features (to be added): 5# - arguments to allow generating only the main waterfall report, 6# or only the rotating builder reports, or only the failures 7# report; or the waterfall reports without the failures report. 8# - Better way of figuring out which dates/builds to generate 9# reports for: probably an argument specifying a date or a date 10# range, then use something like the new buildbot utils to 11# query the build logs to find the right build numbers for the 12# builders for the specified dates. 13# - Store/get the json/data files in mobiletc-prebuild's x20 area. 14# - Update data in json file to reflect, for each testsuite, which 15# tests are not expected to run on which boards; update this 16# script to use that data appropriately. 17# - Make sure user's prodaccess is up-to-date before trying to use 18# this script. 19# - Add some nice formatting/highlighting to reports. 20 21from __future__ import print_function 22 23import argparse 24import getpass 25import json 26import os 27import shutil 28import sys 29import time 30 31from cros_utils import command_executer 32 33# All the test suites whose data we might want for the reports. 34TESTS = ( 35 ('bvt-inline', 'HWTest'), 36 ('bvt-cq', 'HWTest'), 37 ('toolchain-tests', 'HWTest'), 38 ('security', 'HWTest'), 39 ('kernel_daily_regression', 'HWTest'), 40 ('kernel_daily_benchmarks', 'HWTest'),) 41 42# The main waterfall builders, IN THE ORDER IN WHICH WE WANT THEM 43# LISTED IN THE REPORT. 44WATERFALL_BUILDERS = [ 45 'amd64-gcc-toolchain', 'arm-gcc-toolchain', 'arm64-gcc-toolchain', 46 'x86-gcc-toolchain', 'amd64-llvm-toolchain', 'arm-llvm-toolchain', 47 'arm64-llvm-toolchain', 'x86-llvm-toolchain', 'amd64-llvm-next-toolchain', 48 'arm-llvm-next-toolchain', 'arm64-llvm-next-toolchain', 49 'x86-llvm-next-toolchain' 50] 51 52DATA_DIR = '/usr/local/google2/cmtice/toolchain-utils/waterfall-report-data/' 53ARCHIVE_DIR = '/usr/local/google2/cmtice/toolchain-utils/waterfall-reports/' 54#DATA_DIR = '/google/data/rw/users/mo/mobiletc-prebuild/waterfall-report-data/' 55#ARCHIVE_DIR = '/google/data/rw/users/mo/mobiletc-prebuild/waterfall-reports/' 56DOWNLOAD_DIR = '/tmp/waterfall-logs' 57MAX_SAVE_RECORDS = 7 58BUILD_DATA_FILE = '%s/build-data.txt' % DATA_DIR 59GCC_ROTATING_BUILDER = 'gcc_toolchain' 60LLVM_ROTATING_BUILDER = 'llvm_next_toolchain' 61ROTATING_BUILDERS = [GCC_ROTATING_BUILDER, LLVM_ROTATING_BUILDER] 62 63# For int-to-string date conversion. Note, the index of the month in this 64# list needs to correspond to the month's integer value. i.e. 'Sep' must 65# be as MONTHS[9]. 66MONTHS = [ 67 '', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 68 'Nov', 'Dec' 69] 70 71 72def format_date(int_date): 73 """Convert an integer date to a string date. YYYYMMDD -> YYYY-MMM-DD""" 74 75 if int_date == 0: 76 return 'today' 77 78 tmp_date = int_date 79 day = tmp_date % 100 80 tmp_date = tmp_date / 100 81 month = tmp_date % 100 82 year = tmp_date / 100 83 84 month_str = MONTHS[month] 85 date_str = '%d-%s-%d' % (year, month_str, day) 86 return date_str 87 88 89def EmailReport(report_file, report_type, date): 90 subject = '%s Waterfall Summary report, %s' % (report_type, date) 91 email_to = getpass.getuser() 92 sendgmr_path = '/google/data/ro/projects/gws-sre/sendgmr' 93 command = ('%s --to=%s@google.com --subject="%s" --body_file=%s' % 94 (sendgmr_path, email_to, subject, report_file)) 95 command_executer.GetCommandExecuter().RunCommand(command) 96 97 98def PruneOldFailures(failure_dict, int_date): 99 earliest_date = int_date - MAX_SAVE_RECORDS 100 for suite in failure_dict: 101 suite_dict = failure_dict[suite] 102 test_keys_to_remove = [] 103 for test in suite_dict: 104 test_dict = suite_dict[test] 105 msg_keys_to_remove = [] 106 for msg in test_dict: 107 fails = test_dict[msg] 108 i = 0 109 while i < len(fails) and fails[i][0] <= earliest_date: 110 i += 1 111 new_fails = fails[i:] 112 test_dict[msg] = new_fails 113 if len(new_fails) == 0: 114 msg_keys_to_remove.append(msg) 115 116 for k in msg_keys_to_remove: 117 del test_dict[k] 118 119 suite_dict[test] = test_dict 120 if len(test_dict) == 0: 121 test_keys_to_remove.append(test) 122 123 for k in test_keys_to_remove: 124 del suite_dict[k] 125 126 failure_dict[suite] = suite_dict 127 128 129def GenerateFailuresReport(fail_dict, date): 130 filename = 'waterfall_report.failures.%s.txt' % date 131 date_string = format_date(date) 132 with open(filename, 'w') as out_file: 133 # Write failure report section. 134 out_file.write('\n\nSummary of Test Failures as of %s\n\n' % date_string) 135 136 # We want to sort the errors and output them in order of the ones that occur 137 # most often. So we have to collect the data about all of them, then sort 138 # it. 139 error_groups = [] 140 for suite in fail_dict: 141 suite_dict = fail_dict[suite] 142 if suite_dict: 143 for test in suite_dict: 144 test_dict = suite_dict[test] 145 for err_msg in test_dict: 146 err_list = test_dict[err_msg] 147 sorted_list = sorted(err_list, key=lambda x: x[0], reverse=True) 148 err_group = [len(sorted_list), suite, test, err_msg, sorted_list] 149 error_groups.append(err_group) 150 151 # Sort the errors by the number of errors of each type. Then output them in 152 # order. 153 sorted_errors = sorted(error_groups, key=lambda x: x[0], reverse=True) 154 for i in range(0, len(sorted_errors)): 155 err_group = sorted_errors[i] 156 suite = err_group[1] 157 test = err_group[2] 158 err_msg = err_group[3] 159 err_list = err_group[4] 160 out_file.write('Suite: %s\n' % suite) 161 out_file.write(' %s (%d failures)\n' % (test, len(err_list))) 162 out_file.write(' (%s)\n' % err_msg) 163 for i in range(0, len(err_list)): 164 err = err_list[i] 165 out_file.write(' %s, %s, %s\n' % (format_date(err[0]), err[1], 166 err[2])) 167 out_file.write('\n') 168 169 print('Report generated in %s.' % filename) 170 return filename 171 172 173def GenerateWaterfallReport(report_dict, fail_dict, waterfall_type, date, 174 omit_failures): 175 """Write out the actual formatted report.""" 176 177 filename = 'waterfall_report.%s_waterfall.%s.txt' % (waterfall_type, date) 178 179 date_string = '' 180 date_list = report_dict['date'] 181 num_dates = len(date_list) 182 i = 0 183 for d in date_list: 184 date_string += d 185 if i < num_dates - 1: 186 date_string += ', ' 187 i += 1 188 189 if waterfall_type == 'main': 190 report_list = WATERFALL_BUILDERS 191 else: 192 report_list = report_dict.keys() 193 194 with open(filename, 'w') as out_file: 195 # Write Report Header 196 out_file.write('\nStatus of %s Waterfall Builds from %s\n\n' % 197 (waterfall_type, date_string)) 198 out_file.write(' ' 199 ' kernel kernel\n') 200 out_file.write(' Build bvt- bvt-cq ' 201 'toolchain- security daily daily\n') 202 out_file.write(' status inline ' 203 ' tests regression benchmarks\n') 204 out_file.write(' [P/ F/ DR]* [P/ F /DR]* ' 205 '[P/ F/ DR]* [P/ F/ DR]* [P/ F/ DR]* [P/ F/ DR]*\n\n') 206 207 # Write daily waterfall status section. 208 for i in range(0, len(report_list)): 209 builder = report_list[i] 210 if builder == 'date': 211 continue 212 213 if builder not in report_dict: 214 out_file.write('Unable to find information for %s.\n\n' % builder) 215 continue 216 217 build_dict = report_dict[builder] 218 status = build_dict.get('build_status', 'bad') 219 inline = build_dict.get('bvt-inline', '[??/ ?? /??]') 220 cq = build_dict.get('bvt-cq', '[??/ ?? /??]') 221 inline_color = build_dict.get('bvt-inline-color', '') 222 cq_color = build_dict.get('bvt-cq-color', '') 223 if 'x86' not in builder: 224 toolchain = build_dict.get('toolchain-tests', '[??/ ?? /??]') 225 security = build_dict.get('security', '[??/ ?? /??]') 226 toolchain_color = build_dict.get('toolchain-tests-color', '') 227 security_color = build_dict.get('security-color', '') 228 if 'gcc' in builder: 229 regression = build_dict.get('kernel_daily_regression', '[??/ ?? /??]') 230 bench = build_dict.get('kernel_daily_benchmarks', '[??/ ?? /??]') 231 regression_color = build_dict.get('kernel_daily_regression-color', '') 232 bench_color = build_dict.get('kernel_daily_benchmarks-color', '') 233 out_file.write(' %6s %6s' 234 ' %6s %6s %6s %6s\n' % 235 (inline_color, cq_color, toolchain_color, 236 security_color, regression_color, bench_color)) 237 out_file.write('%25s %3s %s %s %s %s %s %s\n' % (builder, status, 238 inline, cq, 239 toolchain, security, 240 regression, bench)) 241 else: 242 out_file.write(' %6s %6s' 243 ' %6s %6s\n' % (inline_color, cq_color, 244 toolchain_color, 245 security_color)) 246 out_file.write('%25s %3s %s %s %s %s\n' % (builder, status, inline, 247 cq, toolchain, security)) 248 else: 249 out_file.write(' %6s %6s\n' % 250 (inline_color, cq_color)) 251 out_file.write('%25s %3s %s %s\n' % (builder, status, inline, cq)) 252 if 'build_link' in build_dict: 253 out_file.write('%s\n\n' % build_dict['build_link']) 254 255 out_file.write('\n\n*P = Number of tests in suite that Passed; F = ' 256 'Number of tests in suite that Failed; DR = Number of tests' 257 ' in suite that Didn\'t Run.\n') 258 259 if omit_failures: 260 print('Report generated in %s.' % filename) 261 return filename 262 263 # Write failure report section. 264 out_file.write('\n\nSummary of Test Failures as of %s\n\n' % date_string) 265 266 # We want to sort the errors and output them in order of the ones that occur 267 # most often. So we have to collect the data about all of them, then sort 268 # it. 269 error_groups = [] 270 for suite in fail_dict: 271 suite_dict = fail_dict[suite] 272 if suite_dict: 273 for test in suite_dict: 274 test_dict = suite_dict[test] 275 for err_msg in test_dict: 276 err_list = test_dict[err_msg] 277 sorted_list = sorted(err_list, key=lambda x: x[0], reverse=True) 278 err_group = [len(sorted_list), suite, test, err_msg, sorted_list] 279 error_groups.append(err_group) 280 281 # Sort the errors by the number of errors of each type. Then output them in 282 # order. 283 sorted_errors = sorted(error_groups, key=lambda x: x[0], reverse=True) 284 for i in range(0, len(sorted_errors)): 285 err_group = sorted_errors[i] 286 suite = err_group[1] 287 test = err_group[2] 288 err_msg = err_group[3] 289 err_list = err_group[4] 290 out_file.write('Suite: %s\n' % suite) 291 out_file.write(' %s (%d failures)\n' % (test, len(err_list))) 292 out_file.write(' (%s)\n' % err_msg) 293 for i in range(0, len(err_list)): 294 err = err_list[i] 295 out_file.write(' %s, %s, %s\n' % (format_date(err[0]), err[1], 296 err[2])) 297 out_file.write('\n') 298 299 print('Report generated in %s.' % filename) 300 return filename 301 302 303def UpdateReport(report_dict, builder, test, report_date, build_link, 304 test_summary, board, color): 305 """Update the data in our report dictionary with current test's data.""" 306 307 if 'date' not in report_dict: 308 report_dict['date'] = [report_date] 309 elif report_date not in report_dict['date']: 310 # It is possible that some of the builders started/finished on different 311 # days, so we allow for multiple dates in the reports. 312 report_dict['date'].append(report_date) 313 314 build_key = '' 315 if builder == GCC_ROTATING_BUILDER: 316 build_key = '%s-gcc-toolchain' % board 317 elif builder == LLVM_ROTATING_BUILDER: 318 build_key = '%s-llvm-next-toolchain' % board 319 else: 320 build_key = builder 321 322 if build_key not in report_dict.keys(): 323 build_dict = dict() 324 else: 325 build_dict = report_dict[build_key] 326 327 if 'build_link' not in build_dict: 328 build_dict['build_link'] = build_link 329 330 if 'date' not in build_dict: 331 build_dict['date'] = report_date 332 333 if 'board' in build_dict and build_dict['board'] != board: 334 raise RuntimeError('Error: Two different boards (%s,%s) in one build (%s)!' 335 % (board, build_dict['board'], build_link)) 336 build_dict['board'] = board 337 338 color_key = '%s-color' % test 339 build_dict[color_key] = color 340 341 # Check to see if we already have a build status for this build_key 342 status = '' 343 if 'build_status' in build_dict.keys(): 344 # Use current build_status, unless current test failed (see below). 345 status = build_dict['build_status'] 346 347 if not test_summary: 348 # Current test data was not available, so something was bad with build. 349 build_dict['build_status'] = 'bad' 350 build_dict[test] = '[ no data ]' 351 else: 352 build_dict[test] = test_summary 353 if not status: 354 # Current test ok; no other data, so assume build was ok. 355 build_dict['build_status'] = 'ok' 356 357 report_dict[build_key] = build_dict 358 359 360def UpdateBuilds(builds): 361 """Update the data in our build-data.txt file.""" 362 363 # The build data file records the last build number for which we 364 # generated a report. When we generate the next report, we read 365 # this data and increment it to get the new data; when we finish 366 # generating the reports, we write the updated values into this file. 367 # NOTE: One side effect of doing this at the end: If the script 368 # fails in the middle of generating a report, this data does not get 369 # updated. 370 with open(BUILD_DATA_FILE, 'w') as fp: 371 gcc_max = 0 372 llvm_max = 0 373 for b in builds: 374 if b[0] == GCC_ROTATING_BUILDER: 375 gcc_max = max(gcc_max, b[1]) 376 elif b[0] == LLVM_ROTATING_BUILDER: 377 llvm_max = max(llvm_max, b[1]) 378 else: 379 fp.write('%s,%d\n' % (b[0], b[1])) 380 if gcc_max > 0: 381 fp.write('%s,%d\n' % (GCC_ROTATING_BUILDER, gcc_max)) 382 if llvm_max > 0: 383 fp.write('%s,%d\n' % (LLVM_ROTATING_BUILDER, llvm_max)) 384 385 386def GetBuilds(): 387 """Read build-data.txt to determine values for current report.""" 388 389 # Read the values of the last builds used to generate a report, and 390 # increment them appropriately, to get values for generating the 391 # current report. (See comments in UpdateBuilds). 392 with open(BUILD_DATA_FILE, 'r') as fp: 393 lines = fp.readlines() 394 395 builds = [] 396 for l in lines: 397 l = l.rstrip() 398 words = l.split(',') 399 builder = words[0] 400 build = int(words[1]) 401 builds.append((builder, build + 1)) 402 # NOTE: We are assuming here that there are always 2 daily builds in 403 # each of the rotating builders. I am not convinced this is a valid 404 # assumption. 405 if builder in ROTATING_BUILDERS: 406 builds.append((builder, build + 2)) 407 408 return builds 409 410 411def RecordFailures(failure_dict, platform, suite, builder, int_date, log_file, 412 build_num, failed): 413 """Read and update the stored data about test failures.""" 414 415 # Get the dictionary for this particular test suite from the failures 416 # dictionary. 417 suite_dict = failure_dict[suite] 418 419 # Read in the entire log file for this test/build. 420 with open(log_file, 'r') as in_file: 421 lines = in_file.readlines() 422 423 # Update the entries in the failure dictionary for each test within this suite 424 # that failed. 425 for test in failed: 426 # Check to see if there is already an entry in the suite dictionary for this 427 # test; if so use that, otherwise create a new entry. 428 if test in suite_dict: 429 test_dict = suite_dict[test] 430 else: 431 test_dict = dict() 432 # Parse the lines from the log file, looking for lines that indicate this 433 # test failed. 434 msg = '' 435 for l in lines: 436 words = l.split() 437 if len(words) < 3: 438 continue 439 if ((words[0] == test and words[1] == 'ERROR:') or 440 (words[0] == 'provision' and words[1] == 'FAIL:')): 441 words = words[2:] 442 # Get the error message for the failure. 443 msg = ' '.join(words) 444 if not msg: 445 msg = 'Unknown_Error' 446 447 # Look for an existing entry for this error message in the test dictionary. 448 # If found use that, otherwise create a new entry for this error message. 449 if msg in test_dict: 450 error_list = test_dict[msg] 451 else: 452 error_list = list() 453 # Create an entry for this new failure 454 new_item = [int_date, platform, builder, build_num] 455 # Add this failure to the error list if it's not already there. 456 if new_item not in error_list: 457 error_list.append([int_date, platform, builder, build_num]) 458 # Sort the error list by date. 459 error_list.sort(key=lambda x: x[0]) 460 # Calculate the earliest date to save; delete records for older failures. 461 earliest_date = int_date - MAX_SAVE_RECORDS 462 i = 0 463 while i < len(error_list) and error_list[i][0] <= earliest_date: 464 i += 1 465 if i > 0: 466 error_list = error_list[i:] 467 # Save the error list in the test's dictionary, keyed on error_msg. 468 test_dict[msg] = error_list 469 470 # Save the updated test dictionary in the test_suite dictionary. 471 suite_dict[test] = test_dict 472 473 # Save the updated test_suite dictionary in the failure dictionary. 474 failure_dict[suite] = suite_dict 475 476 477def ParseLogFile(log_file, test_data_dict, failure_dict, test, builder, 478 build_num, build_link): 479 """Parse the log file from the given builder, build_num and test. 480 481 Also adds the results for this test to our test results dictionary, 482 and calls RecordFailures, to update our test failure data. 483 """ 484 485 lines = [] 486 with open(log_file, 'r') as infile: 487 lines = infile.readlines() 488 489 passed = {} 490 failed = {} 491 not_run = {} 492 date = '' 493 status = '' 494 board = '' 495 num_provision_errors = 0 496 build_ok = True 497 afe_line = '' 498 499 for line in lines: 500 if line.rstrip() == '<title>404 Not Found</title>': 501 print('Warning: File for %s (build number %d), %s was not found.' % 502 (builder, build_num, test)) 503 build_ok = False 504 break 505 if '[ PASSED ]' in line: 506 test_name = line.split()[0] 507 if test_name != 'Suite': 508 passed[test_name] = True 509 elif '[ FAILED ]' in line: 510 test_name = line.split()[0] 511 if test_name == 'provision': 512 num_provision_errors += 1 513 not_run[test_name] = True 514 elif test_name != 'Suite': 515 failed[test_name] = True 516 elif line.startswith('started: '): 517 date = line.rstrip() 518 date = date[9:] 519 date_obj = time.strptime(date, '%a %b %d %H:%M:%S %Y') 520 int_date = ( 521 date_obj.tm_year * 10000 + date_obj.tm_mon * 100 + date_obj.tm_mday) 522 date = time.strftime('%a %b %d %Y', date_obj) 523 elif not status and line.startswith('status: '): 524 status = line.rstrip() 525 words = status.split(':') 526 status = words[-1] 527 elif line.find('Suite passed with a warning') != -1: 528 status = 'WARNING' 529 elif line.startswith('@@@STEP_LINK@Link to suite@'): 530 afe_line = line.rstrip() 531 words = afe_line.split('@') 532 for w in words: 533 if w.startswith('http'): 534 afe_line = w 535 afe_line = afe_line.replace('&', '&') 536 elif 'INFO: RunCommand:' in line: 537 words = line.split() 538 for i in range(0, len(words) - 1): 539 if words[i] == '--board': 540 board = words[i + 1] 541 542 test_dict = test_data_dict[test] 543 test_list = test_dict['tests'] 544 545 if build_ok: 546 for t in test_list: 547 if not t in passed and not t in failed: 548 not_run[t] = True 549 550 total_pass = len(passed) 551 total_fail = len(failed) 552 total_notrun = len(not_run) 553 554 else: 555 total_pass = 0 556 total_fail = 0 557 total_notrun = 0 558 status = 'Not found.' 559 if not build_ok: 560 return [], date, board, 0, ' ' 561 562 build_dict = dict() 563 build_dict['id'] = build_num 564 build_dict['builder'] = builder 565 build_dict['date'] = date 566 build_dict['build_link'] = build_link 567 build_dict['total_pass'] = total_pass 568 build_dict['total_fail'] = total_fail 569 build_dict['total_not_run'] = total_notrun 570 build_dict['afe_job_link'] = afe_line 571 build_dict['provision_errors'] = num_provision_errors 572 if status.strip() == 'SUCCESS': 573 build_dict['color'] = 'green ' 574 elif status.strip() == 'FAILURE': 575 build_dict['color'] = ' red ' 576 elif status.strip() == 'WARNING': 577 build_dict['color'] = 'orange' 578 else: 579 build_dict['color'] = ' ' 580 581 # Use YYYYMMDD (integer) as the build record key 582 if build_ok: 583 if board in test_dict: 584 board_dict = test_dict[board] 585 else: 586 board_dict = dict() 587 board_dict[int_date] = build_dict 588 589 # Only keep the last 5 records (based on date) 590 keys_list = board_dict.keys() 591 if len(keys_list) > MAX_SAVE_RECORDS: 592 min_key = min(keys_list) 593 del board_dict[min_key] 594 595 # Make sure changes get back into the main dictionary 596 test_dict[board] = board_dict 597 test_data_dict[test] = test_dict 598 599 if len(failed) > 0: 600 RecordFailures(failure_dict, board, test, builder, int_date, log_file, 601 build_num, failed) 602 603 summary_result = '[%2d/ %2d/ %2d]' % (total_pass, total_fail, total_notrun) 604 605 return summary_result, date, board, int_date, build_dict['color'] 606 607 608def DownloadLogFile(builder, buildnum, test, test_family): 609 610 ce = command_executer.GetCommandExecuter() 611 os.system('mkdir -p %s/%s/%s' % (DOWNLOAD_DIR, builder, test)) 612 if builder in ROTATING_BUILDERS: 613 source = ('https://uberchromegw.corp.google.com/i/chromiumos.tryserver' 614 '/builders/%s/builds/%d/steps/%s%%20%%5B%s%%5D/logs/stdio' % 615 (builder, buildnum, test_family, test)) 616 build_link = ('https://uberchromegw.corp.google.com/i/chromiumos.tryserver' 617 '/builders/%s/builds/%d' % (builder, buildnum)) 618 else: 619 source = ('https://uberchromegw.corp.google.com/i/chromeos/builders/%s/' 620 'builds/%d/steps/%s%%20%%5B%s%%5D/logs/stdio' % 621 (builder, buildnum, test_family, test)) 622 build_link = ('https://uberchromegw.corp.google.com/i/chromeos/builders/%s' 623 '/builds/%d' % (builder, buildnum)) 624 625 target = '%s/%s/%s/%d' % (DOWNLOAD_DIR, builder, test, buildnum) 626 if not os.path.isfile(target) or os.path.getsize(target) == 0: 627 cmd = 'sso_client %s > %s' % (source, target) 628 status = ce.RunCommand(cmd) 629 if status != 0: 630 return '', '' 631 632 return target, build_link 633 634 635# Check for prodaccess. 636def CheckProdAccess(): 637 status, output, _ = command_executer.GetCommandExecuter().RunCommandWOutput( 638 'prodcertstatus') 639 if status != 0: 640 return False 641 # Verify that status is not expired 642 if 'expires' in output: 643 return True 644 return False 645 646 647def ValidOptions(parser, options): 648 too_many_options = False 649 if options.main: 650 if options.rotating or options.failures_report: 651 too_many_options = True 652 elif options.rotating and options.failures_report: 653 too_many_options = True 654 655 if too_many_options: 656 parser.error('Can only specify one of --main, --rotating or' 657 ' --failures_report.') 658 659 conflicting_failure_options = False 660 if options.failures_report and options.omit_failures: 661 conflicting_failure_options = True 662 parser.error('Cannot specify both --failures_report and --omit_failures.') 663 664 return not too_many_options and not conflicting_failure_options 665 666 667def Main(argv): 668 """Main function for this script.""" 669 parser = argparse.ArgumentParser() 670 parser.add_argument( 671 '--main', 672 dest='main', 673 default=False, 674 action='store_true', 675 help='Generate report only for main waterfall ' 676 'builders.') 677 parser.add_argument( 678 '--rotating', 679 dest='rotating', 680 default=False, 681 action='store_true', 682 help='Generate report only for rotating builders.') 683 parser.add_argument( 684 '--failures_report', 685 dest='failures_report', 686 default=False, 687 action='store_true', 688 help='Only generate the failures section of the report.') 689 parser.add_argument( 690 '--omit_failures', 691 dest='omit_failures', 692 default=False, 693 action='store_true', 694 help='Do not generate the failures section of the report.') 695 parser.add_argument( 696 '--no_update', 697 dest='no_update', 698 default=False, 699 action='store_true', 700 help='Run reports, but do not update the data files.') 701 702 options = parser.parse_args(argv) 703 704 if not ValidOptions(parser, options): 705 return 1 706 707 main_only = options.main 708 rotating_only = options.rotating 709 failures_report = options.failures_report 710 omit_failures = options.omit_failures 711 712 test_data_dict = dict() 713 failure_dict = dict() 714 715 prod_access = CheckProdAccess() 716 if not prod_access: 717 print('ERROR: Please run prodaccess first.') 718 return 719 720 with open('%s/waterfall-test-data.json' % DATA_DIR, 'r') as input_file: 721 test_data_dict = json.load(input_file) 722 723 with open('%s/test-failure-data.json' % DATA_DIR, 'r') as fp: 724 failure_dict = json.load(fp) 725 726 builds = GetBuilds() 727 728 waterfall_report_dict = dict() 729 rotating_report_dict = dict() 730 int_date = 0 731 for test_desc in TESTS: 732 test, test_family = test_desc 733 for build in builds: 734 (builder, buildnum) = build 735 if test.startswith('kernel') and 'llvm' in builder: 736 continue 737 if 'x86' in builder and not test.startswith('bvt'): 738 continue 739 target, build_link = DownloadLogFile(builder, buildnum, test, test_family) 740 741 if os.path.exists(target): 742 test_summary, report_date, board, tmp_date, color = ParseLogFile( 743 target, test_data_dict, failure_dict, test, builder, buildnum, 744 build_link) 745 746 if tmp_date != 0: 747 int_date = tmp_date 748 749 if builder in ROTATING_BUILDERS: 750 UpdateReport(rotating_report_dict, builder, test, report_date, 751 build_link, test_summary, board, color) 752 else: 753 UpdateReport(waterfall_report_dict, builder, test, report_date, 754 build_link, test_summary, board, color) 755 756 PruneOldFailures(failure_dict, int_date) 757 758 if waterfall_report_dict and not rotating_only and not failures_report: 759 main_report = GenerateWaterfallReport(waterfall_report_dict, failure_dict, 760 'main', int_date, omit_failures) 761 EmailReport(main_report, 'Main', format_date(int_date)) 762 shutil.copy(main_report, ARCHIVE_DIR) 763 if rotating_report_dict and not main_only and not failures_report: 764 rotating_report = GenerateWaterfallReport(rotating_report_dict, 765 failure_dict, 'rotating', 766 int_date, omit_failures) 767 EmailReport(rotating_report, 'Rotating', format_date(int_date)) 768 shutil.copy(rotating_report, ARCHIVE_DIR) 769 770 if failures_report: 771 failures_report = GenerateFailuresReport(failure_dict, int_date) 772 EmailReport(failures_report, 'Failures', format_date(int_date)) 773 shutil.copy(failures_report, ARCHIVE_DIR) 774 775 if not options.no_update: 776 with open('%s/waterfall-test-data.json' % DATA_DIR, 'w') as out_file: 777 json.dump(test_data_dict, out_file, indent=2) 778 779 with open('%s/test-failure-data.json' % DATA_DIR, 'w') as out_file: 780 json.dump(failure_dict, out_file, indent=2) 781 782 UpdateBuilds(builds) 783 784 785if __name__ == '__main__': 786 Main(sys.argv[1:]) 787 sys.exit(0) 788