diff options
author | Salud Lemus <saludlemus@google.com> | 2019-08-29 11:46:49 -0700 |
---|---|---|
committer | Salud Lemus <saludlemus@google.com> | 2019-09-03 21:54:36 +0000 |
commit | c856b68b3ca72481a147e73108ea95b030da2606 (patch) | |
tree | e72022ffb5869ba01beaccaa616bea5c5509eca7 /llvm_tools/llvm_bisection.py | |
parent | 3effa0615c6958953aa6de826ca2f11aa8a70418 (diff) | |
download | toolchain-utils-c856b68b3ca72481a147e73108ea95b030da2606.tar.gz |
LLVM tools: Added auto bisection of LLVM
Similar to 'llvm_bisection.py' script but uses `cros buildresult` to
update each tryjob's 'status' field. The script sleeps for X minutes and
continues where bisection left off (similar behavior if script
terminates). The script is an infinite loop but terminates when there
are no more revisions between the new 'start' and new 'end' or if an
exception happens.
This script is using the scripts such as bisection script and updating a
tryjob's status script in a loop.
BUG=None
TEST=Ran 'auto_llvm_bisection.py' script with a file that was not
created and start revision of 369410 and end revision of 369420.
Successfully created tryjobs between the start and end and the script
went to sleep for X minutes and woke up to resume where bisection
left off.
Change-Id: I711988b164c41f56ecc2c9478527bdcfe8f5bb88
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/1776330
Reviewed-by: George Burgess <gbiv@chromium.org>
Reviewed-by: Manoj Gupta <manojgupta@chromium.org>
Tested-by: Salud Lemus <saludlemus@google.com>
Diffstat (limited to 'llvm_tools/llvm_bisection.py')
-rwxr-xr-x | llvm_tools/llvm_bisection.py | 140 |
1 files changed, 107 insertions, 33 deletions
diff --git a/llvm_tools/llvm_bisection.py b/llvm_tools/llvm_bisection.py index da26a540..d9eecce6 100755 --- a/llvm_tools/llvm_bisection.py +++ b/llvm_tools/llvm_bisection.py @@ -10,9 +10,11 @@ from __future__ import division from __future__ import print_function import argparse +import enum import errno import json import os +import sys from assert_not_in_chroot import VerifyOutsideChroot from get_llvm_hash import CreateTempLLVMRepo @@ -23,6 +25,13 @@ from update_tryjob_status import FindTryjobIndex from update_tryjob_status import TryjobStatus +class BisectionExitStatus(enum.Enum): + """Exit code when performing bisection.""" + + # Means that there are no more revisions available to bisect. + BISECTION_COMPLETE = 126 + + def is_file_and_json(json_file): """Validates that the file exists and is a JSON file.""" return os.path.isfile(json_file) and json_file.endswith('.json') @@ -302,7 +311,69 @@ def GetRevisionsListAndHashList(start, end, parallel, src_path, return revisions, git_hashes -def main(): +def DieWithNoRevisionsError(start, end, skip_revisions, pending_revisions): + """Raises a ValueError exception with useful information.""" + + no_revisions_message = ('No revisions between start %d and end ' + '%d to create tryjobs' % (start, end)) + + if pending_revisions: + no_revisions_message += '\nThe following tryjobs are pending:\n' \ + + '\n'.join(str(rev) for rev in pending_revisions) + + if skip_revisions: + no_revisions_message += '\nThe following tryjobs were skipped:\n' \ + + '\n'.join(str(rev) for rev in skip_revisions) + + raise ValueError(no_revisions_message) + + +def CheckForExistingTryjobsInRevisionsToLaunch(revisions, jobs): + """Checks if a revision in 'revisions' exists in 'jobs' list.""" + + for rev in revisions: + if FindTryjobIndex(rev, jobs) is not None: + raise ValueError('Revision %d exists already in \'jobs\'' % rev) + + +def UpdateBisection(revisions, git_hashes, bisect_contents, last_tested, + update_packages, chroot_path, patch_metadata_file, + extra_change_lists, options, builder, log_level): + """Adds tryjobs and updates the status file with the new tryjobs.""" + + try: + for svn_revision, git_hash in zip(revisions, git_hashes): + tryjob_dict = AddTryjob(update_packages, git_hash, svn_revision, + chroot_path, patch_metadata_file, + extra_change_lists, options, builder, log_level, + svn_revision) + + bisect_contents['jobs'].append(tryjob_dict) + finally: + # Do not want to lose progress if there is an exception. + if last_tested: + new_file = '%s.new' % last_tested + with open(new_file, 'w') as json_file: + json.dump(bisect_contents, json_file, indent=4, separators=(',', ': ')) + + os.rename(new_file, last_tested) + + +def _NoteCompletedBisection(last_tested, src_path, end): + """Prints that bisection is complete.""" + + print('Finished bisecting for %s' % last_tested) + + if src_path: + bad_llvm_hash = LLVMHash().GetGitHashForVersion(src_path, end) + else: + bad_llvm_hash = LLVMHash().GetLLVMHash(end) + + print( + 'The bad revision is %d and its commit hash is %s' % (end, bad_llvm_hash)) + + +def main(args_output): """Bisects LLVM based off of a .JSON file. Raises: @@ -311,8 +382,6 @@ def main(): VerifyOutsideChroot() - args_output = GetCommandLineArgs() - update_packages = [ 'sys-devel/llvm', 'sys-libs/compiler-rt', 'sys-libs/libcxx', 'sys-libs/libcxxabi', 'sys-libs/llvm-libunwind' @@ -333,8 +402,7 @@ def main(): _ValidateStartAndEndAgainstJSONStartAndEnd( start, end, bisect_contents['start'], bisect_contents['end']) - print(start, end) - print(bisect_contents['jobs']) + # Pending and skipped revisions are between 'start_revision' and # 'end_revision'. start_revision, end_revision, pending_revisions, skip_revisions = \ @@ -344,41 +412,47 @@ def main(): start_revision, end_revision, args_output.parallel, args_output.src_path, pending_revisions, skip_revisions) + # No more revisions between 'start_revision' and 'end_revision', so + # bisection is complete. + # + # This is determined by finding all valid revisions between 'start_revision' + # and 'end_revision' and that are NOT in the 'pending' and 'skipped' set. if not revisions: - no_revisions_message = ( - 'No revisions between start %d and end ' - '%d to create tryjobs' % (start_revision, end_revision)) + # Successfully completed bisection where there are 2 cases: + # 1) 'start_revision' and 'end_revision' are back-to-back (example: + # 'start_revision' is 369410 and 'end_revision' is 369411). + # + # 2) 'start_revision' and 'end_revision' are NOT back-to-back, so there must + # be tryjobs in between which are labeled as 'skip' for their 'status' + # value. + # + # In either case, there are no 'pending' jobs. + if not pending_revisions: + _NoteCompletedBisection(args_output.last_tested, args_output.src_path, + end_revision) - if skip_revisions: - no_revisions_message += '\nThe following tryjobs were skipped:\n' \ - + '\n'.join(str(rev) for rev in skip_revisions) + if skip_revisions: + skip_revisions_message = ('\nThe following revisions were skipped:\n' + + '\n'.join(str(rev) for rev in skip_revisions)) - raise ValueError(no_revisions_message) + print(skip_revisions_message) - # Check if any revisions that are going to be added as a tryjob exist already - # in the 'jobs' list. - for rev in revisions: - if FindTryjobIndex(rev, bisect_contents['jobs']) is not None: - raise ValueError('Revision %d exists already in \'jobs\'' % rev) + return BisectionExitStatus.BISECTION_COMPLETE.value - try: - for svn_revision, git_hash in zip(revisions, git_hashes): - tryjob_dict = AddTryjob(update_packages, git_hash, svn_revision, - args_output.chroot_path, patch_metadata_file, - args_output.extra_change_lists, - args_output.options, args_output.builder, - args_output.log_level, svn_revision) + # Some tryjobs are not finished which may change the actual bad + # commit/revision when those tryjobs are finished. + DieWithNoRevisionsError(start_revision, end_revision, skip_revisions, + pending_revisions) - bisect_contents['jobs'].append(tryjob_dict) - finally: - # Do not want to lose progress if there is an exception. - if args_output.last_tested: - new_file = '%s.new' % args_output.last_tested - with open(new_file, 'w') as json_file: - json.dump(bisect_contents, json_file, indent=4, separators=(',', ': ')) + CheckForExistingTryjobsInRevisionsToLaunch(revisions, bisect_contents['jobs']) - os.rename(new_file, args_output.last_tested) + UpdateBisection(revisions, git_hashes, bisect_contents, + args_output.last_tested, update_packages, + args_output.chroot_path, patch_metadata_file, + args_output.extra_change_lists, args_output.options, + args_output.builder, args_output.log_level) if __name__ == '__main__': - main() + args_output = GetCommandLineArgs() + sys.exit(main(args_output)) |