commit-bot@chromium.org | 336df8f | 2014-05-21 22:57:44 +0000 | [diff] [blame] | 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 | |
commit-bot@chromium.org | 5ddea76 | 2014-05-21 21:12:11 +0000 | [diff] [blame] | 8 | |
| 9 | """greenify.py: standalone script to correct flaky bench expectations. |
commit-bot@chromium.org | 336df8f | 2014-05-21 22:57:44 +0000 | [diff] [blame] | 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> |
commit-bot@chromium.org | 5ddea76 | 2014-05-21 21:12:11 +0000 | [diff] [blame] | 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 | |
commit-bot@chromium.org | 7776735 | 2014-05-22 12:00:55 +0000 | [diff] [blame] | 63 | TBR=bsalomon@google.com |
commit-bot@chromium.org | 5ddea76 | 2014-05-21 21:12:11 +0000 | [diff] [blame] | 64 | |
commit-bot@chromium.org | 7776735 | 2014-05-22 12:00:55 +0000 | [diff] [blame] | 65 | Bypassing trybots: |
| 66 | NOTRY=true""" % (bot, build) |
commit-bot@chromium.org | 5ddea76 | 2014-05-21 21:12:11 +0000 | [diff] [blame] | 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() |
commit-bot@chromium.org | 5ddea76 | 2014-05-21 21:12:11 +0000 | [diff] [blame] | 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 | |
commit-bot@chromium.org | 336df8f | 2014-05-21 22:57:44 +0000 | [diff] [blame] | 190 | exp_dir = os.path.join(d, 'exp' + ts_str) |
| 191 | clean_dir(exp_dir) |
commit-bot@chromium.org | 5ddea76 | 2014-05-21 21:12:11 +0000 | [diff] [blame] | 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() |