OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2014 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 | 6 |
7 """rebase.py: standalone script to batch update bench expectations. | 7 """rebase.py: standalone script to batch update bench expectations. |
8 | 8 |
9 Requires gsutil to access gs://chromium-skia-gm and Rietveld credentials. | 9 Requires gsutil to access gs://chromium-skia-gm and Rietveld credentials. |
10 | 10 |
(...skipping 14 matching lines...) Expand all Loading... |
25 import time | 25 import time |
26 import urllib2 | 26 import urllib2 |
27 | 27 |
28 | 28 |
29 # googlesource url that has most recent Skia git hash info. | 29 # googlesource url that has most recent Skia git hash info. |
30 SKIA_GIT_HEAD_URL = 'https://skia.googlesource.com/skia/+log/HEAD' | 30 SKIA_GIT_HEAD_URL = 'https://skia.googlesource.com/skia/+log/HEAD' |
31 | 31 |
32 # Google Storage bench file prefix. | 32 # Google Storage bench file prefix. |
33 GS_PREFIX = 'gs://chromium-skia-gm/perfdata' | 33 GS_PREFIX = 'gs://chromium-skia-gm/perfdata' |
34 | 34 |
35 # List of Perf platforms we want to process. Populate from expectations/bench. | |
36 PLATFORMS = [] | |
37 | |
38 # Regular expression for matching githash data. | 35 # Regular expression for matching githash data. |
39 HA_RE = '<a href="/skia/\+/([0-9a-f]+)">' | 36 HA_RE = '<a href="/skia/\+/([0-9a-f]+)">' |
40 HA_RE_COMPILED = re.compile(HA_RE) | 37 HA_RE_COMPILED = re.compile(HA_RE) |
41 | 38 |
42 | 39 |
43 def get_git_hashes(): | 40 def get_git_hashes(): |
44 print 'Getting recent git hashes...' | 41 print 'Getting recent git hashes...' |
45 hashes = HA_RE_COMPILED.findall( | 42 hashes = HA_RE_COMPILED.findall( |
46 urllib2.urlopen(SKIA_GIT_HEAD_URL).read()) | 43 urllib2.urlopen(SKIA_GIT_HEAD_URL).read()) |
47 | 44 |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
81 files = 0 | 78 files = 0 |
82 for f in os.listdir(os.path.join(gs_dir, p)): | 79 for f in os.listdir(os.path.join(gs_dir, p)): |
83 if filter_file(f): | 80 if filter_file(f): |
84 os.remove(os.path.join(gs_dir, p, f)) | 81 os.remove(os.path.join(gs_dir, p, f)) |
85 else: | 82 else: |
86 files += 1 | 83 files += 1 |
87 if files: | 84 if files: |
88 return True | 85 return True |
89 return False | 86 return False |
90 | 87 |
91 def calc_expectations(p, h, gs_dir, exp_dir, repo_dir): | 88 def get_expectations_dict(f): |
| 89 """Given an expectations file f, returns a dictionary of data.""" |
| 90 # maps row_key to (expected, lower_bound, upper_bound) float tuple. |
| 91 dic = {} |
| 92 for l in open(f).readlines(): |
| 93 line_parts = l.strip().split(',') |
| 94 if line_parts[0].startswith('#') or len(line_parts) != 5: |
| 95 continue |
| 96 dic[','.join(line_parts[:2])] = (float(line_parts[2]), float(line_parts[3]), |
| 97 float(line_parts[4])) |
| 98 |
| 99 return dic |
| 100 |
| 101 def calc_expectations(p, h, gs_dir, exp_dir, repo_dir, extra_dir, extra_hash): |
92 exp_filename = 'bench_expectations_%s.txt' % p | 102 exp_filename = 'bench_expectations_%s.txt' % p |
| 103 exp_fullname = os.path.join(exp_dir, exp_filename) |
93 proc = subprocess.Popen(['python', 'skia/bench/gen_bench_expectations.py', | 104 proc = subprocess.Popen(['python', 'skia/bench/gen_bench_expectations.py', |
94 '-r', h, '-b', p, '-d', os.path.join(gs_dir, p), '-o', | 105 '-r', h, '-b', p, '-d', os.path.join(gs_dir, p), '-o', exp_fullname], |
95 os.path.join(exp_dir, exp_filename)], | |
96 stdout=subprocess.PIPE) | 106 stdout=subprocess.PIPE) |
97 out, err = proc.communicate() | 107 out, err = proc.communicate() |
98 if err: | 108 if err: |
99 print 'ERR_CALCULATING_EXPECTATIONS: ' + err | 109 print 'ERR_CALCULATING_EXPECTATIONS: ' + err |
100 return False | 110 return False |
101 print 'CALCULATED_EXPECTATIONS: ' + out | 111 print 'CALCULATED_EXPECTATIONS: ' + out |
| 112 if extra_dir: # Adjust data with the ones in extra_dir |
| 113 print 'USE_EXTRA_DATA_FOR_ADJUSTMENT.' |
| 114 proc = subprocess.Popen(['python', 'skia/bench/gen_bench_expectations.py', |
| 115 '-r', extra_hash, '-b', p, '-d', os.path.join(extra_dir, p), '-o', |
| 116 os.path.join(extra_dir, exp_filename)], |
| 117 stdout=subprocess.PIPE) |
| 118 out, err = proc.communicate() |
| 119 if err: |
| 120 print 'ERR_CALCULATING_EXTRA_EXPECTATIONS: ' + err |
| 121 return False |
| 122 extra_dic = get_expectations_dict(os.path.join(extra_dir, exp_filename)) |
| 123 output_lines = [] |
| 124 for l in open(exp_fullname).readlines(): |
| 125 parts = l.strip().split(',') |
| 126 if parts[0].startswith('#') or len(parts) != 5: |
| 127 output_lines.append(l.strip()) |
| 128 continue |
| 129 key = ','.join(parts[:2]) |
| 130 if key in extra_dic: |
| 131 exp, lb, ub = (float(parts[2]), float(parts[3]), float(parts[4])) |
| 132 alt, _, _ = extra_dic[key] |
| 133 avg = (exp + alt) / 2 |
| 134 # Keeps the extra range in lower/upper bounds from two actual values. |
| 135 new_lb = min(exp, alt) - (exp - lb) |
| 136 new_ub = max(exp, alt) + (ub - exp) |
| 137 output_lines.append('%s,%.2f,%.2f,%.2f' % (key, avg, new_lb, new_ub)) |
| 138 else: |
| 139 output_lines.append(l.strip()) |
| 140 with open(exp_fullname, 'w') as f: |
| 141 f.write('\n'.join(output_lines)) |
| 142 |
102 repo_file = os.path.join(repo_dir, 'expectations', 'bench', exp_filename) | 143 repo_file = os.path.join(repo_dir, 'expectations', 'bench', exp_filename) |
103 if (os.path.isfile(repo_file) and | 144 if (os.path.isfile(repo_file) and |
104 filecmp.cmp(repo_file, os.path.join(exp_dir, exp_filename))): | 145 filecmp.cmp(repo_file, os.path.join(exp_dir, exp_filename))): |
105 print 'NO CHANGE ON %s' % repo_file | 146 print 'NO CHANGE ON %s' % repo_file |
106 return False | 147 return False |
107 return True | 148 return True |
108 | 149 |
109 def checkout_or_update_skia(repo_dir): | 150 def checkout_or_update_skia(repo_dir): |
110 status = True | 151 status = True |
111 old_cwd = os.getcwd() | 152 old_cwd = os.getcwd() |
112 os.chdir(repo_dir) | 153 os.chdir(repo_dir) |
113 print 'CHECK SKIA REPO...' | 154 print 'CHECK SKIA REPO...' |
114 if subprocess.call(['git', 'pull'], | 155 if subprocess.call(['git', 'pull'], |
115 stderr=subprocess.PIPE): | 156 stderr=subprocess.PIPE): |
116 print 'Checking out Skia from git, please be patient...' | 157 print 'Checking out Skia from git, please be patient...' |
117 os.chdir(old_cwd) | 158 os.chdir(old_cwd) |
118 clean_dir(repo_dir) | 159 clean_dir(repo_dir) |
119 os.chdir(repo_dir) | 160 os.chdir(repo_dir) |
120 if subprocess.call(['git', 'clone', '-q', '--depth=50', '--single-branch', | 161 if subprocess.call(['git', 'clone', '-q', '--depth=50', '--single-branch', |
121 'https://skia.googlesource.com/skia.git', '.']): | 162 'https://skia.googlesource.com/skia.git', '.']): |
122 status = False | 163 status = False |
123 subprocess.call(['git', 'checkout', 'master']) | 164 subprocess.call(['git', 'checkout', 'master']) |
124 subprocess.call(['git', 'pull']) | 165 subprocess.call(['git', 'pull']) |
125 os.chdir(old_cwd) | 166 os.chdir(old_cwd) |
126 return status | 167 return status |
127 | 168 |
128 def git_commit_expectations(repo_dir, exp_dir, update_li, h, commit): | 169 def git_commit_expectations(repo_dir, exp_dir, update_li, h, commit, |
129 commit_msg = """manual bench rebase after %s | 170 extra_hash): |
| 171 if extra_hash: |
| 172 extra_hash = ', adjusted with ' + extra_hash |
| 173 commit_msg = """manual bench rebase after %s%s |
130 | 174 |
131 TBR=robertphillips@google.com | 175 TBR=robertphillips@google.com |
132 | 176 |
133 Bypassing trybots: | 177 Bypassing trybots: |
134 NOTRY=true""" % h | 178 NOTRY=true""" % (h, extra_hash) |
135 old_cwd = os.getcwd() | 179 old_cwd = os.getcwd() |
136 os.chdir(repo_dir) | 180 os.chdir(repo_dir) |
137 upload = ['git', 'cl', 'upload', '-f', '--bypass-hooks', | 181 upload = ['git', 'cl', 'upload', '-f', '--bypass-hooks', |
138 '--bypass-watchlists', '-m', commit_msg] | 182 '--bypass-watchlists', '-m', commit_msg] |
139 branch = exp_dir.split('/')[-1] | 183 branch = exp_dir.split('/')[-1] |
140 if commit: | 184 if commit: |
141 upload.append('--use-commit-queue') | 185 upload.append('--use-commit-queue') |
142 cmds = ([['git', 'checkout', 'master'], | 186 cmds = ([['git', 'checkout', 'master'], |
143 ['git', 'pull'], | 187 ['git', 'pull'], |
144 ['git', 'checkout', '-b', branch, '-t', 'origin/master']] + | 188 ['git', 'checkout', '-b', branch, '-t', 'origin/master']] + |
(...skipping 23 matching lines...) Expand all Loading... |
168 | 212 |
169 | 213 |
170 def main(): | 214 def main(): |
171 d = os.path.dirname(os.path.abspath(__file__)) | 215 d = os.path.dirname(os.path.abspath(__file__)) |
172 os.chdir(d) | 216 os.chdir(d) |
173 if not subprocess.call(['git', 'rev-parse'], stderr=subprocess.PIPE): | 217 if not subprocess.call(['git', 'rev-parse'], stderr=subprocess.PIPE): |
174 print 'Please copy script to a separate dir outside git repos to use.' | 218 print 'Please copy script to a separate dir outside git repos to use.' |
175 return | 219 return |
176 parser = argparse.ArgumentParser() | 220 parser = argparse.ArgumentParser() |
177 parser.add_argument('--githash', | 221 parser.add_argument('--githash', |
178 help='Githash prefix (7+ chars) to rebaseline to.') | 222 help=('Githash prefix (7+ chars) to rebaseline to. If ' |
| 223 'a second one is supplied after comma, and it has ' |
| 224 'corresponding bench data, will shift the range ' |
| 225 'center to the average of two expected values.')) |
| 226 parser.add_argument('--bots', |
| 227 help=('Comma-separated list of bots to work on. If no ' |
| 228 'matching bots are found in the list, will default ' |
| 229 'to processing all bots.')) |
179 parser.add_argument('--commit', action='store_true', | 230 parser.add_argument('--commit', action='store_true', |
180 help='Whether to commit changes automatically.') | 231 help='Whether to commit changes automatically.') |
181 args = parser.parse_args() | 232 args = parser.parse_args() |
182 | 233 |
183 repo_dir = os.path.join(d, 'skia') | 234 repo_dir = os.path.join(d, 'skia') |
184 if not os.path.exists(repo_dir): | 235 if not os.path.exists(repo_dir): |
185 os.makedirs(repo_dir) | 236 os.makedirs(repo_dir) |
186 if not checkout_or_update_skia(repo_dir): | 237 if not checkout_or_update_skia(repo_dir): |
187 print 'ERROR setting up Skia repo at %s' % repo_dir | 238 print 'ERROR setting up Skia repo at %s' % repo_dir |
188 return 1 | 239 return 1 |
189 | 240 |
190 file_in_repo = os.path.join(d, 'skia/experimental/benchtools/rebase.py') | 241 file_in_repo = os.path.join(d, 'skia/experimental/benchtools/rebase.py') |
191 if not filecmp.cmp(__file__, file_in_repo): | 242 if not filecmp.cmp(__file__, file_in_repo): |
192 shutil.copy(file_in_repo, __file__) | 243 shutil.copy(file_in_repo, __file__) |
193 print 'Updated this script from repo; please run again.' | 244 print 'Updated this script from repo; please run again.' |
194 return | 245 return |
195 | 246 |
| 247 all_platforms = [] # Find existing list of platforms with expectations. |
196 for item in os.listdir(os.path.join(d, 'skia/expectations/bench')): | 248 for item in os.listdir(os.path.join(d, 'skia/expectations/bench')): |
197 PLATFORMS.append( | 249 all_platforms.append( |
198 item.replace('bench_expectations_', '').replace('.txt', '')) | 250 item.replace('bench_expectations_', '').replace('.txt', '')) |
199 | 251 |
| 252 platforms = [] |
| 253 # If at least one given bot is in all_platforms, use list of valid args.bots. |
| 254 if args.bots: |
| 255 bots = args.bots.strip().split(',') |
| 256 for bot in bots: |
| 257 if bot in all_platforms: # Filters platforms with given bot list. |
| 258 platforms.append(bot) |
| 259 if not platforms: # Include all existing platforms with expectations. |
| 260 platforms = all_platforms |
| 261 |
200 if not args.githash or len(args.githash) < 7: | 262 if not args.githash or len(args.githash) < 7: |
201 raise Exception('Please provide --githash with a longer prefix (7+).') | 263 raise Exception('Please provide --githash with a longer prefix (7+).') |
| 264 githashes = args.githash.strip().split(',') |
| 265 if len(githashes[0]) < 7: |
| 266 raise Exception('Please provide --githash with longer prefixes (7+).') |
202 commit = False | 267 commit = False |
203 if args.commit: | 268 if args.commit: |
204 commit = True | 269 commit = True |
205 rebase_hash = args.githash[:7] | 270 rebase_hash = githashes[0][:7] |
| 271 extra_hash = '' |
| 272 if len(githashes) == 2: |
| 273 extra_hash = githashes[1][:7] |
206 hashes = get_git_hashes() | 274 hashes = get_git_hashes() |
207 short_hashes = [h[:7] for h in hashes] | 275 short_hashes = [h[:7] for h in hashes] |
208 if rebase_hash not in short_hashes: | 276 if (rebase_hash not in short_hashes or |
209 raise Exception('Provided --githash not found in recent history!') | 277 (extra_hash and extra_hash not in short_hashes) or |
| 278 rebase_hash == extra_hash): |
| 279 raise Exception('Provided --githashes not found, or identical!') |
| 280 if extra_hash: |
| 281 extra_hash = hashes[short_hashes.index(extra_hash)] |
210 hashes = hashes[:short_hashes.index(rebase_hash) + 1] | 282 hashes = hashes[:short_hashes.index(rebase_hash) + 1] |
211 update_li = [] | 283 update_li = [] |
212 | 284 |
213 ts_str = '%s' % time.time() | 285 ts_str = '%s' % time.time() |
214 gs_dir = os.path.join(d, 'gs' + ts_str) | 286 gs_dir = os.path.join(d, 'gs' + ts_str) |
215 exp_dir = os.path.join(d, 'exp' + ts_str) | 287 exp_dir = os.path.join(d, 'exp' + ts_str) |
| 288 extra_dir = os.path.join(d, 'extra' + ts_str) |
216 clean_dir(gs_dir) | 289 clean_dir(gs_dir) |
217 clean_dir(exp_dir) | 290 clean_dir(exp_dir) |
218 for p in PLATFORMS: | 291 clean_dir(extra_dir) |
| 292 for p in platforms: |
219 clean_dir(os.path.join(gs_dir, p)) | 293 clean_dir(os.path.join(gs_dir, p)) |
| 294 clean_dir(os.path.join(extra_dir, p)) |
220 hash_to_use = '' | 295 hash_to_use = '' |
221 for h in reversed(hashes): | 296 for h in reversed(hashes): |
222 li = get_gs_filelist(p, h) | 297 li = get_gs_filelist(p, h) |
223 if not len(li): # no data | 298 if not len(li): # no data |
224 continue | 299 continue |
225 if download_gs_files(p, h, gs_dir): | 300 if download_gs_files(p, h, gs_dir): |
226 print 'Copied %s/%s' % (p, h) | 301 print 'Copied %s/%s' % (p, h) |
227 hash_to_use = h | 302 hash_to_use = h |
228 break | 303 break |
229 else: | 304 else: |
230 print 'DOWNLOAD BENCH FAILED %s/%s' % (p, h) | 305 print 'DOWNLOAD BENCH FAILED %s/%s' % (p, h) |
231 break | 306 break |
232 if hash_to_use: | 307 if hash_to_use: |
233 if calc_expectations(p, h, gs_dir, exp_dir, repo_dir): | 308 if extra_hash and download_gs_files(p, extra_hash, extra_dir): |
| 309 print 'Copied extra data %s/%s' % (p, extra_hash) |
| 310 if calc_expectations(p, h, gs_dir, exp_dir, repo_dir, extra_dir, |
| 311 extra_hash): |
| 312 update_li.append('bench_expectations_%s.txt' % p) |
| 313 elif calc_expectations(p, h, gs_dir, exp_dir, repo_dir, '', ''): |
234 update_li.append('bench_expectations_%s.txt' % p) | 314 update_li.append('bench_expectations_%s.txt' % p) |
235 if not update_li: | 315 if not update_li: |
236 print 'No bench data to update after %s!' % args.githash | 316 print 'No bench data to update after %s!' % args.githash |
237 elif not git_commit_expectations( | 317 elif not git_commit_expectations( |
238 repo_dir, exp_dir, update_li, args.githash[:7], commit): | 318 repo_dir, exp_dir, update_li, rebase_hash, commit, extra_hash): |
239 print 'ERROR uploading expectations using git.' | 319 print 'ERROR uploading expectations using git.' |
240 elif not commit: | 320 elif not commit: |
241 print 'CL created. Please take a look at the link above.' | 321 print 'CL created. Please take a look at the link above.' |
242 else: | 322 else: |
243 print 'New bench baselines should be in CQ now.' | 323 print 'New bench baselines should be in CQ now.' |
244 delete_dirs([gs_dir, exp_dir]) | 324 delete_dirs([gs_dir, exp_dir, extra_dir]) |
245 | 325 |
246 | 326 |
247 if __name__ == "__main__": | 327 if __name__ == "__main__": |
248 main() | 328 main() |
OLD | NEW |