Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1201)

Side by Side Diff: build/android/pylib/local/device/local_device_perf_test_run.py

Issue 2200193002: [Android] Add ability to run deviceless tests on hosts without devices for perf tests. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: [Android] Add ability to run deviceless tests on hosts without devices for perf tests. Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2016 The Chromium Authors. All rights reserved. 1 # Copyright 2016 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 import io 5 import io
6 import json 6 import json
7 import logging 7 import logging
8 import os 8 import os
9 import pickle 9 import pickle
10 import shutil 10 import shutil
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
54 self._timer.cancel() 54 self._timer.cancel()
55 self._running = False 55 self._running = False
56 56
57 def _LogMessage(self): 57 def _LogMessage(self):
58 logging.info('Currently working on test %s', self._shard.current_test) 58 logging.info('Currently working on test %s', self._shard.current_test)
59 self._timer = threading.Timer(self._wait_time, self._LogMessage) 59 self._timer = threading.Timer(self._wait_time, self._LogMessage)
60 self._timer.start() 60 self._timer.start()
61 61
62 62
63 class TestShard(object): 63 class TestShard(object):
64 def __init__( 64 def __init__(self, env, test_instance, tests, retries=3, timeout=None):
65 self, env, test_instance, device, index, tests, retries=3, timeout=None): 65 logging.info('Create shard for the following tests:')
66 logging.info('Create shard %s for device %s to run the following tests:',
67 index, device)
68 for t in tests: 66 for t in tests:
69 logging.info(' %s', t) 67 logging.info(' %s', t)
70 self._battery = battery_utils.BatteryUtils(device)
71 self._current_test = None 68 self._current_test = None
72 self._device = device
73 self._env = env 69 self._env = env
74 self._index = index 70 self._heart_beat = HeartBeat(self)
71 self._index = -1
perezju 2016/08/04 15:10:50 can we use "None" for no device needed?
75 self._output_dir = None 72 self._output_dir = None
76 self._retries = retries 73 self._retries = retries
77 self._test_instance = test_instance 74 self._test_instance = test_instance
78 self._tests = tests 75 self._tests = tests
79 self._timeout = timeout 76 self._timeout = timeout
80 self._heart_beat = HeartBeat(self)
81
82 @local_device_environment.handle_shard_failures
83 def RunTestsOnShard(self):
84 results = base_test_result.TestRunResults()
85 for test in self._tests:
86 tries_left = self._retries
87 result_type = None
88 while (result_type != base_test_result.ResultType.PASS
89 and tries_left > 0):
90 try:
91 self._TestSetUp(test)
92 result_type = self._RunSingleTest(test)
93 except device_errors.CommandTimeoutError:
94 result_type = base_test_result.ResultType.TIMEOUT
95 except device_errors.CommandFailedError:
96 logging.exception('Exception when executing %s.', test)
97 result_type = base_test_result.ResultType.FAIL
98 finally:
99 self._TestTearDown()
100 if result_type != base_test_result.ResultType.PASS:
101 try:
102 device_recovery.RecoverDevice(self._device, self._env.blacklist)
103 except device_errors.CommandTimeoutError:
104 logging.exception(
105 'Device failed to recover after failing %s.', test)
106 tries_left = tries_left - 1
107
108 results.AddResult(base_test_result.BaseTestResult(test, result_type))
109 return results
110
111 def _TestSetUp(self, test):
112 if not self._device.IsOnline():
113 msg = 'Device %s is unresponsive.' % str(self._device)
114 raise device_errors.DeviceUnreachableError(msg)
115
116 logging.info('Charge level: %s%%',
117 str(self._battery.GetBatteryInfo().get('level')))
118 if self._test_instance.min_battery_level:
119 self._battery.ChargeDeviceToLevel(self._test_instance.min_battery_level)
120
121 logging.info('temperature: %s (0.1 C)',
122 str(self._battery.GetBatteryInfo().get('temperature')))
123 if self._test_instance.max_battery_temp:
124 self._battery.LetBatteryCoolToTemperature(
125 self._test_instance.max_battery_temp)
126
127 if not self._device.IsScreenOn():
128 self._device.SetScreen(True)
129
130 if (self._test_instance.collect_chartjson_data
131 or self._tests[test].get('archive_output_dir')):
132 self._output_dir = tempfile.mkdtemp()
133
134 self._current_test = test
135 self._heart_beat.Start()
136 77
137 def _RunSingleTest(self, test): 78 def _RunSingleTest(self, test):
138 self._test_instance.WriteBuildBotJson(self._output_dir) 79 self._test_instance.WriteBuildBotJson(self._output_dir)
139 80
140 timeout = self._tests[test].get('timeout', self._timeout) 81 timeout = self._tests[test].get('timeout', self._timeout)
141 cmd = self._CreateCmd(test) 82 cmd = self._CreateCmd(test)
142 cwd = os.path.abspath(host_paths.DIR_SOURCE_ROOT) 83 cwd = os.path.abspath(host_paths.DIR_SOURCE_ROOT)
143 84
144 logging.debug("Running %s with command '%s' on shard %d with timeout %d", 85 logging.debug("Running %s with command '%s' on shard %d with timeout %d",
145 test, cmd, self._index, timeout) 86 test, cmd, self._index, timeout)
(...skipping 12 matching lines...) Expand all
158 end_time = time.time() 99 end_time = time.time()
159 exit_code = -1 100 exit_code = -1
160 output = e.output 101 output = e.output
161 json_output = '' 102 json_output = ''
162 result_type = base_test_result.ResultType.TIMEOUT 103 result_type = base_test_result.ResultType.TIMEOUT
163 104
164 return self._ProcessTestResult(test, cmd, start_time, end_time, exit_code, 105 return self._ProcessTestResult(test, cmd, start_time, end_time, exit_code,
165 output, json_output, result_type) 106 output, json_output, result_type)
166 107
167 def _CreateCmd(self, test): 108 def _CreateCmd(self, test):
168 cmd = '%s --device %s' % (self._tests[test]['cmd'], str(self._device)) 109 cmd = []
110 if self._test_instance.dry_run:
111 cmd.extend(['echo'])
112 cmd.extend([self._tests[test]['cmd']])
perezju 2016/08/04 15:10:50 nit: cmd.append if you're just adding one thing
169 if self._output_dir: 113 if self._output_dir:
170 cmd = cmd + ' --output-dir=%s' % self._output_dir 114 cmd.append('--output-dir=%s' % self._output_dir)
171 if self._test_instance.dry_run: 115 return ' '.join(self._ExtendCmd(cmd))
172 cmd = 'echo %s' % cmd 116
117 def _ExtendCmd(self, cmd): # pylint: disable=no-self-use
173 return cmd 118 return cmd
174 119
120 def _LogTestExit(self, test, exit_code, # pylint: disable=no-self-use
121 duration):
122 logging.info('%s : exit_code=%d in %d secs.', test, exit_code, duration)
123
124 def _ExtendPersistedResult(self, # pylint: disable=no-self-use
125 persisted_result):
126 pass
127
175 def _ProcessTestResult(self, test, cmd, start_time, end_time, exit_code, 128 def _ProcessTestResult(self, test, cmd, start_time, end_time, exit_code,
176 output, json_output, result_type): 129 output, json_output, result_type):
177 if exit_code is None: 130 if exit_code is None:
178 exit_code = -1 131 exit_code = -1
179 logging.info('%s : exit_code=%d in %d secs on device %s', 132
180 test, exit_code, end_time - start_time, 133 self._LogTestExit(test, exit_code, end_time - start_time)
181 str(self._device))
182 134
183 actual_exit_code = exit_code 135 actual_exit_code = exit_code
184 if (self._test_instance.flaky_steps 136 if (self._test_instance.flaky_steps
185 and test in self._test_instance.flaky_steps): 137 and test in self._test_instance.flaky_steps):
186 exit_code = 0 138 exit_code = 0
187 archive_bytes = (self._ArchiveOutputDir() 139 archive_bytes = (self._ArchiveOutputDir()
188 if self._tests[test].get('archive_output_dir') 140 if self._tests[test].get('archive_output_dir')
189 else None) 141 else None)
190 persisted_result = { 142 persisted_result = {
191 'name': test, 143 'name': test,
192 'output': [output], 144 'output': [output],
193 'chartjson': json_output, 145 'chartjson': json_output,
194 'archive_bytes': archive_bytes, 146 'archive_bytes': archive_bytes,
195 'exit_code': exit_code, 147 'exit_code': exit_code,
196 'actual_exit_code': actual_exit_code, 148 'actual_exit_code': actual_exit_code,
197 'result_type': result_type, 149 'result_type': result_type,
198 'start_time': start_time, 150 'start_time': start_time,
199 'end_time': end_time, 151 'end_time': end_time,
200 'total_time': end_time - start_time, 152 'total_time': end_time - start_time,
201 'device': str(self._device),
202 'cmd': cmd, 153 'cmd': cmd,
203 } 154 }
155 self._ExtendPersistedResult(persisted_result)
204 self._SaveResult(persisted_result) 156 self._SaveResult(persisted_result)
205 return result_type 157 return result_type
206 158
207 def _ArchiveOutputDir(self): 159 def _ArchiveOutputDir(self):
208 """Archive all files in the output dir, and return as compressed bytes.""" 160 """Archive all files in the output dir, and return as compressed bytes."""
209 with io.BytesIO() as archive: 161 with io.BytesIO() as archive:
210 with zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED) as contents: 162 with zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED) as contents:
211 num_files = 0 163 num_files = 0
212 for absdir, _, files in os.walk(self._output_dir): 164 for absdir, _, files in os.walk(self._output_dir):
213 reldir = os.path.relpath(absdir, self._output_dir) 165 reldir = os.path.relpath(absdir, self._output_dir)
(...skipping 12 matching lines...) Expand all
226 @staticmethod 178 @staticmethod
227 def _SaveResult(result): 179 def _SaveResult(result):
228 pickled = os.path.join(constants.PERF_OUTPUT_DIR, result['name']) 180 pickled = os.path.join(constants.PERF_OUTPUT_DIR, result['name'])
229 if os.path.exists(pickled): 181 if os.path.exists(pickled):
230 with file(pickled, 'r') as f: 182 with file(pickled, 'r') as f:
231 previous = pickle.loads(f.read()) 183 previous = pickle.loads(f.read())
232 result['output'] = previous['output'] + result['output'] 184 result['output'] = previous['output'] + result['output']
233 with file(pickled, 'w') as f: 185 with file(pickled, 'w') as f:
234 f.write(pickle.dumps(result)) 186 f.write(pickle.dumps(result))
235 187
188 @property
189 def current_test(self):
190 return self._current_test
191
192
193 class DeviceTestShard(TestShard):
194 def __init__(
195 self, env, test_instance, device, index, tests, retries=3, timeout=None):
196 super(DeviceTestShard, self).__init__(
197 env, test_instance, tests, retries, timeout)
198 self._battery = battery_utils.BatteryUtils(device) if device else None
199 self._device = device
200 self._index = index
201
202 @local_device_environment.handle_shard_failures
203 def RunTestsOnShard(self):
204 results = base_test_result.TestRunResults()
205 for test in self._tests:
206 tries_left = self._retries
207 result_type = None
208 while (result_type != base_test_result.ResultType.PASS
209 and tries_left > 0):
210 try:
211 self._TestSetUp(test)
212 result_type = self._RunSingleTest(test)
213 except device_errors.CommandTimeoutError:
214 result_type = base_test_result.ResultType.TIMEOUT
215 except device_errors.CommandFailedError:
216 logging.exception('Exception when executing %s.', test)
217 result_type = base_test_result.ResultType.FAIL
218 finally:
219 self._TestTearDown()
220 if result_type != base_test_result.ResultType.PASS:
221 try:
222 device_recovery.RecoverDevice(self._device, self._env.blacklist)
223 except device_errors.CommandTimeoutError:
224 logging.exception(
225 'Device failed to recover after failing %s.', test)
226 tries_left = tries_left - 1
227
228 results.AddResult(base_test_result.BaseTestResult(test, result_type))
229 return results
230
231 def _LogTestExit(self, test, exit_code, duration):
232 logging.info('%s : exit_code=%d in %d secs on device %s',
233 test, exit_code, duration, str(self._device))
234
235 def _TestSetUp(self, test):
236 if not self._device.IsOnline():
237 msg = 'Device %s is unresponsive.' % str(self._device)
238 raise device_errors.DeviceUnreachableError(msg)
239
240 logging.info('Charge level: %s%%',
241 str(self._battery.GetBatteryInfo().get('level')))
242 if self._test_instance.min_battery_level:
243 self._battery.ChargeDeviceToLevel(self._test_instance.min_battery_level)
244
245 logging.info('temperature: %s (0.1 C)',
246 str(self._battery.GetBatteryInfo().get('temperature')))
247 if self._test_instance.max_battery_temp:
248 self._battery.LetBatteryCoolToTemperature(
249 self._test_instance.max_battery_temp)
250
251 if not self._device.IsScreenOn():
252 self._device.SetScreen(True)
253
254 if (self._test_instance.collect_chartjson_data
255 or self._tests[test].get('archive_output_dir')):
256 self._output_dir = tempfile.mkdtemp()
257
258 self._current_test = test
259 self._heart_beat.Start()
260
261 def _ExtendCmd(self, cmd):
262 cmd.extend(['--device=%s' % str(self._device)])
263 return cmd
264
265 def _ExtendPersistedResult(self, persisted_result):
266 persisted_result['host_test'] = False
267 persisted_result['device'] = str(self._device)
268
236 def _TestTearDown(self): 269 def _TestTearDown(self):
237 if self._output_dir: 270 if self._output_dir:
238 shutil.rmtree(self._output_dir, ignore_errors=True) 271 shutil.rmtree(self._output_dir, ignore_errors=True)
239 self._output_dir = None 272 self._output_dir = None
240 try: 273 try:
241 logging.info('Unmapping device ports for %s.', self._device) 274 logging.info('Unmapping device ports for %s.', self._device)
242 forwarder.Forwarder.UnmapAllDevicePorts(self._device) 275 forwarder.Forwarder.UnmapAllDevicePorts(self._device)
243 except Exception: # pylint: disable=broad-except 276 except Exception: # pylint: disable=broad-except
244 logging.exception('Exception when resetting ports.') 277 logging.exception('Exception when resetting ports.')
245 finally: 278 finally:
246 self._heart_beat.Stop() 279 self._heart_beat.Stop()
247 self._current_test = None 280 self._current_test = None
248 281
249 @property 282
250 def current_test(self): 283 class HostTestShard(TestShard):
251 return self._current_test 284 def __init__(self, env, test_instance, tests, retries=3, timeout=None):
285 super(HostTestShard, self).__init__(
286 env, test_instance, tests, retries, timeout)
287
288 @local_device_environment.handle_shard_failures
289 def RunTestsOnShard(self):
290 results = base_test_result.TestRunResults()
291 for test in self._tests:
292 tries_left = self._retries
293 result_type = None
294 while (result_type != base_test_result.ResultType.PASS
295 and tries_left > 0):
296 try:
297 self._TestSetUp(test)
298 result_type = self._RunSingleTest(test)
299 finally:
300 self._TestTearDown()
301 results.AddResult(base_test_result.BaseTestResult(test, result_type))
302 return results
303
304 def _TestSetUp(self, test):
305 if (self._test_instance.collect_chartjson_data
306 or self._tests[test].get('archive_output_dir')):
307 self._output_dir = tempfile.mkdtemp()
308 self._current_test = test
309 self._heart_beat.Start()
310
311 def _ExtendPersistedResult(self, persisted_result):
312 persisted_result['host_test'] = True
313
314 def _TestTearDown(self):
315 if self._output_dir:
316 shutil.rmtree(self._output_dir, ignore_errors=True)
317 self._output_dir = None
318 self._heart_beat.Stop()
319 self._current_test = None
320
252 321
253 class LocalDevicePerfTestRun(local_device_test_run.LocalDeviceTestRun): 322 class LocalDevicePerfTestRun(local_device_test_run.LocalDeviceTestRun):
254 323
255 _DEFAULT_TIMEOUT = 60 * 60 324 _DEFAULT_TIMEOUT = 60 * 60
256 _CONFIG_VERSION = 1 325 _CONFIG_VERSION = 1
257 326
258 def __init__(self, env, test_instance): 327 def __init__(self, env, test_instance):
259 super(LocalDevicePerfTestRun, self).__init__(env, test_instance) 328 super(LocalDevicePerfTestRun, self).__init__(env, test_instance)
260 self._devices = None 329 self._devices = None
261 self._env = env 330 self._env = env
331 self._no_device_tests = {}
262 self._test_buckets = [] 332 self._test_buckets = []
263 self._test_instance = test_instance 333 self._test_instance = test_instance
264 self._timeout = None if test_instance.no_timeout else self._DEFAULT_TIMEOUT 334 self._timeout = None if test_instance.no_timeout else self._DEFAULT_TIMEOUT
265 335
266 def SetUp(self): 336 def SetUp(self):
267 self._devices = self._GetAllDevices(self._env.devices, 337 self._devices = self._GetAllDevices(self._env.devices,
268 self._test_instance.known_devices_file) 338 self._test_instance.known_devices_file)
269 339
270 if os.path.exists(constants.PERF_OUTPUT_DIR): 340 if os.path.exists(constants.PERF_OUTPUT_DIR):
271 shutil.rmtree(constants.PERF_OUTPUT_DIR) 341 shutil.rmtree(constants.PERF_OUTPUT_DIR)
(...skipping 26 matching lines...) Expand all
298 'Neither single_step or steps set in test_instance.') 368 'Neither single_step or steps set in test_instance.')
299 369
300 def _SplitTestsByAffinity(self): 370 def _SplitTestsByAffinity(self):
301 # This splits tests by their device affinity so that the same tests always 371 # This splits tests by their device affinity so that the same tests always
302 # run on the same devices. This is important for perf tests since different 372 # run on the same devices. This is important for perf tests since different
303 # devices might yield slightly different performance results. 373 # devices might yield slightly different performance results.
304 test_dict = self._GetStepsFromDict() 374 test_dict = self._GetStepsFromDict()
305 for test, test_config in test_dict['steps'].iteritems(): 375 for test, test_config in test_dict['steps'].iteritems():
306 try: 376 try:
307 affinity = test_config['device_affinity'] 377 affinity = test_config['device_affinity']
308 if len(self._test_buckets) < affinity + 1: 378 if affinity == -1:
perezju 2016/08/04 15:10:50 ditto, can we use None instead of -1?
309 while len(self._test_buckets) != affinity + 1: 379 self._no_device_tests[test] = test_config
310 self._test_buckets.append({}) 380 else:
311 self._test_buckets[affinity][test] = test_config 381 if len(self._test_buckets) < affinity + 1:
382 while len(self._test_buckets) != affinity + 1:
383 self._test_buckets.append({})
384 self._test_buckets[affinity][test] = test_config
312 except KeyError: 385 except KeyError:
313 logging.exception( 386 logging.exception(
314 'Test config for %s is bad.\n Config:%s', test, str(test_config)) 387 'Test config for %s is bad.\n Config:%s', test, str(test_config))
315 388
316 @staticmethod 389 @staticmethod
317 def _GetAllDevices(active_devices, devices_path): 390 def _GetAllDevices(active_devices, devices_path):
318 try: 391 try:
319 if devices_path: 392 if devices_path:
320 devices = [device_utils.DeviceUtils(s) 393 devices = [device_utils.DeviceUtils(s)
321 for s in device_list.GetPersistentDeviceList(devices_path)] 394 for s in device_list.GetPersistentDeviceList(devices_path)]
322 if not devices and active_devices: 395 if not devices and active_devices:
323 logging.warning('%s is empty. Falling back to active devices.', 396 logging.warning('%s is empty. Falling back to active devices.',
324 devices_path) 397 devices_path)
325 devices = active_devices 398 devices = active_devices
326 else: 399 else:
327 logging.warning('Known devices file path not being passed. For device ' 400 logging.warning('Known devices file path not being passed. For device '
328 'affinity to work properly, it must be passed.') 401 'affinity to work properly, it must be passed.')
329 devices = active_devices 402 devices = active_devices
330 except IOError as e: 403 except IOError as e:
331 logging.error('Unable to find %s [%s]', devices_path, e) 404 logging.error('Unable to find %s [%s]', devices_path, e)
332 devices = active_devices 405 devices = active_devices
333 return sorted(devices) 406 return sorted(devices)
334 407
335 #override 408 #override
336 def RunTests(self): 409 def RunTests(self):
337 # Affinitize the tests. 410 # Affinitize the tests.
338 self._SplitTestsByAffinity() 411 self._SplitTestsByAffinity()
339 if not self._test_buckets: 412 if not self._test_buckets and not self._no_device_tests:
340 raise local_device_test_run.NoTestsError() 413 raise local_device_test_run.NoTestsError()
341 414
342 def run_perf_tests(shard_id): 415 def run_perf_tests(shard_id):
343 if device_status.IsBlacklisted( 416 if shard_id == -1:
344 str(self._devices[shard_id]), self._env.blacklist): 417 s = HostTestShard(self._env, self._test_instance, self._no_device_tests,
345 logging.warning('Device %s is not active. Will not create shard %s.', 418 retries=3, timeout=self._timeout)
346 str(self._devices[shard_id]), shard_id) 419 else:
347 return None 420 if device_status.IsBlacklisted(
348 s = TestShard(self._env, self._test_instance, self._devices[shard_id], 421 str(self._devices[shard_id]), self._env.blacklist):
349 shard_id, self._test_buckets[shard_id], 422 logging.warning('Device %s is not active. Will not create shard %s.',
350 retries=self._env.max_tries, timeout=self._timeout) 423 str(self._devices[shard_id]), shard_id)
424 return None
425 s = DeviceTestShard(self._env, self._test_instance,
426 self._devices[shard_id], shard_id,
427 self._test_buckets[shard_id],
428 retries=self._env.max_tries, timeout=self._timeout)
351 return s.RunTestsOnShard() 429 return s.RunTestsOnShard()
352 430
353 device_indices = range(min(len(self._devices), len(self._test_buckets))) 431 device_indices = range(min(len(self._devices), len(self._test_buckets)))
432 if self._no_device_tests:
433 device_indices.append(-1)
354 shards = parallelizer.Parallelizer(device_indices).pMap(run_perf_tests) 434 shards = parallelizer.Parallelizer(device_indices).pMap(run_perf_tests)
355 return [x for x in shards.pGet(self._timeout) if x is not None] 435 return [x for x in shards.pGet(self._timeout) if x is not None]
356 436
357 # override 437 # override
358 def TestPackage(self): 438 def TestPackage(self):
359 return 'perf' 439 return 'perf'
360 440
361 # override 441 # override
362 def _CreateShards(self, _tests): 442 def _CreateShards(self, _tests):
363 raise NotImplementedError 443 raise NotImplementedError
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
417 # override 497 # override
418 def _RunTest(self, _device, _test): 498 def _RunTest(self, _device, _test):
419 raise NotImplementedError 499 raise NotImplementedError
420 500
421 501
422 class TestDictVersionError(Exception): 502 class TestDictVersionError(Exception):
423 pass 503 pass
424 504
425 class PerfTestRunGetStepsError(Exception): 505 class PerfTestRunGetStepsError(Exception):
426 pass 506 pass
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698