普通文本  |  206行  |  6.2 KB

#!/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.


"""greenify.py: standalone script to correct flaky bench expectations.

    Requires Rietveld credentials on the running machine.

    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.
      ./greenify.py --url <the stdio url from failed CheckForRegressions step>
"""

import argparse
import filecmp
import os
import re
import shutil
import subprocess
import time
import urllib2


# Regular expression for matching exception data.
EXCEPTION_RE = ('Bench (\S+) out of range \[(\d+.\d+), (\d+.\d+)\] \((\d+.\d+) '
                'vs (\d+.\d+), ')
EXCEPTION_RE_COMPILED = re.compile(EXCEPTION_RE)


def clean_dir(d):
  if os.path.exists(d):
    shutil.rmtree(d)
  os.makedirs(d)

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, bot, build, commit):
  commit_msg = """Greenify bench bot %s at build %s

TBR=bsalomon@google.com

Bypassing trybots:
NOTRY=true""" % (bot, build)
  old_cwd = os.getcwd()
  os.chdir(repo_dir)
  upload = ['git', 'cl', 'upload', '-f', '--bypass-hooks',
            '--bypass-watchlists', '-m', commit_msg]
  if commit:
    upload.append('--use-commit-queue')
  branch = exp_dir[exp_dir.rfind('/') + 1:]
  filename = 'bench_expectations_%s.txt' % bot
  cmds = ([['git', 'checkout', 'master'],
           ['git', 'pull'],
           ['git', 'checkout', '-b', branch, '-t', 'origin/master'],
           ['cp', '%s/%s' % (exp_dir, filename), 'expectations/bench'],
           ['git', 'add', 'expectations/bench/' + filename],
           ['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 widen_bench_ranges(url, bot, repo_dir, exp_dir):
  fname = 'bench_expectations_%s.txt' % bot
  src = os.path.join(repo_dir, 'expectations', 'bench', fname)
  if not os.path.isfile(src):
    print 'This bot has no expectations! %s' % bot
    return False
  row_dic = {}
  for l in urllib2.urlopen(url).read().split('\n'):
    data = EXCEPTION_RE_COMPILED.search(l)
    if data:
      row = data.group(1)
      lb = float(data.group(2))
      ub = float(data.group(3))
      actual = float(data.group(4))
      exp = float(data.group(5))
      avg = (actual + exp) / 2
      shift = avg - exp
      lb = lb + shift
      ub = ub + shift
      # In case outlier really fluctuates a lot
      if actual < lb:
        lb = actual - abs(shift) * 0.1 + 0.5
      elif actual > ub:
        ub = actual + abs(shift) * 0.1 + 0.5
      row_dic[row] = '%.2f,%.2f,%.2f' % (avg, lb, ub)
  if not row_dic:
    print 'NO out-of-range benches found at %s' % url
    return False

  changed = 0
  li = []
  for l in open(src).readlines():
    parts = l.strip().split(',')
    if parts[0].startswith('#') or len(parts) != 5:
      li.append(l.strip())
      continue
    if ','.join(parts[:2]) in row_dic:
      li.append(','.join(parts[:2]) + ',' + row_dic[','.join(parts[:2])])
      changed += 1
    else:
      li.append(l.strip())
  if not changed:
    print 'Not in source file:\n' + '\n'.join(row_dic.keys())
    return False

  dst = os.path.join(exp_dir, fname)
  with open(dst, 'w+') as f:
    f.write('\n'.join(li))
  return True


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
  ts_str = '%s' % time.time()

  parser = argparse.ArgumentParser()
  parser.add_argument('--url',
                      help='Broken bench build CheckForRegressions page url.')
  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/greenify.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

  if not args.url:
    raise Exception('Please provide a url with broken CheckForRegressions.')
  path = args.url.split('/')
  if len(path) != 11 or not path[6].isdigit():
    raise Exception('Unexpected url format: %s' % args.url)
  bot = path[4]
  build = path[6]
  commit = False
  if args.commit:
    commit = True

  exp_dir = os.path.join(d, 'exp' + ts_str)
  clean_dir(exp_dir)
  if not widen_bench_ranges(args.url, bot, repo_dir, exp_dir):
    print 'NO bench exceptions found! %s' % args.url
  elif not git_commit_expectations(
      repo_dir, exp_dir, bot, build, 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([exp_dir])


if __name__ == "__main__":
  main()