1#!/usr/bin/env python
2# Copyright (c) 2013 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"""Run Performance Test Bisect Tool
7
8This script is used by a trybot to run the src/tools/bisect-perf-regression.py
9script with the parameters specified in run-bisect-perf-regression.cfg. It will
10check out a copy of the depot in a subdirectory 'bisect' of the working
11directory provided, and run the bisect-perf-regression.py script there.
12
13"""
14
15import imp
16import optparse
17import os
18import subprocess
19import sys
20import traceback
21
22CROS_BOARD_ENV = 'BISECT_CROS_BOARD'
23CROS_IP_ENV = 'BISECT_CROS_IP'
24
25def LoadConfigFile(path_to_file):
26  """Attempts to load the file 'run-bisect-perf-regression.cfg' as a module
27  and grab the global config dict.
28
29  Args:
30    path_to_file: Path to the run-bisect-perf-regression.cfg file.
31
32  Returns:
33    The config dict which should be formatted as follows:
34    {'command': string, 'good_revision': string, 'bad_revision': string
35     'metric': string}.
36    Returns None on failure.
37  """
38  try:
39    local_vars = {}
40    execfile(os.path.join(path_to_file, 'run-bisect-perf-regression.cfg'),
41             local_vars)
42
43    return local_vars['config']
44  except:
45    print
46    traceback.print_exc()
47    print
48    return None
49
50
51def RunBisectionScript(config, working_directory, path_to_file, path_to_goma):
52  """Attempts to execute src/tools/bisect-perf-regression.py with the parameters
53  passed in.
54
55  Args:
56    config: A dict containing the parameters to pass to the script.
57    working_directory: A working directory to provide to the
58      bisect-perf-regression.py script, where it will store it's own copy of
59      the depot.
60    path_to_file: Path to the bisect-perf-regression.py script.
61    path_to_goma: Path to goma directory.
62
63  Returns:
64    0 on success, otherwise 1.
65  """
66
67  cmd = ['python', os.path.join(path_to_file, 'bisect-perf-regression.py'),
68         '-c', config['command'],
69         '-g', config['good_revision'],
70         '-b', config['bad_revision'],
71         '-m', config['metric'],
72         '--working_directory', working_directory,
73         '--output_buildbot_annotations']
74
75  if config['repeat_count']:
76    cmd.extend(['-r', config['repeat_count']])
77
78  if config['truncate_percent']:
79    cmd.extend(['-t', config['truncate_percent']])
80
81  if config['max_time_minutes']:
82    cmd.extend(['--repeat_test_max_time', config['max_time_minutes']])
83
84  cmd.extend(['--build_preference', 'ninja'])
85
86  if '--browser=cros' in config['command']:
87    cmd.extend(['--target_platform', 'cros'])
88
89    if os.environ[CROS_BOARD_ENV] and os.environ[CROS_IP_ENV]:
90      cmd.extend(['--cros_board', os.environ[CROS_BOARD_ENV]])
91      cmd.extend(['--cros_remote_ip', os.environ[CROS_IP_ENV]])
92    else:
93      print 'Error: Cros build selected, but BISECT_CROS_IP or'\
94            'BISECT_CROS_BOARD undefined.'
95      print
96      return 1
97
98  if '--browser=android' in config['command']:
99    cmd.extend(['--target_platform', 'android'])
100
101  goma_file = ''
102  if path_to_goma:
103    path_to_goma = os.path.abspath(path_to_goma)
104
105    if os.name == 'nt':
106      os.environ['CC'] = os.path.join(path_to_goma, 'gomacc.exe') + ' cl.exe'
107      os.environ['CXX'] = os.path.join(path_to_goma, 'gomacc.exe') + ' cl.exe'
108      goma_file = os.path.join(path_to_goma, 'goma_ctl.bat')
109    else:
110      os.environ['PATH'] = os.pathsep.join([path_to_goma, os.environ['PATH']])
111      goma_file = os.path.join(path_to_goma, 'goma_ctl.sh')
112
113    cmd.append('--use_goma')
114
115    # Sometimes goma is lingering around if something went bad on a previous
116    # run. Stop it before starting a new process. Can ignore the return code
117    # since it will return an error if it wasn't running.
118    subprocess.call([goma_file, 'stop'])
119
120    return_code = subprocess.call([goma_file, 'start'])
121    if return_code:
122      print 'Error: goma failed to start.'
123      print
124      return return_code
125
126  cmd = [str(c) for c in cmd]
127
128  return_code = subprocess.call(cmd)
129
130  if path_to_goma:
131    subprocess.call([goma_file, 'stop'])
132
133  if return_code:
134    print 'Error: bisect-perf-regression.py returned with error %d' %\
135        return_code
136    print
137
138  return return_code
139
140
141def main():
142
143  usage = ('%prog [options] [-- chromium-options]\n'
144           'Used by a trybot to run the bisection script using the parameters'
145           ' provided in the run-bisect-perf-regression.cfg file.')
146
147  parser = optparse.OptionParser(usage=usage)
148  parser.add_option('-w', '--working_directory',
149                    type='str',
150                    help='A working directory to supply to the bisection '
151                    'script, which will use it as the location to checkout '
152                    'a copy of the chromium depot.')
153  parser.add_option('-p', '--path_to_goma',
154                    type='str',
155                    help='Path to goma directory. If this is supplied, goma '
156                    'builds will be enabled.')
157  (opts, args) = parser.parse_args()
158
159  if not opts.working_directory:
160    print 'Error: missing required parameter: --working_directory'
161    print
162    parser.print_help()
163    return 1
164
165  path_to_file = os.path.abspath(os.path.dirname(sys.argv[0]))
166
167  config = LoadConfigFile(path_to_file)
168  if not config:
169    print 'Error: Could not load config file. Double check your changes to '\
170          'run-bisect-perf-regression.cfg for syntax errors.'
171    print
172    return 1
173
174  return RunBisectionScript(config, opts.working_directory, path_to_file,
175                            opts.path_to_goma)
176
177
178if __name__ == '__main__':
179  sys.exit(main())
180