| #!/usr/bin/env python3 | |
| # Copyright (C) 2024 The Android Open Source Project | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """ | |
| Runs a bisection on the autopush ui.perfetto.dev builds | |
| Similar to git bisect, but bisects UI releases rather than commits. | |
| This works only for autopush builds from main, ignores canary and stable | |
| channels, as they make the history non-linear. | |
| How it works: | |
| - It first obtains an unordered list of versions from gs://ui.perfetto.dev | |
| - Then obtains the list of ordered commits from git | |
| - Intersects the two lists, keeping only git commits that have a corresponding | |
| ui autopush release. | |
| - Proceeds with a guided bisect in the range. | |
| """ | |
| import argparse | |
| import sys | |
| from subprocess import check_output | |
| COMMIT_ABBR_LEN = 9 # UI truncates commitish to 9 chars, e.g. v45.0-38b7c2b12. | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| '--good', | |
| default=None, | |
| help='Last good release (e.g. v44.0-257a02990).' + | |
| 'Defaults to the first verion available') | |
| parser.add_argument( | |
| '--bad', | |
| default=None, | |
| help='First bad release. Defaults to the latest version available') | |
| args = parser.parse_args() | |
| print('Fetching list of UI releases from GCS...') | |
| rev_list = check_output(['gsutil.py', 'ls', 'gs://ui.perfetto.dev/']).decode() | |
| ui_map = {} # maps '38b7c2b12' -> 'v45.0-38b7c2b12' | |
| for line in rev_list.split(): | |
| version = line.split('/')[3] | |
| if '-' not in version: | |
| continue | |
| ver_hash = version.split('-')[1] | |
| ui_map[ver_hash] = version | |
| print('Found %d UI versions' % len(ui_map)) | |
| # Get the linear history of all commits. | |
| print('Fetching revision history from git...') | |
| ui_versions = [] | |
| git_out = check_output(['git', 'rev-list', '--left-only', | |
| 'origin/main']).decode() | |
| for line in git_out.split(): | |
| line = line.strip() | |
| rev = line[0:COMMIT_ABBR_LEN] | |
| if rev not in ui_map: | |
| continue # Not all perfetto commits have a UI autopush build. | |
| ui_versions.append(ui_map[rev]) | |
| # git rev-list emits entries in recent -> older versions. Reverse it. | |
| ui_versions.reverse() | |
| # Note that not all the entries in ui_map will be present in ui_versions. | |
| # This is because ui_map contains also builds coming from canary and stable | |
| # branches, that we ignore here. | |
| start = ui_versions.index(args.good) if args.good else 0 | |
| end = ui_versions.index(args.bad) if args.bad else len(ui_versions) - 1 | |
| while end - start > 1: | |
| print('\033c', end='') # clear terminal. | |
| print( | |
| 'Bisecting from %s (last good) to %s (first bad), %d revisions to go' % | |
| (ui_versions[start], ui_versions[end], end - start + 1)) | |
| mid = (end + start) // 2 | |
| # Print a visual indication of where we are in the bisect. | |
| for i in reversed(range(start, end + 1)): | |
| sfx = '' | |
| if i == start: | |
| sfx = ' GOOD --------------' | |
| elif i == end: | |
| sfx = ' BAD ---------------' | |
| elif i == mid: | |
| sfx = ' <- version to test' | |
| print(ui_versions[i] + sfx) | |
| user_feedback = input( | |
| 'https://ui.perfetto.dev/%s/. Type g for good and b for bad: ' % | |
| ui_versions[mid]) | |
| if user_feedback == 'b': | |
| end = mid | |
| elif user_feedback == 'g': | |
| start = mid | |
| else: | |
| print('Unrecognised key "%d", try again' % user_feedback) | |
| print('First bad UI release %s' % ui_versions[end]) | |
| print('You should now inspect the individual commits via git log good..bad') | |
| if __name__ == '__main__': | |
| sys.exit(main()) |