diff options
Diffstat (limited to 'llvm_tools/auto_llvm_bisection.py')
-rwxr-xr-x | llvm_tools/auto_llvm_bisection.py | 100 |
1 files changed, 76 insertions, 24 deletions
diff --git a/llvm_tools/auto_llvm_bisection.py b/llvm_tools/auto_llvm_bisection.py index dd29cf4f..7e8fb1dd 100755 --- a/llvm_tools/auto_llvm_bisection.py +++ b/llvm_tools/auto_llvm_bisection.py @@ -8,6 +8,8 @@ from __future__ import print_function +import enum +import json import os import subprocess import sys @@ -17,7 +19,7 @@ import traceback import chroot from llvm_bisection import BisectionExitStatus import llvm_bisection -from update_all_tryjobs_with_auto import GetPathToUpdateAllTryjobsWithAutoScript +import update_tryjob_status # Used to re-try for 'llvm_bisection.py' to attempt to launch more tryjobs. BISECTION_RETRY_TIME_SECS = 10 * 60 @@ -39,6 +41,52 @@ BISECTION_ATTEMPTS = 3 POLLING_LIMIT_SECS = 18 * 60 * 60 +class BuilderStatus(enum.Enum): + """Actual values given via 'cros buildresult'.""" + + PASS = 'pass' + FAIL = 'fail' + RUNNING = 'running' + + +builder_status_mapping = { + BuilderStatus.PASS.value: update_tryjob_status.TryjobStatus.GOOD.value, + BuilderStatus.FAIL.value: update_tryjob_status.TryjobStatus.BAD.value, + BuilderStatus.RUNNING.value: update_tryjob_status.TryjobStatus.PENDING.value +} + + +def GetBuildResult(chroot_path, buildbucket_id): + """Returns the conversion of the result of 'cros buildresult'.""" + + # Calls 'cros buildresult' to get the status of the tryjob. + try: + tryjob_json = subprocess.check_output( + [ + 'cros_sdk', '--', 'cros', 'buildresult', '--buildbucket-id', + str(buildbucket_id), '--report', 'json' + ], + cwd=chroot_path, + stderr=subprocess.STDOUT, + encoding='UTF-8', + ) + except subprocess.CalledProcessError as err: + if 'No build found. Perhaps not started' not in err.output: + raise + return None + + tryjob_content = json.loads(tryjob_json) + + build_result = str(tryjob_content['%d' % buildbucket_id]['status']) + + # The string returned by 'cros buildresult' might not be in the mapping. + if build_result not in builder_status_mapping: + raise ValueError('"cros buildresult" return value is invalid: %s' % + build_result) + + return builder_status_mapping[build_result] + + def main(): """Bisects LLVM using the result of `cros buildresult` of each tryjob. @@ -50,51 +98,58 @@ def main(): args_output = llvm_bisection.GetCommandLineArgs() - exec_update_tryjobs = [ - GetPathToUpdateAllTryjobsWithAutoScript(), '--chroot_path', - args_output.chroot_path, '--last_tested', args_output.last_tested - ] - if os.path.isfile(args_output.last_tested): print('Resuming bisection for %s' % args_output.last_tested) else: print('Starting a new bisection for %s' % args_output.last_tested) while True: + # Update the status of existing tryjobs if os.path.isfile(args_output.last_tested): update_start_time = time.time() - - # Update all tryjobs whose status is 'pending' to the result of `cros - # buildresult`. + with open(args_output.last_tested) as json_file: + json_dict = json.load(json_file) while True: print('\nAttempting to update all tryjobs whose "status" is ' '"pending":') print('-' * 40) - update_ret = subprocess.call(exec_update_tryjobs) + completed = True + for tryjob in json_dict['jobs']: + if tryjob[ + 'status'] == update_tryjob_status.TryjobStatus.PENDING.value: + status = GetBuildResult(args_output.chroot_path, + tryjob['buildbucket_id']) + if status: + tryjob['status'] = status + else: + completed = False print('-' * 40) - # Successfully updated all tryjobs whose 'status' was 'pending'/ no - # updates were needed (all tryjobs already have been updated). - if update_ret == 0: + # Proceed to the next step if all the existing tryjobs have completed. + if completed: break delta_time = time.time() - update_start_time if delta_time > POLLING_LIMIT_SECS: - print('Unable to update tryjobs whose status is "pending" to ' - 'the result of `cros buildresult`.') - # Something is wrong with updating the tryjobs's 'status' via # `cros buildresult` (e.g. network issue, etc.). - sys.exit(1) + sys.exit('Failed to update pending tryjobs.') + print('-' * 40) print('Sleeping for %d minutes.' % (POLL_RETRY_TIME_SECS // 60)) time.sleep(POLL_RETRY_TIME_SECS) - # Launch more tryjobs if possible to narrow down the bad commit/revision or - # terminate the bisection because the bad commit/revision was found. + # There should always be update from the tryjobs launched in the + # last iteration. + temp_filename = '%s.new' % args_output.last_tested + with open(temp_filename, 'w') as temp_file: + json.dump(json_dict, temp_file, indent=4, separators=(',', ': ')) + os.rename(temp_filename, args_output.last_tested) + + # Launch more tryjobs. for cur_try in range(1, BISECTION_ATTEMPTS + 1): try: print('\nAttempting to launch more tryjobs if possible:') @@ -104,8 +159,7 @@ def main(): print('-' * 40) - # Exit code 126 means that there are no more revisions to test between - # 'start' and 'end', so bisection is complete. + # Stop if the bisection has completed. if bisection_ret == BisectionExitStatus.BISECTION_COMPLETE.value: sys.exit(0) @@ -118,9 +172,7 @@ def main(): # Exceeded the number of times to launch more tryjobs. if cur_try == BISECTION_ATTEMPTS: - print('Unable to continue bisection.') - - sys.exit(1) + sys.exit('Unable to continue bisection.') num_retries_left = BISECTION_ATTEMPTS - cur_try |