1#!/usr/bin/env python3 2# Copyright (C) 2024 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15""" 16Runs a bisection on the autopush ui.perfetto.dev builds 17 18Similar to git bisect, but bisects UI releases rather than commits. 19This works only for autopush builds from main, ignores canary and stable 20channels, as they make the history non-linear. 21 22How it works: 23- It first obtains an unordered list of versions from gs://ui.perfetto.dev 24- Then obtains the list of ordered commits from git 25- Intersects the two lists, keeping only git commits that have a corresponding 26 ui autopush release. 27- Proceeds with a guided bisect in the range. 28""" 29 30import argparse 31import sys 32 33from subprocess import check_output 34 35COMMIT_ABBR_LEN = 9 # UI truncates commitish to 9 chars, e.g. v45.0-38b7c2b12. 36 37 38def main(): 39 parser = argparse.ArgumentParser() 40 parser.add_argument( 41 '--good', 42 default=None, 43 help='Last good release (e.g. v44.0-257a02990).' + 44 'Defaults to the first verion available') 45 parser.add_argument( 46 '--bad', 47 default=None, 48 help='First bad release. Defaults to the latest version available') 49 args = parser.parse_args() 50 51 print('Fetching list of UI releases from GCS...') 52 rev_list = check_output(['gsutil.py', 'ls', 'gs://ui.perfetto.dev/']).decode() 53 ui_map = {} # maps '38b7c2b12' -> 'v45.0-38b7c2b12' 54 for line in rev_list.split(): 55 version = line.split('/')[3] 56 if '-' not in version: 57 continue 58 ver_hash = version.split('-')[1] 59 ui_map[ver_hash] = version 60 print('Found %d UI versions' % len(ui_map)) 61 62 # Get the linear history of all commits. 63 print('Fetching revision history from git...') 64 ui_versions = [] 65 git_out = check_output(['git', 'rev-list', '--left-only', 66 'origin/main']).decode() 67 for line in git_out.split(): 68 line = line.strip() 69 rev = line[0:COMMIT_ABBR_LEN] 70 if rev not in ui_map: 71 continue # Not all perfetto commits have a UI autopush build. 72 ui_versions.append(ui_map[rev]) 73 74 # git rev-list emits entries in recent -> older versions. Reverse it. 75 ui_versions.reverse() 76 77 # Note that not all the entries in ui_map will be present in ui_versions. 78 # This is because ui_map contains also builds coming from canary and stable 79 # branches, that we ignore here. 80 81 start = ui_versions.index(args.good) if args.good else 0 82 end = ui_versions.index(args.bad) if args.bad else len(ui_versions) - 1 83 while end - start > 1: 84 print('\033c', end='') # clear terminal. 85 print( 86 'Bisecting from %s (last good) to %s (first bad), %d revisions to go' % 87 (ui_versions[start], ui_versions[end], end - start + 1)) 88 mid = (end + start) // 2 89 90 # Print a visual indication of where we are in the bisect. 91 for i in reversed(range(start, end + 1)): 92 sfx = '' 93 if i == start: 94 sfx = ' GOOD --------------' 95 elif i == end: 96 sfx = ' BAD ---------------' 97 elif i == mid: 98 sfx = ' <- version to test' 99 print(ui_versions[i] + sfx) 100 101 user_feedback = input( 102 'https://ui.perfetto.dev/%s/. Type g for good and b for bad: ' % 103 ui_versions[mid]) 104 if user_feedback == 'b': 105 end = mid 106 elif user_feedback == 'g': 107 start = mid 108 else: 109 print('Unrecognised key "%d", try again' % user_feedback) 110 111 print('First bad UI release %s' % ui_versions[end]) 112 print('You should now inspect the individual commits via git log good..bad') 113 114 115if __name__ == '__main__': 116 sys.exit(main()) 117