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 |