#!/usr/bin/env python # Copyright (c) 2014 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """rebase.py: standalone script to batch update bench expectations. Requires gsutil to access gs://chromium-skia-gm and Rietveld credentials. Usage: Copy script to a separate dir outside Skia repo. The script will create a skia dir on the first run to host the repo, and will create/delete temp dirs as needed. ./rebase.py --githash <githash prefix to use for getting bench data> """ import argparse import filecmp import os import re import shutil import subprocess import time import urllib2 # googlesource url that has most recent Skia git hash info. SKIA_GIT_HEAD_URL = 'https://skia.googlesource.com/skia/+log/HEAD' # Google Storage bench file prefix. GS_PREFIX = 'gs://chromium-skia-gm/perfdata' # List of Perf platforms we want to process. Populate from expectations/bench. PLATFORMS = [] # Regular expression for matching githash data. HA_RE = '<a href="/skia/\+/([0-9a-f]+)">' HA_RE_COMPILED = re.compile(HA_RE) def get_git_hashes(): print 'Getting recent git hashes...' hashes = HA_RE_COMPILED.findall( urllib2.urlopen(SKIA_GIT_HEAD_URL).read()) return hashes def filter_file(f): if f.find('_msaa') > 0 or f.find('_record') > 0: return True return False def clean_dir(d): if os.path.exists(d): shutil.rmtree(d) os.makedirs(d) def get_gs_filelist(p, h): print 'Looking up for the closest bench files in Google Storage...' proc = subprocess.Popen(['gsutil', 'ls', '/'.join([GS_PREFIX, p, 'bench_' + h + '_data_skp_*'])], stdout=subprocess.PIPE) out, err = proc.communicate() if err or not out: return [] return [i for i in out.strip().split('\n') if not filter_file(i)] def download_gs_files(p, h, gs_dir): print 'Downloading raw bench files from Google Storage...' proc = subprocess.Popen(['gsutil', 'cp', '/'.join([GS_PREFIX, p, 'bench_' + h + '_data_skp_*']), '%s/%s' % (gs_dir, p)], stdout=subprocess.PIPE) out, err = proc.communicate() if err: clean_dir(gs_dir) return False files = 0 for f in os.listdir(os.path.join(gs_dir, p)): if filter_file(f): os.remove(os.path.join(gs_dir, p, f)) else: files += 1 if files: return True return False def calc_expectations(p, h, gs_dir, exp_dir, repo_dir): exp_filename = 'bench_expectations_%s.txt' % p proc = subprocess.Popen(['python', 'skia/bench/gen_bench_expectations.py', '-r', h, '-b', p, '-d', os.path.join(gs_dir, p), '-o', os.path.join(exp_dir, exp_filename)], stdout=subprocess.PIPE) out, err = proc.communicate() if err: print 'ERR_CALCULATING_EXPECTATIONS: ' + err return False print 'CALCULATED_EXPECTATIONS: ' + out repo_file = os.path.join(repo_dir, 'expectations', 'bench', exp_filename) if (os.path.isfile(repo_file) and filecmp.cmp(repo_file, os.path.join(exp_dir, exp_filename))): print 'NO CHANGE ON %s' % repo_file return False return True def checkout_or_update_skia(repo_dir): status = True old_cwd = os.getcwd() os.chdir(repo_dir) print 'CHECK SKIA REPO...' if subprocess.call(['git', 'pull'], stderr=subprocess.PIPE): print 'Checking out Skia from git, please be patient...' os.chdir(old_cwd) clean_dir(repo_dir) os.chdir(repo_dir) if subprocess.call(['git', 'clone', '-q', '--depth=50', '--single-branch', 'https://skia.googlesource.com/skia.git', '.']): status = False subprocess.call(['git', 'checkout', 'master']) subprocess.call(['git', 'pull']) os.chdir(old_cwd) return status def git_commit_expectations(repo_dir, exp_dir, update_li, h, commit): commit_msg = """manual bench rebase after %s TBR=robertphillips@google.com Bypassing trybots: NOTRY=true""" % h old_cwd = os.getcwd() os.chdir(repo_dir) upload = ['git', 'cl', 'upload', '-f', '--bypass-hooks', '--bypass-watchlists', '-m', commit_msg] branch = exp_dir.split('/')[-1] if commit: upload.append('--use-commit-queue') cmds = ([['git', 'checkout', 'master'], ['git', 'pull'], ['git', 'checkout', '-b', branch, '-t', 'origin/master']] + [['cp', '%s/%s' % (exp_dir, f), 'expectations/bench'] for f in update_li] + [['git', 'add'] + ['expectations/bench/%s' % i for i in update_li], ['git', 'commit', '-m', commit_msg], upload, ['git', 'checkout', 'master'], ['git', 'branch', '-D', branch], ]) status = True for cmd in cmds: print 'Running ' + ' '.join(cmd) if subprocess.call(cmd): print 'FAILED. Please check if skia git repo is present.' subprocess.call(['git', 'checkout', 'master']) status = False break os.chdir(old_cwd) return status def delete_dirs(li): for d in li: print 'Deleting directory %s' % d shutil.rmtree(d) def main(): d = os.path.dirname(os.path.abspath(__file__)) os.chdir(d) if not subprocess.call(['git', 'rev-parse'], stderr=subprocess.PIPE): print 'Please copy script to a separate dir outside git repos to use.' return parser = argparse.ArgumentParser() parser.add_argument('--githash', help='Githash prefix (7+ chars) to rebaseline to.') parser.add_argument('--commit', action='store_true', help='Whether to commit changes automatically.') args = parser.parse_args() repo_dir = os.path.join(d, 'skia') if not os.path.exists(repo_dir): os.makedirs(repo_dir) if not checkout_or_update_skia(repo_dir): print 'ERROR setting up Skia repo at %s' % repo_dir return 1 file_in_repo = os.path.join(d, 'skia/experimental/benchtools/rebase.py') if not filecmp.cmp(__file__, file_in_repo): shutil.copy(file_in_repo, __file__) print 'Updated this script from repo; please run again.' return for item in os.listdir(os.path.join(d, 'skia/expectations/bench')): PLATFORMS.append( item.replace('bench_expectations_', '').replace('.txt', '')) if not args.githash or len(args.githash) < 7: raise Exception('Please provide --githash with a longer prefix (7+).') commit = False if args.commit: commit = True rebase_hash = args.githash[:7] hashes = get_git_hashes() short_hashes = [h[:7] for h in hashes] if rebase_hash not in short_hashes: raise Exception('Provided --githash not found in recent history!') hashes = hashes[:short_hashes.index(rebase_hash) + 1] update_li = [] ts_str = '%s' % time.time() gs_dir = os.path.join(d, 'gs' + ts_str) exp_dir = os.path.join(d, 'exp' + ts_str) clean_dir(gs_dir) clean_dir(exp_dir) for p in PLATFORMS: clean_dir(os.path.join(gs_dir, p)) hash_to_use = '' for h in reversed(hashes): li = get_gs_filelist(p, h) if not len(li): # no data continue if download_gs_files(p, h, gs_dir): print 'Copied %s/%s' % (p, h) hash_to_use = h break else: print 'DOWNLOAD BENCH FAILED %s/%s' % (p, h) break if hash_to_use: if calc_expectations(p, h, gs_dir, exp_dir, repo_dir): update_li.append('bench_expectations_%s.txt' % p) if not update_li: print 'No bench data to update after %s!' % args.githash elif not git_commit_expectations( repo_dir, exp_dir, update_li, args.githash[:7], commit): print 'ERROR uploading expectations using git.' elif not commit: print 'CL created. Please take a look at the link above.' else: print 'New bench baselines should be in CQ now.' delete_dirs([gs_dir, exp_dir]) if __name__ == "__main__": main()