| #!/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', '-q', |
| '/'.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() |