Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Run Performance Test Bisect Tool | 6 """Run Performance Test Bisect Tool |
| 7 | 7 |
| 8 This script is used by a trybot to run the src/tools/bisect-perf-regression.py | 8 This script is used by a trybot to run the src/tools/bisect-perf-regression.py |
| 9 script with the parameters specified in run-bisect-perf-regression.cfg. It will | 9 script with the parameters specified in run-bisect-perf-regression.cfg. It will |
| 10 check out a copy of the depot in a subdirectory 'bisect' of the working | 10 check out a copy of the depot in a subdirectory 'bisect' of the working |
| 11 directory provided, and run the bisect-perf-regression.py script there. | 11 directory provided, and run the bisect-perf-regression.py script there. |
| 12 | 12 |
| 13 """ | 13 """ |
| 14 | 14 |
| 15 import imp | 15 import imp |
| 16 import optparse | 16 import optparse |
| 17 import os | 17 import os |
| 18 import subprocess | 18 import subprocess |
| 19 import sys | 19 import sys |
| 20 import traceback | 20 import traceback |
| 21 | 21 |
| 22 import bisect_utils | |
| 23 bisect = imp.load_source('bisect-perf-regression', | |
| 24 os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), | |
| 25 'bisect-perf-regression.py')) | |
| 26 | |
| 27 | |
| 22 CROS_BOARD_ENV = 'BISECT_CROS_BOARD' | 28 CROS_BOARD_ENV = 'BISECT_CROS_BOARD' |
| 23 CROS_IP_ENV = 'BISECT_CROS_IP' | 29 CROS_IP_ENV = 'BISECT_CROS_IP' |
| 24 | 30 |
| 25 def LoadConfigFile(path_to_file): | 31 def _LoadConfigFile(path_to_file): |
| 26 """Attempts to load the file 'run-bisect-perf-regression.cfg' as a module | 32 """Attempts to load the specified config file as a module |
| 27 and grab the global config dict. | 33 and grab the global config dict. |
| 28 | 34 |
| 29 Args: | 35 Args: |
| 30 path_to_file: Path to the run-bisect-perf-regression.cfg file. | 36 path_to_file: Path to the file. |
| 31 | 37 |
| 32 Returns: | 38 Returns: |
| 33 The config dict which should be formatted as follows: | 39 The config dict which should be formatted as follows: |
| 34 {'command': string, 'good_revision': string, 'bad_revision': string | 40 {'command': string, 'good_revision': string, 'bad_revision': string |
| 35 'metric': string}. | 41 'metric': string, etc...}. |
| 36 Returns None on failure. | 42 Returns None on failure. |
| 37 """ | 43 """ |
| 38 try: | 44 try: |
| 39 local_vars = {} | 45 local_vars = {} |
| 40 execfile(os.path.join(path_to_file, 'run-bisect-perf-regression.cfg'), | 46 execfile(path_to_file, local_vars) |
| 41 local_vars) | |
| 42 | 47 |
| 43 return local_vars['config'] | 48 return local_vars['config'] |
| 44 except: | 49 except: |
| 45 print | 50 print |
| 46 traceback.print_exc() | 51 traceback.print_exc() |
| 47 print | 52 print |
| 48 return None | 53 return None |
| 49 | 54 |
| 50 | 55 |
| 51 def RunBisectionScript(config, working_directory, path_to_file, path_to_goma): | 56 def _GetGOMAExecutable(path_to_goma): |
|
tonyg
2013/10/17 01:01:32
I like the refactor so far, but you've got an easy
shatch
2013/10/17 21:59:08
Neat, didn't know about that. Planning on making a
| |
| 57 if os.name == 'nt': | |
| 58 return os.path.join(path_to_goma, 'goma_ctl.bat') | |
| 59 else: | |
| 60 return os.path.join(path_to_goma, 'goma_ctl.sh') | |
| 61 | |
| 62 | |
| 63 def _SetupAndStartGOMA(path_to_goma): | |
| 64 """Sets up GOMA and launches it. | |
| 65 | |
| 66 Args: | |
| 67 path_to_goma: Path to goma directory. | |
| 68 | |
| 69 Returns: | |
| 70 True if successful.""" | |
| 71 abs_path_to_goma = os.path.abspath(path_to_goma) | |
| 72 goma_file = _GetGOMAExecutable(abs_path_to_goma) | |
| 73 | |
| 74 if os.name == 'nt': | |
| 75 os.environ['CC'] = os.path.join(abs_path_to_goma, 'gomacc.exe') + ' cl.exe' | |
| 76 os.environ['CXX'] = os.path.join(abs_path_to_goma, 'gomacc.exe') + ' cl.exe' | |
| 77 else: | |
| 78 os.environ['PATH'] = os.pathsep.join([abs_path_to_goma, os.environ['PATH']]) | |
| 79 | |
| 80 # Sometimes goma is lingering around if something went bad on a previous | |
| 81 # run. Stop it before starting a new process. Can ignore the return code | |
| 82 # since it will return an error if it wasn't running. | |
| 83 _StopGOMA(path_to_goma) | |
| 84 | |
| 85 return not subprocess.call([goma_file, 'start']) | |
| 86 | |
| 87 | |
| 88 def _StopGOMA(path_to_goma): | |
| 89 abs_path_to_goma = os.path.abspath(path_to_goma) | |
| 90 goma_file = _GetGOMAExecutable(abs_path_to_goma) | |
| 91 subprocess.call([goma_file, 'stop']) | |
| 92 | |
| 93 | |
| 94 def _OutputFailedResults(text_to_print): | |
| 95 bisect_utils.OutputAnnotationStepStart('Results - Failed') | |
| 96 print | |
| 97 print text_to_print | |
| 98 print | |
| 99 bisect_utils.OutputAnnotationStepClosed() | |
| 100 | |
| 101 | |
| 102 def _CreateBisectOptionsFromConfig(config): | |
| 103 opts_dict = {} | |
| 104 opts_dict['command'] = config['command'] | |
| 105 opts_dict['metric'] = config['metric'] | |
| 106 | |
| 107 if config['repeat_count']: | |
| 108 opts_dict['repeat_test_count'] = int(config['repeat_count']) | |
| 109 | |
| 110 if config['truncate_percent']: | |
| 111 opts_dict['truncate_percent'] = int(config['truncate_percent']) | |
| 112 | |
| 113 if config['max_time_minutes']: | |
| 114 opts_dict['max_time_minutes'] = int(config['max_time_minutes']) | |
| 115 | |
| 116 if config.has_key('use_goma'): | |
| 117 opts_dict['use_goma'] = config['use_goma'] | |
| 118 | |
| 119 opts_dict['build_preference'] = 'ninja' | |
| 120 opts_dict['output_buildbot_annotations'] = True | |
| 121 | |
| 122 if '--browser=cros' in config['command']: | |
| 123 opts_dict['target_platform'] = 'cros' | |
| 124 | |
| 125 if os.environ[CROS_BOARD_ENV] and os.environ[CROS_IP_ENV]: | |
| 126 opts_dict['cros_board'] = os.environ[CROS_BOARD_ENV] | |
| 127 opts_dict['cros_remote_ip'] = os.environ[CROS_IP_ENV] | |
| 128 else: | |
| 129 raise RuntimeError('Cros build selected, but BISECT_CROS_IP or' | |
| 130 'BISECT_CROS_BOARD undefined.') | |
| 131 elif 'android' in config['command']: | |
| 132 opts_dict['target_platform'] = 'android' | |
| 133 | |
| 134 return bisect.BisectOptions.FromDict(opts_dict) | |
| 135 | |
| 136 | |
| 137 def _RunPerformancetest(config, path_to_file): | |
|
tonyg
2013/10/17 01:01:32
Capitalize Test?
shatch
2013/10/17 21:59:08
Done.
| |
| 138 # Bisect script expects to be run from src | |
| 139 os.chdir(os.path.join(path_to_file, '..')) | |
| 140 | |
| 141 bisect_utils.OutputAnnotationStepStart('Building With Patch') | |
| 142 | |
| 143 opts = _CreateBisectOptionsFromConfig(config) | |
| 144 b = bisect.BisectPerformanceMetrics(None, opts) | |
| 145 | |
| 146 if bisect_utils.RunGClient(['runhooks']): | |
| 147 raise RuntimeError('Failed to run gclient runhooks') | |
| 148 | |
| 149 if not b.BuildCurrentRevision('chromium'): | |
| 150 raise RuntimeError('Patched version failed to build.') | |
| 151 | |
| 152 bisect_utils.OutputAnnotationStepClosed() | |
| 153 bisect_utils.OutputAnnotationStepStart('Running With Patch') | |
| 154 | |
| 155 results_with_patch = b.RunPerformanceTestAndParseResults( | |
| 156 opts.command, opts.metric, reset_on_first_run=True) | |
| 157 | |
| 158 if results_with_patch[1]: | |
| 159 raise RuntimeError('Patched version failed to run performance test.') | |
| 160 | |
| 161 bisect_utils.OutputAnnotationStepClosed() | |
| 162 | |
| 163 bisect_utils.OutputAnnotationStepStart('Reverting Patch') | |
| 164 if bisect_utils.RunGClient(['revert']): | |
| 165 raise RuntimeError('Failed to run gclient runhooks') | |
| 166 bisect_utils.OutputAnnotationStepClosed() | |
| 167 | |
| 168 bisect_utils.OutputAnnotationStepStart('Building Without Patch') | |
| 169 | |
| 170 if bisect_utils.RunGClient(['runhooks']): | |
| 171 raise RuntimeError('Failed to run gclient runhooks') | |
| 172 | |
| 173 if not b.BuildCurrentRevision('chromium'): | |
| 174 raise RuntimeError('Unpatched version failed to build.') | |
| 175 | |
| 176 bisect_utils.OutputAnnotationStepClosed() | |
| 177 bisect_utils.OutputAnnotationStepStart('Running Without Patch') | |
| 178 | |
| 179 results_without_patch = b.RunPerformanceTestAndParseResults( | |
| 180 opts.command, opts.metric, upload_on_last_run=True) | |
| 181 | |
| 182 if results_without_patch[1]: | |
| 183 raise RuntimeError('Unpatched version failed to run performance test.') | |
| 184 | |
| 185 # Find the link to the cloud stored results file. | |
| 186 output = results_without_patch[2] | |
| 187 cloud_file_link = [t for t in output.splitlines() if 'View online at' in t] | |
|
tonyg
2013/10/17 01:01:32
Just realized a minor wrinkle. It won't be obvious
shatch
2013/10/17 21:59:08
Sure, I'll get a separate CL going to pass labels
| |
| 188 if cloud_file_link: | |
| 189 cloud_file_link = cloud_file_link[0] | |
| 190 else: | |
| 191 cloud_file_link = '' | |
| 192 | |
| 193 bisect_utils.OutputAnnotationStepClosed() | |
| 194 if cloud_file_link: | |
| 195 bisect_utils.OutputAnnotationStepStart('Results - %s' % cloud_file_link) | |
|
tonyg
2013/10/17 01:01:32
I think what we want is:
OutputAnnotationStepStar
shatch
2013/10/17 21:59:08
Done.
| |
| 196 else: | |
| 197 bisect_utils.OutputAnnotationStepStart('Results') | |
| 198 print ' %s %s %s' % (''.center(10, ' '), 'Mean'.center(20, ' '), | |
| 199 'Std. Error'.center(20, ' ')) | |
| 200 print ' %s %s %s' % ('Patch'.center(10, ' '), | |
| 201 ('%.02f' % results_with_patch[0]['mean']).center(20, ' '), | |
| 202 ('%.02f' % results_with_patch[0]['std_err']).center(20, ' ')) | |
| 203 print ' %s %s %s' % ('No Patch'.center(10, ' '), | |
| 204 ('%.02f' % results_without_patch[0]['mean']).center(20, ' '), | |
| 205 ('%.02f' % results_without_patch[0]['std_err']).center(20, ' ')) | |
| 206 print | |
| 207 print cloud_file_link | |
| 208 print | |
| 209 bisect_utils.OutputAnnotationStepClosed() | |
| 210 | |
| 211 | |
| 212 def _SetupAndRunPerformanceTest(config, path_to_file, path_to_goma): | |
| 213 """Attempts to build and run the current revision with and without the | |
| 214 current patch, with the parameters passed in. | |
| 215 | |
| 216 Args: | |
| 217 config: The config read from run-perf-test.cfg. | |
| 218 path_to_file: Path to the bisect-perf-regression.py script. | |
| 219 path_to_goma: Path to goma directory. | |
| 220 | |
| 221 Returns: | |
| 222 0 on success, otherwise 1. | |
| 223 """ | |
| 224 if path_to_goma: | |
| 225 config['use_goma'] = True | |
| 226 if not _SetupAndStartGOMA(path_to_goma): | |
| 227 _OutputFailedResults('Error: goma failed to start.') | |
| 228 return 1 | |
| 229 | |
| 230 cwd = os.getcwd() | |
| 231 try: | |
| 232 _RunPerformancetest(config, path_to_file) | |
| 233 return 0 | |
| 234 except RuntimeError, e: | |
| 235 bisect_utils.OutputAnnotationStepClosed() | |
| 236 _OutputFailedResults('Error: %s' % e.message) | |
| 237 return 1 | |
| 238 finally: | |
| 239 os.chdir(cwd) | |
| 240 if path_to_goma: | |
| 241 _StopGOMA(path_to_goma) | |
| 242 | |
| 243 | |
| 244 def _RunBisectionScript(config, working_directory, path_to_file, path_to_goma): | |
| 52 """Attempts to execute src/tools/bisect-perf-regression.py with the parameters | 245 """Attempts to execute src/tools/bisect-perf-regression.py with the parameters |
| 53 passed in. | 246 passed in. |
| 54 | 247 |
| 55 Args: | 248 Args: |
| 56 config: A dict containing the parameters to pass to the script. | 249 config: A dict containing the parameters to pass to the script. |
| 57 working_directory: A working directory to provide to the | 250 working_directory: A working directory to provide to the |
| 58 bisect-perf-regression.py script, where it will store it's own copy of | 251 bisect-perf-regression.py script, where it will store it's own copy of |
| 59 the depot. | 252 the depot. |
| 60 path_to_file: Path to the bisect-perf-regression.py script. | 253 path_to_file: Path to the bisect-perf-regression.py script. |
| 61 path_to_goma: Path to goma directory. | 254 path_to_goma: Path to goma directory. |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 91 cmd.extend(['--cros_remote_ip', os.environ[CROS_IP_ENV]]) | 284 cmd.extend(['--cros_remote_ip', os.environ[CROS_IP_ENV]]) |
| 92 else: | 285 else: |
| 93 print 'Error: Cros build selected, but BISECT_CROS_IP or'\ | 286 print 'Error: Cros build selected, but BISECT_CROS_IP or'\ |
| 94 'BISECT_CROS_BOARD undefined.' | 287 'BISECT_CROS_BOARD undefined.' |
| 95 print | 288 print |
| 96 return 1 | 289 return 1 |
| 97 | 290 |
| 98 if 'android' in config['command']: | 291 if 'android' in config['command']: |
| 99 cmd.extend(['--target_platform', 'android']) | 292 cmd.extend(['--target_platform', 'android']) |
| 100 | 293 |
| 101 goma_file = '' | |
| 102 if path_to_goma: | 294 if path_to_goma: |
| 103 path_to_goma = os.path.abspath(path_to_goma) | 295 if not _SetupAndStartGOMA(path_to_goma): |
| 104 | 296 print 'Error: goma failed to start.' |
| 105 if os.name == 'nt': | 297 print |
| 106 os.environ['CC'] = os.path.join(path_to_goma, 'gomacc.exe') + ' cl.exe' | 298 return 1 |
| 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 | 299 |
| 113 cmd.append('--use_goma') | 300 cmd.append('--use_goma') |
| 114 | 301 |
| 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] | 302 cmd = [str(c) for c in cmd] |
| 127 | 303 |
| 128 return_code = subprocess.call(cmd) | 304 return_code = subprocess.call(cmd) |
| 129 | 305 |
| 130 if path_to_goma: | 306 if path_to_goma: |
| 131 subprocess.call([goma_file, 'stop']) | 307 _StopGOMA(path_to_goma) |
| 132 | 308 |
| 133 if return_code: | 309 if return_code: |
| 134 print 'Error: bisect-perf-regression.py returned with error %d' %\ | 310 print 'Error: bisect-perf-regression.py returned with error %d' %\ |
| 135 return_code | 311 return_code |
| 136 print | 312 print |
| 137 | 313 |
| 138 return return_code | 314 return return_code |
| 139 | 315 |
| 140 | 316 |
| 141 def main(): | 317 def main(): |
| 142 | 318 |
| 143 usage = ('%prog [options] [-- chromium-options]\n' | 319 usage = ('%prog [options] [-- chromium-options]\n' |
| 144 'Used by a trybot to run the bisection script using the parameters' | 320 'Used by a trybot to run the bisection script using the parameters' |
| 145 ' provided in the run-bisect-perf-regression.cfg file.') | 321 ' provided in the run-bisect-perf-regression.cfg file.') |
| 146 | 322 |
| 147 parser = optparse.OptionParser(usage=usage) | 323 parser = optparse.OptionParser(usage=usage) |
| 148 parser.add_option('-w', '--working_directory', | 324 parser.add_option('-w', '--working_directory', |
| 149 type='str', | 325 type='str', |
| 150 help='A working directory to supply to the bisection ' | 326 help='A working directory to supply to the bisection ' |
| 151 'script, which will use it as the location to checkout ' | 327 'script, which will use it as the location to checkout ' |
| 152 'a copy of the chromium depot.') | 328 'a copy of the chromium depot.') |
| 153 parser.add_option('-p', '--path_to_goma', | 329 parser.add_option('-p', '--path_to_goma', |
| 154 type='str', | 330 type='str', |
| 155 help='Path to goma directory. If this is supplied, goma ' | 331 help='Path to goma directory. If this is supplied, goma ' |
| 156 'builds will be enabled.') | 332 'builds will be enabled.') |
| 157 (opts, args) = parser.parse_args() | 333 (opts, args) = parser.parse_args() |
| 158 | 334 |
| 159 if not opts.working_directory: | 335 path_to_current_directory = os.path.abspath(os.path.dirname(sys.argv[0])) |
| 160 print 'Error: missing required parameter: --working_directory' | 336 path_to_bisect_cfg = os.path.join(path_to_current_directory, |
| 161 print | 337 'run-bisect-perf-regression.cfg') |
| 162 parser.print_help() | |
| 163 return 1 | |
| 164 | 338 |
| 165 path_to_file = os.path.abspath(os.path.dirname(sys.argv[0])) | 339 config = _LoadConfigFile(path_to_bisect_cfg) |
| 166 | 340 |
| 167 config = LoadConfigFile(path_to_file) | 341 # Check if the config is empty |
| 168 if not config: | 342 config_has_values = [v for v in config.values() if v] |
| 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 | 343 |
| 174 return RunBisectionScript(config, opts.working_directory, path_to_file, | 344 if config and config_has_values: |
| 175 opts.path_to_goma) | 345 if not opts.working_directory: |
| 346 print 'Error: missing required parameter: --working_directory' | |
| 347 print | |
| 348 parser.print_help() | |
| 349 return 1 | |
| 350 | |
| 351 return _RunBisectionScript(config, opts.working_directory, | |
| 352 path_to_current_directory, opts.path_to_goma) | |
| 353 else: | |
| 354 path_to_perf_cfg = os.path.join( | |
| 355 os.path.abspath(os.path.dirname(sys.argv[0])), 'run-perf-test.cfg') | |
| 356 | |
| 357 config = _LoadConfigFile(path_to_perf_cfg) | |
| 358 | |
| 359 if config: | |
| 360 return _SetupAndRunPerformanceTest(config, path_to_current_directory, | |
| 361 opts.path_to_goma) | |
| 362 else: | |
| 363 print 'Error: Could not load config file. Double check your changes to '\ | |
| 364 'run-bisect-perf-regression.cfg for syntax errors.' | |
| 365 print | |
| 366 return 1 | |
| 176 | 367 |
| 177 | 368 |
| 178 if __name__ == '__main__': | 369 if __name__ == '__main__': |
| 179 sys.exit(main()) | 370 sys.exit(main()) |
| OLD | NEW |