diff options
Diffstat (limited to 'deprecated/new-generate-waterfall-reports.py')
-rwxr-xr-x | deprecated/new-generate-waterfall-reports.py | 410 |
1 files changed, 410 insertions, 0 deletions
diff --git a/deprecated/new-generate-waterfall-reports.py b/deprecated/new-generate-waterfall-reports.py new file mode 100755 index 00000000..ef48f8be --- /dev/null +++ b/deprecated/new-generate-waterfall-reports.py @@ -0,0 +1,410 @@ +#!/usr/bin/env python2 +"""Generate summary report for ChromeOS toolchain waterfalls.""" + +from __future__ import print_function + +import argparse +import datetime +import getpass +import json +import os +import re +import shutil +import sys +import time + +from cros_utils import command_executer + +# All the test suites whose data we might want for the reports. +TESTS = (('bvt-inline', 'HWTest [bvt-inline]'), ('bvt-cq', 'HWTest [bvt-cq]'), + ('security', 'HWTest [security]')) + +# The main waterfall builders, IN THE ORDER IN WHICH WE WANT THEM +# LISTED IN THE REPORT. +WATERFALL_BUILDERS = [ + 'amd64-llvm-next-toolchain', + 'arm-llvm-next-toolchain', + 'arm64-llvm-next-toolchain', +] + +DATA_DIR = '/google/data/rw/users/mo/mobiletc-prebuild/waterfall-report-data/' +ARCHIVE_DIR = '/google/data/rw/users/mo/mobiletc-prebuild/waterfall-reports/' +DOWNLOAD_DIR = '/tmp/waterfall-logs' +MAX_SAVE_RECORDS = 7 +BUILD_DATA_FILE = '%s/build-data.txt' % DATA_DIR +LLVM_ROTATING_BUILDER = 'llvm_next_toolchain' +ROTATING_BUILDERS = [LLVM_ROTATING_BUILDER] + +# For int-to-string date conversion. Note, the index of the month in this +# list needs to correspond to the month's integer value. i.e. 'Sep' must +# be as MONTHS[9]. +MONTHS = [ + '', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', + 'Nov', 'Dec' +] + +DAYS_PER_MONTH = { + 1: 31, + 2: 28, + 3: 31, + 4: 30, + 5: 31, + 6: 30, + 7: 31, + 8: 31, + 9: 30, + 10: 31, + 11: 31, + 12: 31 +} + + +def format_date(int_date, use_int_month=False): + """Convert an integer date to a string date. YYYYMMDD -> YYYY-MMM-DD""" + + if int_date == 0: + return 'today' + + tmp_date = int_date + day = tmp_date % 100 + tmp_date = tmp_date / 100 + month = tmp_date % 100 + year = tmp_date / 100 + + if use_int_month: + date_str = '%d-%02d-%02d' % (year, month, day) + else: + month_str = MONTHS[month] + date_str = '%d-%s-%d' % (year, month_str, day) + return date_str + + +def EmailReport(report_file, report_type, date, email_to): + """Emails the report to the approprite address.""" + subject = '%s Waterfall Summary report, %s' % (report_type, date) + sendgmr_path = '/google/data/ro/projects/gws-sre/sendgmr' + command = ('%s --to=%s --subject="%s" --body_file=%s' % + (sendgmr_path, email_to, subject, report_file)) + command_executer.GetCommandExecuter().RunCommand(command) + + +def GetColor(status): + """Given a job status string, returns appropriate color string.""" + if status.strip() == 'pass': + color = 'green ' + elif status.strip() == 'fail': + color = ' red ' + elif status.strip() == 'warning': + color = 'orange' + else: + color = ' ' + return color + + +def GenerateWaterfallReport(report_dict, waterfall_type, date): + """Write out the actual formatted report.""" + + filename = 'waterfall_report.%s_waterfall.%s.txt' % (waterfall_type, date) + + date_string = '' + report_list = report_dict.keys() + + with open(filename, 'w') as out_file: + # Write Report Header + out_file.write('\nStatus of %s Waterfall Builds from %s\n\n' % + (waterfall_type, date_string)) + out_file.write(' \n') + out_file.write( + ' Build bvt- ' + ' bvt-cq ' + ' security \n') + out_file.write( + ' status inline ' + ' \n') + + # Write daily waterfall status section. + for builder in report_list: + build_dict = report_dict[builder] + buildbucket_id = build_dict['buildbucket_id'] + overall_status = build_dict['status'] + if 'bvt-inline' in build_dict.keys(): + inline_status = build_dict['bvt-inline'] + else: + inline_status = ' ' + if 'bvt-cq' in build_dict.keys(): + cq_status = build_dict['bvt-cq'] + else: + cq_status = ' ' + if 'security' in build_dict.keys(): + security_status = build_dict['security'] + else: + security_status = ' ' + inline_color = GetColor(inline_status) + cq_color = GetColor(cq_status) + security_color = GetColor(security_status) + + out_file.write( + '%26s %4s %6s %6s %6s\n' % + (builder, overall_status, inline_color, cq_color, security_color)) + if waterfall_type == 'main': + out_file.write(' build url: https://cros-goldeneye.corp.google.com/' + 'chromeos/healthmonitoring/buildDetails?buildbucketId=%s' + '\n' % buildbucket_id) + else: + out_file.write(' build url: https://ci.chromium.org/p/chromeos/' + 'builds/b%s \n' % buildbucket_id) + report_url = ('https://logs.chromium.org/v/?s=chromeos%2Fbuildbucket%2F' + 'cr-buildbucket.appspot.com%2F' + buildbucket_id + + '%2F%2B%2Fsteps%2FReport%2F0%2Fstdout') + out_file.write('\n report status url: %s\n' % report_url) + out_file.write('\n') + + print('Report generated in %s.' % filename) + return filename + + +def GetTryjobData(date, rotating_builds_dict): + """Read buildbucket id and board from stored file. + + buildbot_test_llvm.py, when it launches the rotating builders, + records the buildbucket_id and board for each launch in a file. + This reads that data out of the file so we can find the right + tryjob data. + """ + + date_str = format_date(date, use_int_month=True) + fname = '%s.builds' % date_str + filename = os.path.join(DATA_DIR, 'rotating-builders', fname) + + if not os.path.exists(filename): + print('Cannot find file: %s' % filename) + print('Unable to generate rotating builder report for date %d.' % date) + return + + with open(filename, 'r') as in_file: + lines = in_file.readlines() + + for line in lines: + l = line.strip() + parts = l.split(',') + if len(parts) != 2: + print('Warning: Illegal line in data file.') + print('File: %s' % filename) + print('Line: %s' % l) + continue + buildbucket_id = parts[0] + board = parts[1] + rotating_builds_dict[board] = buildbucket_id + + return + + +def GetRotatingBuildData(date, report_dict, chromeos_root, board, + buildbucket_id, ce): + """Gets rotating builder job results via 'cros buildresult'.""" + path = os.path.join(chromeos_root, 'chromite') + save_dir = os.getcwd() + date_str = format_date(date, use_int_month=True) + os.chdir(path) + + command = ( + 'cros buildresult --buildbucket-id %s --report json' % buildbucket_id) + _, out, _ = ce.RunCommandWOutput(command) + tmp_dict = json.loads(out) + results = tmp_dict[buildbucket_id] + + board_dict = dict() + board_dict['buildbucket_id'] = buildbucket_id + stages_results = results['stages'] + for test in TESTS: + key1 = test[0] + key2 = test[1] + if key2 in stages_results: + board_dict[key1] = stages_results[key2] + board_dict['status'] = results['status'] + report_dict[board] = board_dict + os.chdir(save_dir) + return + + +def GetMainWaterfallData(date, report_dict, chromeos_root, ce): + """Gets main waterfall job results via 'cros buildresult'.""" + path = os.path.join(chromeos_root, 'chromite') + save_dir = os.getcwd() + date_str = format_date(date, use_int_month=True) + os.chdir(path) + for builder in WATERFALL_BUILDERS: + command = ('cros buildresult --build-config %s --date %s --report json' % + (builder, date_str)) + _, out, _ = ce.RunCommandWOutput(command) + tmp_dict = json.loads(out) + builder_dict = dict() + for k in tmp_dict.keys(): + buildbucket_id = k + results = tmp_dict[k] + + builder_dict['buildbucket_id'] = buildbucket_id + builder_dict['status'] = results['status'] + stages_results = results['stages'] + for test in TESTS: + key1 = test[0] + key2 = test[1] + builder_dict[key1] = stages_results[key2] + report_dict[builder] = builder_dict + os.chdir(save_dir) + return + + +# Check for prodaccess. +def CheckProdAccess(): + """Verifies prodaccess is current.""" + status, output, _ = command_executer.GetCommandExecuter().RunCommandWOutput( + 'prodcertstatus') + if status != 0: + return False + # Verify that status is not expired + if 'expires' in output: + return True + return False + + +def ValidDate(date): + """Ensures 'date' is a valid date.""" + min_year = 2018 + + tmp_date = date + day = tmp_date % 100 + tmp_date = tmp_date / 100 + month = tmp_date % 100 + year = tmp_date / 100 + + if day < 1 or month < 1 or year < min_year: + return False + + cur_year = datetime.datetime.now().year + if year > cur_year: + return False + + if month > 12: + return False + + if month == 2 and cur_year % 4 == 0 and cur_year % 100 != 0: + max_day = 29 + else: + max_day = DAYS_PER_MONTH[month] + + if day > max_day: + return False + + return True + + +def ValidOptions(parser, options): + """Error-check the options passed to this script.""" + too_many_options = False + if options.main: + if options.rotating: + too_many_options = True + + if too_many_options: + parser.error('Can only specify one of --main, --rotating.') + + if not os.path.exists(options.chromeos_root): + parser.error( + 'Invalid chromeos root. Cannot find: %s' % options.chromeos_root) + + email_ok = True + if options.email and options.email.find('@') == -1: + email_ok = False + parser.error('"%s" is not a valid email address; it must contain "@..."' % + options.email) + + valid_date = ValidDate(options.date) + + return not too_many_options and valid_date and email_ok + + +def Main(argv): + """Main function for this script.""" + parser = argparse.ArgumentParser() + parser.add_argument( + '--main', + dest='main', + default=False, + action='store_true', + help='Generate report only for main waterfall ' + 'builders.') + parser.add_argument( + '--rotating', + dest='rotating', + default=False, + action='store_true', + help='Generate report only for rotating builders.') + parser.add_argument( + '--date', + dest='date', + required=True, + type=int, + help='The date YYYYMMDD of waterfall report.') + parser.add_argument( + '--email', + dest='email', + default='', + help='Email address to use for sending the report.') + parser.add_argument( + '--chromeos_root', + dest='chromeos_root', + required=True, + help='Chrome OS root in which to run chroot commands.') + + options = parser.parse_args(argv) + + if not ValidOptions(parser, options): + return 1 + + main_only = options.main + rotating_only = options.rotating + date = options.date + + prod_access = CheckProdAccess() + if not prod_access: + print('ERROR: Please run prodaccess first.') + return + + waterfall_report_dict = dict() + rotating_report_dict = dict() + + ce = command_executer.GetCommandExecuter() + if not rotating_only: + GetMainWaterfallData(date, waterfall_report_dict, options.chromeos_root, ce) + + if not main_only: + rotating_builds_dict = dict() + GetTryjobData(date, rotating_builds_dict) + if len(rotating_builds_dict.keys()) > 0: + for board in rotating_builds_dict.keys(): + buildbucket_id = rotating_builds_dict[board] + GetRotatingBuildData(date, rotating_report_dict, options.chromeos_root, + board, buildbucket_id, ce) + + if options.email: + email_to = options.email + else: + email_to = getpass.getuser() + + if waterfall_report_dict and not rotating_only: + main_report = GenerateWaterfallReport(waterfall_report_dict, 'main', date) + + EmailReport(main_report, 'Main', format_date(date), email_to) + shutil.copy(main_report, ARCHIVE_DIR) + if rotating_report_dict and not main_only: + rotating_report = GenerateWaterfallReport(rotating_report_dict, 'rotating', + date) + + EmailReport(rotating_report, 'Rotating', format_date(date), email_to) + shutil.copy(rotating_report, ARCHIVE_DIR) + + +if __name__ == '__main__': + Main(sys.argv[1:]) + sys.exit(0) |