Home | History | Annotate | Download | only in benchtools
      1 #!/usr/bin/env python
      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.
      5 
      6 
      7 """rebase.py: standalone script to batch update bench expectations.
      8 
      9     Requires gsutil to access gs://chromium-skia-gm and Rietveld credentials.
     10 
     11     Usage:
     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
     14           temp dirs as needed.
     15       ./rebase.py --githash <githash prefix to use for getting bench data>
     16 """
     17 
     18 
     19 import argparse
     20 import filecmp
     21 import os
     22 import re
     23 import shutil
     24 import subprocess
     25 import time
     26 import urllib2
     27 
     28 
     29 # googlesource url that has most recent Skia git hash info.
     30 SKIA_GIT_HEAD_URL = 'https://skia.googlesource.com/skia/+log/HEAD'
     31 
     32 # Google Storage bench file prefix.
     33 GS_PREFIX = 'gs://chromium-skia-gm/perfdata'
     34 
     35 # List of Perf platforms we want to process. Populate from expectations/bench.
     36 PLATFORMS = []
     37 
     38 # Regular expression for matching githash data.
     39 HA_RE = '<a href="/skia/\+/([0-9a-f]+)">'
     40 HA_RE_COMPILED = re.compile(HA_RE)
     41 
     42 
     43 def get_git_hashes():
     44   print 'Getting recent git hashes...'
     45   hashes = HA_RE_COMPILED.findall(
     46       urllib2.urlopen(SKIA_GIT_HEAD_URL).read())
     47 
     48   return hashes
     49 
     50 def filter_file(f):
     51   if f.find('_msaa') > 0 or f.find('_record') > 0:
     52     return True
     53 
     54   return False
     55 
     56 def clean_dir(d):
     57   if os.path.exists(d):
     58     shutil.rmtree(d)
     59   os.makedirs(d)
     60 
     61 def get_gs_filelist(p, h):
     62   print 'Looking up for the closest bench files in Google Storage...'
     63   proc = subprocess.Popen(['gsutil', 'ls',
     64       '/'.join([GS_PREFIX, p, 'bench_' + h + '_data_skp_*'])],
     65           stdout=subprocess.PIPE)
     66   out, err = proc.communicate()
     67   if err or not out:
     68     return []
     69   return [i for i in out.strip().split('\n') if not filter_file(i)]
     70 
     71 def download_gs_files(p, h, gs_dir):
     72   print 'Downloading raw bench files from Google Storage...'
     73   proc = subprocess.Popen(['gsutil', 'cp',
     74       '/'.join([GS_PREFIX, p, 'bench_' + h + '_data_skp_*']),
     75           '%s/%s' % (gs_dir, p)],
     76           stdout=subprocess.PIPE)
     77   out, err = proc.communicate()
     78   if err:
     79     clean_dir(gs_dir)
     80     return False
     81   files = 0
     82   for f in os.listdir(os.path.join(gs_dir, p)):
     83     if filter_file(f):
     84       os.remove(os.path.join(gs_dir, p, f))
     85     else:
     86       files += 1
     87   if files:
     88     return True
     89   return False
     90 
     91 def calc_expectations(p, h, gs_dir, exp_dir, repo_dir):
     92   exp_filename = 'bench_expectations_%s.txt' % p
     93   proc = subprocess.Popen(['python', 'skia/bench/gen_bench_expectations.py',
     94       '-r', h, '-b', p, '-d', os.path.join(gs_dir, p), '-o',
     95           os.path.join(exp_dir, exp_filename)],
     96               stdout=subprocess.PIPE)
     97   out, err = proc.communicate()
     98   if err:
     99     print 'ERR_CALCULATING_EXPECTATIONS: ' + err
    100     return False
    101   print 'CALCULATED_EXPECTATIONS: ' + out
    102   repo_file = os.path.join(repo_dir, 'expectations', 'bench', exp_filename)
    103   if (os.path.isfile(repo_file) and
    104       filecmp.cmp(repo_file, os.path.join(exp_dir, exp_filename))):
    105       print 'NO CHANGE ON %s' % repo_file
    106       return False
    107   return True
    108 
    109 def checkout_or_update_skia(repo_dir):
    110   status = True
    111   old_cwd = os.getcwd()
    112   os.chdir(repo_dir)
    113   print 'CHECK SKIA REPO...'
    114   if subprocess.call(['git', 'pull'],
    115                      stderr=subprocess.PIPE):
    116     print 'Checking out Skia from git, please be patient...'
    117     os.chdir(old_cwd)
    118     clean_dir(repo_dir)
    119     os.chdir(repo_dir)
    120     if subprocess.call(['git', 'clone', '-q', '--depth=50', '--single-branch',
    121                         'https://skia.googlesource.com/skia.git', '.']):
    122       status = False
    123   subprocess.call(['git', 'checkout', 'master'])
    124   subprocess.call(['git', 'pull'])
    125   os.chdir(old_cwd)
    126   return status
    127 
    128 def git_commit_expectations(repo_dir, exp_dir, update_li, h, commit):
    129   commit_msg = """manual bench rebase after %s
    130 
    131 TBR=robertphillips (at] google.com
    132 
    133 Bypassing trybots:
    134 NOTRY=true""" % h
    135   old_cwd = os.getcwd()
    136   os.chdir(repo_dir)
    137   upload = ['git', 'cl', 'upload', '-f', '--bypass-hooks',
    138             '--bypass-watchlists', '-m', commit_msg]
    139   branch = exp_dir.split('/')[-1]
    140   if commit:
    141     upload.append('--use-commit-queue')
    142   cmds = ([['git', 'checkout', 'master'],
    143            ['git', 'pull'],
    144            ['git', 'checkout', '-b', branch, '-t', 'origin/master']] +
    145           [['cp', '%s/%s' % (exp_dir, f), 'expectations/bench'] for f in
    146            update_li] +
    147           [['git', 'add'] + ['expectations/bench/%s' % i for i in update_li],
    148            ['git', 'commit', '-m', commit_msg],
    149            upload,
    150            ['git', 'checkout', 'master'],
    151            ['git', 'branch', '-D', branch],
    152           ])
    153   status = True
    154   for cmd in cmds:
    155     print 'Running ' + ' '.join(cmd)
    156     if subprocess.call(cmd):
    157       print 'FAILED. Please check if skia git repo is present.'
    158       subprocess.call(['git', 'checkout', 'master'])
    159       status = False
    160       break
    161   os.chdir(old_cwd)
    162   return status
    163 
    164 def delete_dirs(li):
    165   for d in li:
    166     print 'Deleting directory %s' % d
    167     shutil.rmtree(d)
    168 
    169 
    170 def main():
    171   d = os.path.dirname(os.path.abspath(__file__))
    172   os.chdir(d)
    173   if not subprocess.call(['git', 'rev-parse'], stderr=subprocess.PIPE):
    174     print 'Please copy script to a separate dir outside git repos to use.'
    175     return
    176   parser = argparse.ArgumentParser()
    177   parser.add_argument('--githash',
    178                       help='Githash prefix (7+ chars) to rebaseline to.')
    179   parser.add_argument('--commit', action='store_true',
    180                       help='Whether to commit changes automatically.')
    181   args = parser.parse_args()
    182 
    183   repo_dir = os.path.join(d, 'skia')
    184   if not os.path.exists(repo_dir):
    185     os.makedirs(repo_dir)
    186   if not checkout_or_update_skia(repo_dir):
    187     print 'ERROR setting up Skia repo at %s' % repo_dir
    188     return 1
    189 
    190   file_in_repo = os.path.join(d, 'skia/experimental/benchtools/rebase.py')
    191   if not filecmp.cmp(__file__, file_in_repo):
    192     shutil.copy(file_in_repo, __file__)
    193     print 'Updated this script from repo; please run again.'
    194     return
    195 
    196   for item in os.listdir(os.path.join(d, 'skia/expectations/bench')):
    197     PLATFORMS.append(
    198         item.replace('bench_expectations_', '').replace('.txt', ''))
    199 
    200   if not args.githash or len(args.githash) < 7:
    201     raise Exception('Please provide --githash with a longer prefix (7+).')
    202   commit = False
    203   if args.commit:
    204     commit = True
    205   rebase_hash = args.githash[:7]
    206   hashes = get_git_hashes()
    207   short_hashes = [h[:7] for h in hashes]
    208   if rebase_hash not in short_hashes:
    209     raise Exception('Provided --githash not found in recent history!')
    210   hashes = hashes[:short_hashes.index(rebase_hash) + 1]
    211   update_li = []
    212 
    213   ts_str = '%s' % time.time()
    214   gs_dir = os.path.join(d, 'gs' + ts_str)
    215   exp_dir = os.path.join(d, 'exp' + ts_str)
    216   clean_dir(gs_dir)
    217   clean_dir(exp_dir)
    218   for p in PLATFORMS:
    219     clean_dir(os.path.join(gs_dir, p))
    220     hash_to_use = ''
    221     for h in reversed(hashes):
    222       li = get_gs_filelist(p, h)
    223       if not len(li):  # no data
    224         continue
    225       if download_gs_files(p, h, gs_dir):
    226         print 'Copied %s/%s' % (p, h)
    227         hash_to_use = h
    228         break
    229       else:
    230         print 'DOWNLOAD BENCH FAILED %s/%s' % (p, h)
    231         break
    232     if hash_to_use:
    233       if calc_expectations(p, h, gs_dir, exp_dir, repo_dir):
    234         update_li.append('bench_expectations_%s.txt' % p)
    235   if not update_li:
    236     print 'No bench data to update after %s!' % args.githash
    237   elif not git_commit_expectations(
    238       repo_dir, exp_dir, update_li, args.githash[:7], commit):
    239     print 'ERROR uploading expectations using git.'
    240   elif not commit:
    241     print 'CL created. Please take a look at the link above.'
    242   else:
    243     print 'New bench baselines should be in CQ now.'
    244   delete_dirs([gs_dir, exp_dir])
    245 
    246 
    247 if __name__ == "__main__":
    248   main()
    249