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.
7 """rebase.py: standalone script to batch update bench expectations.
9 Requires gsutil to access gs://chromium-skia-gm and Rietveld credentials.
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
15 ./rebase.py --githash <githash prefix to use for getting bench data>
29 # googlesource url that has most recent Skia git hash info.
30 SKIA_GIT_HEAD_URL = 'https://skia.googlesource.com/skia/+log/HEAD'
32 # Google Storage bench file prefix.
33 GS_PREFIX = 'gs://chromium-skia-gm/perfdata'
35 # Regular expression for matching githash data.
36 HA_RE = '<a href="/skia/\+/([0-9a-f]+)">'
37 HA_RE_COMPILED = re.compile(HA_RE)
41 print 'Getting recent git hashes...'
42 hashes = HA_RE_COMPILED.findall(
43 urllib2.urlopen(SKIA_GIT_HEAD_URL).read())
48 if f.find('_msaa') > 0 or f.find('_record') > 0:
58 def get_gs_filelist(p, h):
59 print 'Looking up for the closest bench files in Google Storage...'
60 proc = subprocess.Popen(['gsutil', 'ls',
61 '/'.join([GS_PREFIX, p, 'bench_' + h + '_data_skp_*'])],
62 stdout=subprocess.PIPE)
63 out, err = proc.communicate()
66 return [i for i in out.strip().split('\n') if not filter_file(i)]
68 def download_gs_files(p, h, gs_dir):
69 print 'Downloading raw bench files from Google Storage...'
70 proc = subprocess.Popen(['gsutil', 'cp',
71 '/'.join([GS_PREFIX, p, 'bench_' + h + '_data_skp_*']),
72 '%s/%s' % (gs_dir, p)],
73 stdout=subprocess.PIPE)
74 out, err = proc.communicate()
79 for f in os.listdir(os.path.join(gs_dir, p)):
81 os.remove(os.path.join(gs_dir, p, f))
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.
92 for l in open(f).readlines():
93 line_parts = l.strip().split(',')
94 if line_parts[0].startswith('#') or len(line_parts) != 5:
96 dic[','.join(line_parts[:2])] = (float(line_parts[2]), float(line_parts[3]),
101 def calc_expectations(p, h, gs_dir, exp_dir, repo_dir, extra_dir, extra_hash):
102 exp_filename = 'bench_expectations_%s.txt' % p
103 exp_fullname = os.path.join(exp_dir, exp_filename)
104 proc = subprocess.Popen(['python', 'skia/bench/gen_bench_expectations.py',
105 '-r', h, '-b', p, '-d', os.path.join(gs_dir, p), '-o', exp_fullname],
106 stdout=subprocess.PIPE)
107 out, err = proc.communicate()
109 print 'ERR_CALCULATING_EXPECTATIONS: ' + err
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()
120 print 'ERR_CALCULATING_EXTRA_EXPECTATIONS: ' + err
122 extra_dic = get_expectations_dict(os.path.join(extra_dir, exp_filename))
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())
129 key = ','.join(parts[:2])
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))
139 output_lines.append(l.strip())
140 with open(exp_fullname, 'w') as f:
141 f.write('\n'.join(output_lines))
143 repo_file = os.path.join(repo_dir, 'expectations', 'bench', exp_filename)
144 if (os.path.isfile(repo_file) and
145 filecmp.cmp(repo_file, os.path.join(exp_dir, exp_filename))):
146 print 'NO CHANGE ON %s' % repo_file
150 def checkout_or_update_skia(repo_dir):
152 old_cwd = os.getcwd()
154 print 'CHECK SKIA REPO...'
155 if subprocess.call(['git', 'pull'],
156 stderr=subprocess.PIPE):
157 print 'Checking out Skia from git, please be patient...'
161 if subprocess.call(['git', 'clone', '-q', '--depth=50', '--single-branch',
162 'https://skia.googlesource.com/skia.git', '.']):
164 subprocess.call(['git', 'checkout', 'master'])
165 subprocess.call(['git', 'pull'])
169 def git_commit_expectations(repo_dir, exp_dir, update_li, h, commit,
172 extra_hash = ', adjusted with ' + extra_hash
173 commit_msg = """manual bench rebase after %s%s
175 TBR=robertphillips@google.com
178 NOTRY=true""" % (h, extra_hash)
179 old_cwd = os.getcwd()
181 upload = ['git', 'cl', 'upload', '-f', '--bypass-hooks',
182 '--bypass-watchlists', '-m', commit_msg]
183 branch = exp_dir.split('/')[-1]
185 upload.append('--use-commit-queue')
186 cmds = ([['git', 'checkout', 'master'],
188 ['git', 'checkout', '-b', branch, '-t', 'origin/master']] +
189 [['cp', '%s/%s' % (exp_dir, f), 'expectations/bench'] for f in
191 [['git', 'add'] + ['expectations/bench/%s' % i for i in update_li],
192 ['git', 'commit', '-m', commit_msg],
194 ['git', 'checkout', 'master'],
195 ['git', 'branch', '-D', branch],
199 print 'Running ' + ' '.join(cmd)
200 if subprocess.call(cmd):
201 print 'FAILED. Please check if skia git repo is present.'
202 subprocess.call(['git', 'checkout', 'master'])
210 print 'Deleting directory %s' % d
215 d = os.path.dirname(os.path.abspath(__file__))
217 if not subprocess.call(['git', 'rev-parse'], stderr=subprocess.PIPE):
218 print 'Please copy script to a separate dir outside git repos to use.'
220 parser = argparse.ArgumentParser()
221 parser.add_argument('--githash',
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.'))
230 parser.add_argument('--commit', action='store_true',
231 help='Whether to commit changes automatically.')
232 args = parser.parse_args()
234 repo_dir = os.path.join(d, 'skia')
235 if not os.path.exists(repo_dir):
236 os.makedirs(repo_dir)
237 if not checkout_or_update_skia(repo_dir):
238 print 'ERROR setting up Skia repo at %s' % repo_dir
241 file_in_repo = os.path.join(d, 'skia/experimental/benchtools/rebase.py')
242 if not filecmp.cmp(__file__, file_in_repo):
243 shutil.copy(file_in_repo, __file__)
244 print 'Updated this script from repo; please run again.'
247 all_platforms = [] # Find existing list of platforms with expectations.
248 for item in os.listdir(os.path.join(d, 'skia/expectations/bench')):
249 all_platforms.append(
250 item.replace('bench_expectations_', '').replace('.txt', ''))
253 # If at least one given bot is in all_platforms, use list of valid args.bots.
255 bots = args.bots.strip().split(',')
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
262 if not args.githash or len(args.githash) < 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+).')
270 rebase_hash = githashes[0][:7]
272 if len(githashes) == 2:
273 extra_hash = githashes[1][:7]
274 hashes = get_git_hashes()
275 short_hashes = [h[:7] for h in hashes]
276 if (rebase_hash not in short_hashes or
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!')
281 extra_hash = hashes[short_hashes.index(extra_hash)]
282 hashes = hashes[:short_hashes.index(rebase_hash) + 1]
285 ts_str = '%s' % time.time()
286 gs_dir = os.path.join(d, 'gs' + ts_str)
287 exp_dir = os.path.join(d, 'exp' + ts_str)
288 extra_dir = os.path.join(d, 'extra' + ts_str)
293 clean_dir(os.path.join(gs_dir, p))
294 clean_dir(os.path.join(extra_dir, p))
296 for h in reversed(hashes):
297 li = get_gs_filelist(p, h)
298 if not len(li): # no data
300 if download_gs_files(p, h, gs_dir):
301 print 'Copied %s/%s' % (p, h)
305 print 'DOWNLOAD BENCH FAILED %s/%s' % (p, h)
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,
312 update_li.append('bench_expectations_%s.txt' % p)
313 elif calc_expectations(p, h, gs_dir, exp_dir, repo_dir, '', ''):
314 update_li.append('bench_expectations_%s.txt' % p)
316 print 'No bench data to update after %s!' % args.githash
317 elif not git_commit_expectations(
318 repo_dir, exp_dir, update_li, rebase_hash, commit, extra_hash):
319 print 'ERROR uploading expectations using git.'
321 print 'CL created. Please take a look at the link above.'
323 print 'New bench baselines should be in CQ now.'
324 delete_dirs([gs_dir, exp_dir, extra_dir])
327 if __name__ == "__main__":