| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Runs perf tests. | 5 """Runs perf tests. |
| 6 | 6 |
| 7 Our buildbot infrastructure requires each slave to run steps serially. | 7 Our buildbot infrastructure requires each slave to run steps serially. |
| 8 This is sub-optimal for android, where these steps can run independently on | 8 This is sub-optimal for android, where these steps can run independently on |
| 9 multiple connected devices. | 9 multiple connected devices. |
| 10 | 10 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 45 option: | 45 option: |
| 46 --device: the serial number to be passed to all adb commands. | 46 --device: the serial number to be passed to all adb commands. |
| 47 """ | 47 """ |
| 48 | 48 |
| 49 import collections | 49 import collections |
| 50 import datetime | 50 import datetime |
| 51 import json | 51 import json |
| 52 import logging | 52 import logging |
| 53 import os | 53 import os |
| 54 import pickle | 54 import pickle |
| 55 import shutil |
| 55 import sys | 56 import sys |
| 57 import tempfile |
| 56 import threading | 58 import threading |
| 57 import time | 59 import time |
| 58 | 60 |
| 59 from pylib import cmd_helper | 61 from pylib import cmd_helper |
| 60 from pylib import constants | 62 from pylib import constants |
| 61 from pylib import forwarder | 63 from pylib import forwarder |
| 62 from pylib.base import base_test_result | 64 from pylib.base import base_test_result |
| 63 from pylib.base import base_test_runner | 65 from pylib.base import base_test_runner |
| 64 from pylib.device import device_errors | 66 from pylib.device import device_errors |
| 65 | 67 |
| 66 | 68 |
| 67 def OutputJsonList(json_input, json_output): | 69 def OutputJsonList(json_input, json_output): |
| 68 with file(json_input, 'r') as i: | 70 with file(json_input, 'r') as i: |
| 69 all_steps = json.load(i) | 71 all_steps = json.load(i) |
| 70 step_names = all_steps['steps'].keys() | 72 step_names = all_steps['steps'].keys() |
| 71 with file(json_output, 'w') as o: | 73 with file(json_output, 'w') as o: |
| 72 o.write(json.dumps(step_names)) | 74 o.write(json.dumps(step_names)) |
| 73 return 0 | 75 return 0 |
| 74 | 76 |
| 75 | 77 |
| 78 def OutputChartjson(test_name, json_file_name): |
| 79 file_name = os.path.join(constants.PERF_OUTPUT_DIR, test_name) |
| 80 with file(file_name, 'r') as f: |
| 81 persisted_result = pickle.load(f) |
| 82 with open(json_file_name, 'w') as o: |
| 83 o.write(persisted_result['chartjson']) |
| 84 |
| 85 |
| 76 def PrintTestOutput(test_name): | 86 def PrintTestOutput(test_name): |
| 77 """Helper method to print the output of previously executed test_name. | 87 """Helper method to print the output of previously executed test_name. |
| 78 | 88 |
| 79 Args: | 89 Args: |
| 80 test_name: name of the test that has been previously executed. | 90 test_name: name of the test that has been previously executed. |
| 81 | 91 |
| 82 Returns: | 92 Returns: |
| 83 exit code generated by the test step. | 93 exit code generated by the test step. |
| 84 """ | 94 """ |
| 85 file_name = os.path.join(constants.PERF_OUTPUT_DIR, test_name) | 95 file_name = os.path.join(constants.PERF_OUTPUT_DIR, test_name) |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 161 max_shards: the maximum shard index. | 171 max_shards: the maximum shard index. |
| 162 tests: a dict mapping test_name to command. | 172 tests: a dict mapping test_name to command. |
| 163 flaky_tests: a list of flaky test_name. | 173 flaky_tests: a list of flaky test_name. |
| 164 """ | 174 """ |
| 165 super(TestRunner, self).__init__(device, None, 'Release') | 175 super(TestRunner, self).__init__(device, None, 'Release') |
| 166 self._options = test_options | 176 self._options = test_options |
| 167 self._shard_index = shard_index | 177 self._shard_index = shard_index |
| 168 self._max_shard = max_shard | 178 self._max_shard = max_shard |
| 169 self._tests = tests | 179 self._tests = tests |
| 170 self._flaky_tests = flaky_tests | 180 self._flaky_tests = flaky_tests |
| 181 self._output_dir = None |
| 171 | 182 |
| 172 @staticmethod | 183 @staticmethod |
| 173 def _IsBetter(result): | 184 def _IsBetter(result): |
| 174 if result['actual_exit_code'] == 0: | 185 if result['actual_exit_code'] == 0: |
| 175 return True | 186 return True |
| 176 pickled = os.path.join(constants.PERF_OUTPUT_DIR, | 187 pickled = os.path.join(constants.PERF_OUTPUT_DIR, |
| 177 result['name']) | 188 result['name']) |
| 178 if not os.path.exists(pickled): | 189 if not os.path.exists(pickled): |
| 179 return True | 190 return True |
| 180 with file(pickled, 'r') as f: | 191 with file(pickled, 'r') as f: |
| (...skipping 10 matching lines...) Expand all Loading... |
| 191 def _CheckDeviceAffinity(self, test_name): | 202 def _CheckDeviceAffinity(self, test_name): |
| 192 """Returns True if test_name has affinity for this shard.""" | 203 """Returns True if test_name has affinity for this shard.""" |
| 193 affinity = (self._tests['steps'][test_name]['device_affinity'] % | 204 affinity = (self._tests['steps'][test_name]['device_affinity'] % |
| 194 self._max_shard) | 205 self._max_shard) |
| 195 if self._shard_index == affinity: | 206 if self._shard_index == affinity: |
| 196 return True | 207 return True |
| 197 logging.info('Skipping %s on %s (affinity is %s, device is %s)', | 208 logging.info('Skipping %s on %s (affinity is %s, device is %s)', |
| 198 test_name, self.device_serial, affinity, self._shard_index) | 209 test_name, self.device_serial, affinity, self._shard_index) |
| 199 return False | 210 return False |
| 200 | 211 |
| 212 def _CleanupOutputDirectory(self): |
| 213 if self._output_dir: |
| 214 shutil.rmtree(self._output_dir, ignore_errors=True) |
| 215 self._output_dir = None |
| 216 |
| 217 def _ReadChartjsonOutput(self): |
| 218 if not self._output_dir: |
| 219 return '' |
| 220 |
| 221 json_output_path = os.path.join(self._output_dir, 'results-chart.json') |
| 222 with open(json_output_path) as f: |
| 223 return f.read() |
| 224 |
| 201 def _LaunchPerfTest(self, test_name): | 225 def _LaunchPerfTest(self, test_name): |
| 202 """Runs a perf test. | 226 """Runs a perf test. |
| 203 | 227 |
| 204 Args: | 228 Args: |
| 205 test_name: the name of the test to be executed. | 229 test_name: the name of the test to be executed. |
| 206 | 230 |
| 207 Returns: | 231 Returns: |
| 208 A tuple containing (Output, base_test_result.ResultType) | 232 A tuple containing (Output, base_test_result.ResultType) |
| 209 """ | 233 """ |
| 210 if not self._CheckDeviceAffinity(test_name): | 234 if not self._CheckDeviceAffinity(test_name): |
| 211 return '', base_test_result.ResultType.PASS | 235 return '', base_test_result.ResultType.PASS |
| 212 | 236 |
| 213 try: | 237 try: |
| 214 logging.warning('Unmapping device ports') | 238 logging.warning('Unmapping device ports') |
| 215 forwarder.Forwarder.UnmapAllDevicePorts(self.device) | 239 forwarder.Forwarder.UnmapAllDevicePorts(self.device) |
| 216 self.device.old_interface.RestartAdbdOnDevice() | 240 self.device.old_interface.RestartAdbdOnDevice() |
| 217 except Exception as e: | 241 except Exception as e: |
| 218 logging.error('Exception when tearing down device %s', e) | 242 logging.error('Exception when tearing down device %s', e) |
| 219 | 243 |
| 220 cmd = ('%s --device %s' % | 244 cmd = ('%s --device %s' % |
| 221 (self._tests['steps'][test_name]['cmd'], | 245 (self._tests['steps'][test_name]['cmd'], |
| 222 self.device_serial)) | 246 self.device_serial)) |
| 247 |
| 248 if self._options.collect_chartjson_data: |
| 249 self._output_dir = tempfile.mkdtemp() |
| 250 cmd = cmd + ' --output-dir=%s' % self._output_dir |
| 251 |
| 223 logging.info('%s : %s', test_name, cmd) | 252 logging.info('%s : %s', test_name, cmd) |
| 224 start_time = datetime.datetime.now() | 253 start_time = datetime.datetime.now() |
| 225 | 254 |
| 226 timeout = 5400 | 255 timeout = 5400 |
| 227 if self._options.no_timeout: | 256 if self._options.no_timeout: |
| 228 timeout = None | 257 timeout = None |
| 229 full_cmd = cmd | 258 full_cmd = cmd |
| 230 if self._options.dry_run: | 259 if self._options.dry_run: |
| 231 full_cmd = 'echo %s' % cmd | 260 full_cmd = 'echo %s' % cmd |
| 232 | 261 |
| 233 logfile = sys.stdout | 262 logfile = sys.stdout |
| 234 if self._options.single_step: | 263 if self._options.single_step: |
| 235 # Just print a heart-beat so that the outer buildbot scripts won't timeout | 264 # Just print a heart-beat so that the outer buildbot scripts won't timeout |
| 236 # without response. | 265 # without response. |
| 237 logfile = _HeartBeatLogger() | 266 logfile = _HeartBeatLogger() |
| 238 cwd = os.path.abspath(constants.DIR_SOURCE_ROOT) | 267 cwd = os.path.abspath(constants.DIR_SOURCE_ROOT) |
| 239 if full_cmd.startswith('src/'): | 268 if full_cmd.startswith('src/'): |
| 240 cwd = os.path.abspath(os.path.join(constants.DIR_SOURCE_ROOT, os.pardir)) | 269 cwd = os.path.abspath(os.path.join(constants.DIR_SOURCE_ROOT, os.pardir)) |
| 241 try: | 270 try: |
| 242 exit_code, output = cmd_helper.GetCmdStatusAndOutputWithTimeout( | 271 exit_code, output = cmd_helper.GetCmdStatusAndOutputWithTimeout( |
| 243 full_cmd, timeout, cwd=cwd, shell=True, logfile=logfile) | 272 full_cmd, timeout, cwd=cwd, shell=True, logfile=logfile) |
| 273 json_output = self._ReadChartjsonOutput() |
| 244 except cmd_helper.TimeoutError as e: | 274 except cmd_helper.TimeoutError as e: |
| 245 exit_code = -1 | 275 exit_code = -1 |
| 246 output = str(e) | 276 output = str(e) |
| 277 json_output = '' |
| 247 finally: | 278 finally: |
| 279 self._CleanupOutputDirectory() |
| 248 if self._options.single_step: | 280 if self._options.single_step: |
| 249 logfile.stop() | 281 logfile.stop() |
| 250 end_time = datetime.datetime.now() | 282 end_time = datetime.datetime.now() |
| 251 if exit_code is None: | 283 if exit_code is None: |
| 252 exit_code = -1 | 284 exit_code = -1 |
| 253 logging.info('%s : exit_code=%d in %d secs at %s', | 285 logging.info('%s : exit_code=%d in %d secs at %s', |
| 254 test_name, exit_code, (end_time - start_time).seconds, | 286 test_name, exit_code, (end_time - start_time).seconds, |
| 255 self.device_serial) | 287 self.device_serial) |
| 256 | 288 |
| 257 if exit_code == 0: | 289 if exit_code == 0: |
| (...skipping 12 matching lines...) Expand all Loading... |
| 270 if test_name in self._flaky_tests: | 302 if test_name in self._flaky_tests: |
| 271 # The exit_code is used at the second stage when printing the | 303 # The exit_code is used at the second stage when printing the |
| 272 # test output. If the test is flaky, force to "0" to get that step green | 304 # test output. If the test is flaky, force to "0" to get that step green |
| 273 # whilst still gathering data to the perf dashboards. | 305 # whilst still gathering data to the perf dashboards. |
| 274 # The result_type is used by the test_dispatcher to retry the test. | 306 # The result_type is used by the test_dispatcher to retry the test. |
| 275 exit_code = 0 | 307 exit_code = 0 |
| 276 | 308 |
| 277 persisted_result = { | 309 persisted_result = { |
| 278 'name': test_name, | 310 'name': test_name, |
| 279 'output': output, | 311 'output': output, |
| 312 'chartjson': json_output, |
| 280 'exit_code': exit_code, | 313 'exit_code': exit_code, |
| 281 'actual_exit_code': actual_exit_code, | 314 'actual_exit_code': actual_exit_code, |
| 282 'result_type': result_type, | 315 'result_type': result_type, |
| 283 'total_time': (end_time - start_time).seconds, | 316 'total_time': (end_time - start_time).seconds, |
| 284 'device': self.device_serial, | 317 'device': self.device_serial, |
| 285 'cmd': cmd, | 318 'cmd': cmd, |
| 286 } | 319 } |
| 287 self._SaveResult(persisted_result) | 320 self._SaveResult(persisted_result) |
| 288 | 321 |
| 289 return (output, result_type) | 322 return (output, result_type) |
| 290 | 323 |
| 291 def RunTest(self, test_name): | 324 def RunTest(self, test_name): |
| 292 """Run a perf test on the device. | 325 """Run a perf test on the device. |
| 293 | 326 |
| 294 Args: | 327 Args: |
| 295 test_name: String to use for logging the test result. | 328 test_name: String to use for logging the test result. |
| 296 | 329 |
| 297 Returns: | 330 Returns: |
| 298 A tuple of (TestRunResults, retry). | 331 A tuple of (TestRunResults, retry). |
| 299 """ | 332 """ |
| 300 _, result_type = self._LaunchPerfTest(test_name) | 333 _, result_type = self._LaunchPerfTest(test_name) |
| 301 results = base_test_result.TestRunResults() | 334 results = base_test_result.TestRunResults() |
| 302 results.AddResult(base_test_result.BaseTestResult(test_name, result_type)) | 335 results.AddResult(base_test_result.BaseTestResult(test_name, result_type)) |
| 303 retry = None | 336 retry = None |
| 304 if not results.DidRunPass(): | 337 if not results.DidRunPass(): |
| 305 retry = test_name | 338 retry = test_name |
| 306 return results, retry | 339 return results, retry |
| OLD | NEW |