| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 | |
| 3 | |
| 4 # Copyright (c) 2014 The Chromium Authors. All rights reserved. | |
| 5 # Use of this source code is governed by a BSD-style license that can be | |
| 6 # found in the LICENSE file. | |
| 7 | |
| 8 | |
| 9 """greenify.py: standalone script to correct flaky bench expectations. | |
| 10 | |
| 11 Requires Rietveld credentials on the running machine. | |
| 12 | |
| 13 Usage: | |
| 14 Copy script to a separate dir outside Skia repo. The script will create a | |
| 15 skia dir on the first run to host the repo, and will create/delete | |
| 16 temp dirs as needed. | |
| 17 ./greenify.py --url <the stdio url from failed CheckForRegressions step> | |
| 18 """ | |
| 19 | |
| 20 import argparse | |
| 21 import filecmp | |
| 22 import os | |
| 23 import re | |
| 24 import shutil | |
| 25 import subprocess | |
| 26 import time | |
| 27 import urllib2 | |
| 28 | |
| 29 | |
| 30 # Regular expression for matching exception data. | |
| 31 EXCEPTION_RE = ('Bench (\S+) out of range \[(\d+.\d+), (\d+.\d+)\] \((\d+.\d+) ' | |
| 32 'vs (\d+.\d+), ') | |
| 33 EXCEPTION_RE_COMPILED = re.compile(EXCEPTION_RE) | |
| 34 | |
| 35 | |
| 36 def clean_dir(d): | |
| 37 if os.path.exists(d): | |
| 38 shutil.rmtree(d) | |
| 39 os.makedirs(d) | |
| 40 | |
| 41 def checkout_or_update_skia(repo_dir): | |
| 42 status = True | |
| 43 old_cwd = os.getcwd() | |
| 44 os.chdir(repo_dir) | |
| 45 print 'CHECK SKIA REPO...' | |
| 46 if subprocess.call(['git', 'pull'], | |
| 47 stderr=subprocess.PIPE): | |
| 48 print 'Checking out Skia from git, please be patient...' | |
| 49 os.chdir(old_cwd) | |
| 50 clean_dir(repo_dir) | |
| 51 os.chdir(repo_dir) | |
| 52 if subprocess.call(['git', 'clone', '-q', '--depth=50', '--single-branch', | |
| 53 'https://skia.googlesource.com/skia.git', '.']): | |
| 54 status = False | |
| 55 subprocess.call(['git', 'checkout', 'master']) | |
| 56 subprocess.call(['git', 'pull']) | |
| 57 os.chdir(old_cwd) | |
| 58 return status | |
| 59 | |
| 60 def git_commit_expectations(repo_dir, exp_dir, bot, build, commit): | |
| 61 commit_msg = """Greenify bench bot %s at build %s | |
| 62 | |
| 63 TBR=bsalomon@google.com | |
| 64 | |
| 65 Bypassing trybots: | |
| 66 NOTRY=true""" % (bot, build) | |
| 67 old_cwd = os.getcwd() | |
| 68 os.chdir(repo_dir) | |
| 69 upload = ['git', 'cl', 'upload', '-f', '--bypass-hooks', | |
| 70 '--bypass-watchlists', '-m', commit_msg] | |
| 71 if commit: | |
| 72 upload.append('--use-commit-queue') | |
| 73 branch = exp_dir[exp_dir.rfind('/') + 1:] | |
| 74 filename = 'bench_expectations_%s.txt' % bot | |
| 75 cmds = ([['git', 'checkout', 'master'], | |
| 76 ['git', 'pull'], | |
| 77 ['git', 'checkout', '-b', branch, '-t', 'origin/master'], | |
| 78 ['cp', '%s/%s' % (exp_dir, filename), 'expectations/bench'], | |
| 79 ['git', 'add', 'expectations/bench/' + filename], | |
| 80 ['git', 'commit', '-m', commit_msg], | |
| 81 upload, | |
| 82 ['git', 'checkout', 'master'], | |
| 83 ['git', 'branch', '-D', branch], | |
| 84 ]) | |
| 85 status = True | |
| 86 for cmd in cmds: | |
| 87 print 'Running ' + ' '.join(cmd) | |
| 88 if subprocess.call(cmd): | |
| 89 print 'FAILED. Please check if skia git repo is present.' | |
| 90 subprocess.call(['git', 'checkout', 'master']) | |
| 91 status = False | |
| 92 break | |
| 93 os.chdir(old_cwd) | |
| 94 return status | |
| 95 | |
| 96 def delete_dirs(li): | |
| 97 for d in li: | |
| 98 print 'Deleting directory %s' % d | |
| 99 shutil.rmtree(d) | |
| 100 | |
| 101 def widen_bench_ranges(url, bot, repo_dir, exp_dir): | |
| 102 fname = 'bench_expectations_%s.txt' % bot | |
| 103 src = os.path.join(repo_dir, 'expectations', 'bench', fname) | |
| 104 if not os.path.isfile(src): | |
| 105 print 'This bot has no expectations! %s' % bot | |
| 106 return False | |
| 107 row_dic = {} | |
| 108 for l in urllib2.urlopen(url).read().split('\n'): | |
| 109 data = EXCEPTION_RE_COMPILED.search(l) | |
| 110 if data: | |
| 111 row = data.group(1) | |
| 112 lb = float(data.group(2)) | |
| 113 ub = float(data.group(3)) | |
| 114 actual = float(data.group(4)) | |
| 115 exp = float(data.group(5)) | |
| 116 avg = (actual + exp) / 2 | |
| 117 shift = avg - exp | |
| 118 lb = lb + shift | |
| 119 ub = ub + shift | |
| 120 # In case outlier really fluctuates a lot | |
| 121 if actual < lb: | |
| 122 lb = actual - abs(shift) * 0.1 + 0.5 | |
| 123 elif actual > ub: | |
| 124 ub = actual + abs(shift) * 0.1 + 0.5 | |
| 125 row_dic[row] = '%.2f,%.2f,%.2f' % (avg, lb, ub) | |
| 126 if not row_dic: | |
| 127 print 'NO out-of-range benches found at %s' % url | |
| 128 return False | |
| 129 | |
| 130 changed = 0 | |
| 131 li = [] | |
| 132 for l in open(src).readlines(): | |
| 133 parts = l.strip().split(',') | |
| 134 if parts[0].startswith('#') or len(parts) != 5: | |
| 135 li.append(l.strip()) | |
| 136 continue | |
| 137 if ','.join(parts[:2]) in row_dic: | |
| 138 li.append(','.join(parts[:2]) + ',' + row_dic[','.join(parts[:2])]) | |
| 139 changed += 1 | |
| 140 else: | |
| 141 li.append(l.strip()) | |
| 142 if not changed: | |
| 143 print 'Not in source file:\n' + '\n'.join(row_dic.keys()) | |
| 144 return False | |
| 145 | |
| 146 dst = os.path.join(exp_dir, fname) | |
| 147 with open(dst, 'w+') as f: | |
| 148 f.write('\n'.join(li)) | |
| 149 return True | |
| 150 | |
| 151 | |
| 152 def main(): | |
| 153 d = os.path.dirname(os.path.abspath(__file__)) | |
| 154 os.chdir(d) | |
| 155 if not subprocess.call(['git', 'rev-parse'], stderr=subprocess.PIPE): | |
| 156 print 'Please copy script to a separate dir outside git repos to use.' | |
| 157 return | |
| 158 ts_str = '%s' % time.time() | |
| 159 | |
| 160 parser = argparse.ArgumentParser() | |
| 161 parser.add_argument('--url', | |
| 162 help='Broken bench build CheckForRegressions page url.') | |
| 163 parser.add_argument('--commit', action='store_true', | |
| 164 help='Whether to commit changes automatically.') | |
| 165 args = parser.parse_args() | |
| 166 repo_dir = os.path.join(d, 'skia') | |
| 167 if not os.path.exists(repo_dir): | |
| 168 os.makedirs(repo_dir) | |
| 169 if not checkout_or_update_skia(repo_dir): | |
| 170 print 'ERROR setting up Skia repo at %s' % repo_dir | |
| 171 return 1 | |
| 172 | |
| 173 file_in_repo = os.path.join(d, 'skia/experimental/benchtools/greenify.py') | |
| 174 if not filecmp.cmp(__file__, file_in_repo): | |
| 175 shutil.copy(file_in_repo, __file__) | |
| 176 print 'Updated this script from repo; please run again.' | |
| 177 return | |
| 178 | |
| 179 if not args.url: | |
| 180 raise Exception('Please provide a url with broken CheckForRegressions.') | |
| 181 path = args.url.split('/') | |
| 182 if len(path) != 11 or not path[6].isdigit(): | |
| 183 raise Exception('Unexpected url format: %s' % args.url) | |
| 184 bot = path[4] | |
| 185 build = path[6] | |
| 186 commit = False | |
| 187 if args.commit: | |
| 188 commit = True | |
| 189 | |
| 190 exp_dir = os.path.join(d, 'exp' + ts_str) | |
| 191 clean_dir(exp_dir) | |
| 192 if not widen_bench_ranges(args.url, bot, repo_dir, exp_dir): | |
| 193 print 'NO bench exceptions found! %s' % args.url | |
| 194 elif not git_commit_expectations( | |
| 195 repo_dir, exp_dir, bot, build, commit): | |
| 196 print 'ERROR uploading expectations using git.' | |
| 197 elif not commit: | |
| 198 print 'CL created. Please take a look at the link above.' | |
| 199 else: | |
| 200 print 'New bench baselines should be in CQ now.' | |
| 201 delete_dirs([exp_dir]) | |
| 202 | |
| 203 | |
| 204 if __name__ == "__main__": | |
| 205 main() | |
| OLD | NEW |