1 #!/usr/bin/env python 2 # Copyright (c) 2014 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 7 """rebase.py: standalone script to batch update bench expectations. 8 9 Requires gsutil to access gs://chromium-skia-gm and Rietveld credentials. 10 11 Usage: 12 Copy script to a separate dir outside Skia repo. The script will create a 13 skia dir on the first run to host the repo, and will create/delete 14 temp dirs as needed. 15 ./rebase.py --githash <githash prefix to use for getting bench data> 16 """ 17 18 19 import argparse 20 import filecmp 21 import os 22 import re 23 import shutil 24 import subprocess 25 import time 26 import urllib2 27 28 29 # googlesource url that has most recent Skia git hash info. 30 SKIA_GIT_HEAD_URL = 'https://skia.googlesource.com/skia/+log/HEAD' 31 32 # Google Storage bench file prefix. 33 GS_PREFIX = 'gs://chromium-skia-gm/perfdata' 34 35 # List of Perf platforms we want to process. Populate from expectations/bench. 36 PLATFORMS = [] 37 38 # Regular expression for matching githash data. 39 HA_RE = '<a href="/skia/\+/([0-9a-f]+)">' 40 HA_RE_COMPILED = re.compile(HA_RE) 41 42 43 def get_git_hashes(): 44 print 'Getting recent git hashes...' 45 hashes = HA_RE_COMPILED.findall( 46 urllib2.urlopen(SKIA_GIT_HEAD_URL).read()) 47 48 return hashes 49 50 def filter_file(f): 51 if f.find('_msaa') > 0 or f.find('_record') > 0: 52 return True 53 54 return False 55 56 def clean_dir(d): 57 if os.path.exists(d): 58 shutil.rmtree(d) 59 os.makedirs(d) 60 61 def get_gs_filelist(p, h): 62 print 'Looking up for the closest bench files in Google Storage...' 63 proc = subprocess.Popen(['gsutil', 'ls', 64 '/'.join([GS_PREFIX, p, 'bench_' + h + '_data_skp_*'])], 65 stdout=subprocess.PIPE) 66 out, err = proc.communicate() 67 if err or not out: 68 return [] 69 return [i for i in out.strip().split('\n') if not filter_file(i)] 70 71 def download_gs_files(p, h, gs_dir): 72 print 'Downloading raw bench files from Google Storage...' 73 proc = subprocess.Popen(['gsutil', 'cp', 74 '/'.join([GS_PREFIX, p, 'bench_' + h + '_data_skp_*']), 75 '%s/%s' % (gs_dir, p)], 76 stdout=subprocess.PIPE) 77 out, err = proc.communicate() 78 if err: 79 clean_dir(gs_dir) 80 return False 81 files = 0 82 for f in os.listdir(os.path.join(gs_dir, p)): 83 if filter_file(f): 84 os.remove(os.path.join(gs_dir, p, f)) 85 else: 86 files += 1 87 if files: 88 return True 89 return False 90 91 def calc_expectations(p, h, gs_dir, exp_dir, repo_dir): 92 exp_filename = 'bench_expectations_%s.txt' % p 93 proc = subprocess.Popen(['python', 'skia/bench/gen_bench_expectations.py', 94 '-r', h, '-b', p, '-d', os.path.join(gs_dir, p), '-o', 95 os.path.join(exp_dir, exp_filename)], 96 stdout=subprocess.PIPE) 97 out, err = proc.communicate() 98 if err: 99 print 'ERR_CALCULATING_EXPECTATIONS: ' + err 100 return False 101 print 'CALCULATED_EXPECTATIONS: ' + out 102 repo_file = os.path.join(repo_dir, 'expectations', 'bench', exp_filename) 103 if (os.path.isfile(repo_file) and 104 filecmp.cmp(repo_file, os.path.join(exp_dir, exp_filename))): 105 print 'NO CHANGE ON %s' % repo_file 106 return False 107 return True 108 109 def checkout_or_update_skia(repo_dir): 110 status = True 111 old_cwd = os.getcwd() 112 os.chdir(repo_dir) 113 print 'CHECK SKIA REPO...' 114 if subprocess.call(['git', 'pull'], 115 stderr=subprocess.PIPE): 116 print 'Checking out Skia from git, please be patient...' 117 os.chdir(old_cwd) 118 clean_dir(repo_dir) 119 os.chdir(repo_dir) 120 if subprocess.call(['git', 'clone', '-q', '--depth=50', '--single-branch', 121 'https://skia.googlesource.com/skia.git', '.']): 122 status = False 123 subprocess.call(['git', 'checkout', 'master']) 124 subprocess.call(['git', 'pull']) 125 os.chdir(old_cwd) 126 return status 127 128 def git_commit_expectations(repo_dir, exp_dir, update_li, h, commit): 129 commit_msg = """manual bench rebase after %s 130 131 TBR=robertphillips (at] google.com 132 133 Bypassing trybots: 134 NOTRY=true""" % h 135 old_cwd = os.getcwd() 136 os.chdir(repo_dir) 137 upload = ['git', 'cl', 'upload', '-f', '--bypass-hooks', 138 '--bypass-watchlists', '-m', commit_msg] 139 branch = exp_dir.split('/')[-1] 140 if commit: 141 upload.append('--use-commit-queue') 142 cmds = ([['git', 'checkout', 'master'], 143 ['git', 'pull'], 144 ['git', 'checkout', '-b', branch, '-t', 'origin/master']] + 145 [['cp', '%s/%s' % (exp_dir, f), 'expectations/bench'] for f in 146 update_li] + 147 [['git', 'add'] + ['expectations/bench/%s' % i for i in update_li], 148 ['git', 'commit', '-m', commit_msg], 149 upload, 150 ['git', 'checkout', 'master'], 151 ['git', 'branch', '-D', branch], 152 ]) 153 status = True 154 for cmd in cmds: 155 print 'Running ' + ' '.join(cmd) 156 if subprocess.call(cmd): 157 print 'FAILED. Please check if skia git repo is present.' 158 subprocess.call(['git', 'checkout', 'master']) 159 status = False 160 break 161 os.chdir(old_cwd) 162 return status 163 164 def delete_dirs(li): 165 for d in li: 166 print 'Deleting directory %s' % d 167 shutil.rmtree(d) 168 169 170 def main(): 171 d = os.path.dirname(os.path.abspath(__file__)) 172 os.chdir(d) 173 if not subprocess.call(['git', 'rev-parse'], stderr=subprocess.PIPE): 174 print 'Please copy script to a separate dir outside git repos to use.' 175 return 176 parser = argparse.ArgumentParser() 177 parser.add_argument('--githash', 178 help='Githash prefix (7+ chars) to rebaseline to.') 179 parser.add_argument('--commit', action='store_true', 180 help='Whether to commit changes automatically.') 181 args = parser.parse_args() 182 183 repo_dir = os.path.join(d, 'skia') 184 if not os.path.exists(repo_dir): 185 os.makedirs(repo_dir) 186 if not checkout_or_update_skia(repo_dir): 187 print 'ERROR setting up Skia repo at %s' % repo_dir 188 return 1 189 190 file_in_repo = os.path.join(d, 'skia/experimental/benchtools/rebase.py') 191 if not filecmp.cmp(__file__, file_in_repo): 192 shutil.copy(file_in_repo, __file__) 193 print 'Updated this script from repo; please run again.' 194 return 195 196 for item in os.listdir(os.path.join(d, 'skia/expectations/bench')): 197 PLATFORMS.append( 198 item.replace('bench_expectations_', '').replace('.txt', '')) 199 200 if not args.githash or len(args.githash) < 7: 201 raise Exception('Please provide --githash with a longer prefix (7+).') 202 commit = False 203 if args.commit: 204 commit = True 205 rebase_hash = args.githash[:7] 206 hashes = get_git_hashes() 207 short_hashes = [h[:7] for h in hashes] 208 if rebase_hash not in short_hashes: 209 raise Exception('Provided --githash not found in recent history!') 210 hashes = hashes[:short_hashes.index(rebase_hash) + 1] 211 update_li = [] 212 213 ts_str = '%s' % time.time() 214 gs_dir = os.path.join(d, 'gs' + ts_str) 215 exp_dir = os.path.join(d, 'exp' + ts_str) 216 clean_dir(gs_dir) 217 clean_dir(exp_dir) 218 for p in PLATFORMS: 219 clean_dir(os.path.join(gs_dir, p)) 220 hash_to_use = '' 221 for h in reversed(hashes): 222 li = get_gs_filelist(p, h) 223 if not len(li): # no data 224 continue 225 if download_gs_files(p, h, gs_dir): 226 print 'Copied %s/%s' % (p, h) 227 hash_to_use = h 228 break 229 else: 230 print 'DOWNLOAD BENCH FAILED %s/%s' % (p, h) 231 break 232 if hash_to_use: 233 if calc_expectations(p, h, gs_dir, exp_dir, repo_dir): 234 update_li.append('bench_expectations_%s.txt' % p) 235 if not update_li: 236 print 'No bench data to update after %s!' % args.githash 237 elif not git_commit_expectations( 238 repo_dir, exp_dir, update_li, args.githash[:7], commit): 239 print 'ERROR uploading expectations using git.' 240 elif not commit: 241 print 'CL created. Please take a look at the link above.' 242 else: 243 print 'New bench baselines should be in CQ now.' 244 delete_dirs([gs_dir, exp_dir]) 245 246 247 if __name__ == "__main__": 248 main() 249