Chromium Code Reviews| 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 """Generates test runner factory and tests for performance tests.""" | 5 """Generates test runner factory and tests for performance tests.""" |
| 6 | 6 |
| 7 import json | 7 import json |
| 8 import fnmatch | 8 import fnmatch |
| 9 import hashlib | |
| 9 import logging | 10 import logging |
| 10 import os | 11 import os |
| 11 import shutil | 12 import shutil |
| 12 | 13 |
| 13 from pylib import android_commands | 14 from pylib import android_commands |
| 14 from pylib import constants | 15 from pylib import constants |
| 15 from pylib import forwarder | 16 from pylib import forwarder |
| 16 from pylib.device import device_list | 17 from pylib.device import device_list |
| 17 from pylib.perf import test_runner | 18 from pylib.perf import test_runner |
| 18 from pylib.utils import test_environment | 19 from pylib.utils import test_environment |
| 19 | 20 |
| 20 | 21 |
| 21 def _GetAllDevices(): | |
| 22 devices_path = os.path.join(os.environ.get('CHROMIUM_OUT_DIR', 'out'), | |
| 23 device_list.LAST_DEVICES_FILENAME) | |
| 24 try: | |
| 25 devices = device_list.GetPersistentDeviceList(devices_path) | |
| 26 except IOError as e: | |
| 27 logging.error('Unable to find %s [%s]', devices_path, e) | |
| 28 devices = android_commands.GetAttachedDevices() | |
|
jbudorick
2015/05/23 01:06:49
How is this still around...
luqui
2015/05/27 20:01:12
Looks like I forgot to sync before starting work o
| |
| 29 return sorted(devices) | |
| 30 | |
| 31 | |
| 32 def _GetStepsDictFromSingleStep(test_options): | 22 def _GetStepsDictFromSingleStep(test_options): |
| 33 # Running a single command, build the tests structure. | 23 # Running a single command, build the tests structure. |
| 34 steps_dict = { | 24 steps_dict = { |
| 35 'version': 1, | 25 'version': 1, |
| 36 'steps': { | 26 'steps': { |
| 37 'single_step': { | 27 'single_step': { |
| 38 'device_affinity': 0, | 28 'device_affinity': 0, |
| 39 'cmd': test_options.single_step | 29 'cmd': test_options.single_step |
| 40 }, | 30 }, |
| 41 } | 31 } |
| 42 } | 32 } |
| 43 return steps_dict | 33 return steps_dict |
| 44 | 34 |
| 45 | 35 |
| 46 def _GetStepsDict(test_options): | 36 def _GetStepsDict(test_options): |
| 47 if test_options.single_step: | 37 if test_options.single_step: |
| 48 return _GetStepsDictFromSingleStep(test_options) | 38 return _GetStepsDictFromSingleStep(test_options) |
| 49 if test_options.steps: | 39 if test_options.steps: |
| 50 with file(test_options.steps, 'r') as f: | 40 with file(test_options.steps, 'r') as f: |
| 51 steps = json.load(f) | 41 steps = json.load(f) |
| 52 | 42 |
| 53 # Already using the new format. | 43 # Already using the new format. |
| 54 assert steps['version'] == 1 | 44 assert steps['version'] == 1 |
| 55 return steps | 45 return steps |
| 56 | 46 |
| 57 | 47 |
| 48 def DistributeAffinities(devices): | |
| 49 """Create a mapping from device to affinity using a round-robin hash scheme. | |
| 50 This is a way to make sure that affinity -> device mapping is relatively | |
|
jbudorick
2015/05/23 01:06:49
nit: line break after the first line of the docstr
luqui
2015/05/27 20:01:12
Done.
| |
| 51 stable while still maximizing the use of resources.""" | |
|
jbudorick
2015/05/23 01:06:49
I'm not sure this is stable if you lose one of the
luqui
2015/05/27 20:01:12
IIUC the persistent device list basically messes u
| |
| 52 if not devices: | |
| 53 return {} | |
| 54 | |
| 55 device_cycle = [] | |
| 56 while len(device_cycle) < test_runner.NUM_DEVICE_AFFINITIES: | |
| 57 device_cycle.extend(devices) | |
| 58 device_cycle = device_cycle[:test_runner.NUM_DEVICE_AFFINITIES] | |
| 59 | |
| 60 affinity_to_device = {} | |
| 61 | |
| 62 # Hash and rehash each device ID until we get a unique affinity. | |
| 63 for device in device_cycle: | |
| 64 cur_hash = device | |
| 65 while True: | |
| 66 cur_hash = hashlib.md5(cur_hash).hexdigest() | |
| 67 affinity = int(int(cur_hash, 16) % test_runner.NUM_DEVICE_AFFINITIES) | |
| 68 if affinity not in affinity_to_device: | |
| 69 affinity_to_device[affinity] = device | |
| 70 break | |
| 71 | |
| 72 # Invert the dictionary we just built. | |
| 73 device_to_affinity = {} | |
| 74 for k, v in affinity_to_device.iteritems(): | |
| 75 if v not in device_to_affinity: | |
| 76 device_to_affinity[v] = [] | |
| 77 device_to_affinity[v].append(k) | |
| 78 | |
| 79 return device_to_affinity | |
| 80 | |
| 58 def Setup(test_options): | 81 def Setup(test_options): |
| 59 """Create and return the test runner factory and tests. | 82 """Create and return the test runner factory and tests. |
| 60 | 83 |
| 61 Args: | 84 Args: |
| 62 test_options: A PerformanceOptions object. | 85 test_options: A PerformanceOptions object. |
| 63 | 86 |
| 64 Returns: | 87 Returns: |
| 65 A tuple of (TestRunnerFactory, tests, devices). | 88 A tuple of (TestRunnerFactory, tests, devices). |
| 66 """ | 89 """ |
| 67 # TODO(bulach): remove this once the bot side lands. BUG=318369 | 90 # TODO(bulach): remove this once the bot side lands. BUG=318369 |
| 68 constants.SetBuildType('Release') | 91 constants.SetBuildType('Release') |
| 69 if os.path.exists(constants.PERF_OUTPUT_DIR): | 92 if os.path.exists(constants.PERF_OUTPUT_DIR): |
| 70 shutil.rmtree(constants.PERF_OUTPUT_DIR) | 93 shutil.rmtree(constants.PERF_OUTPUT_DIR) |
| 71 os.makedirs(constants.PERF_OUTPUT_DIR) | 94 os.makedirs(constants.PERF_OUTPUT_DIR) |
| 72 | 95 |
| 73 # Before running the tests, kill any leftover server. | 96 # Before running the tests, kill any leftover server. |
| 74 test_environment.CleanupLeftoverProcesses() | 97 test_environment.CleanupLeftoverProcesses() |
| 75 forwarder.Forwarder.UseMultiprocessing() | 98 forwarder.Forwarder.UseMultiprocessing() |
| 76 | 99 |
| 77 # We want to keep device affinity, so return all devices ever seen. | 100 all_devices = android_commands.GetAttachedDevices() |
|
jbudorick
2015/05/23 01:06:49
device_utils.DeviceUtils.HealthyDevices()
if you
luqui
2015/05/27 20:01:12
Done.
| |
| 78 all_devices = _GetAllDevices() | 101 affinity_map = DistributeAffinities(all_devices) |
| 79 | 102 |
| 80 steps_dict = _GetStepsDict(test_options) | 103 steps_dict = _GetStepsDict(test_options) |
| 81 sorted_step_names = sorted(steps_dict['steps'].keys()) | 104 sorted_step_names = sorted(steps_dict['steps'].keys()) |
| 82 | 105 |
| 83 if test_options.test_filter: | 106 if test_options.test_filter: |
| 84 sorted_step_names = fnmatch.filter(sorted_step_names, | 107 sorted_step_names = fnmatch.filter(sorted_step_names, |
| 85 test_options.test_filter) | 108 test_options.test_filter) |
| 86 | 109 |
| 87 flaky_steps = [] | 110 flaky_steps = [] |
| 88 if test_options.flaky_steps: | 111 if test_options.flaky_steps: |
| 89 with file(test_options.flaky_steps, 'r') as f: | 112 with file(test_options.flaky_steps, 'r') as f: |
| 90 flaky_steps = json.load(f) | 113 flaky_steps = json.load(f) |
| 91 | 114 |
| 92 def TestRunnerFactory(device, shard_index): | 115 def TestRunnerFactory(device, _): |
| 116 affinities = affinity_map[device] | |
| 93 return test_runner.TestRunner( | 117 return test_runner.TestRunner( |
| 94 test_options, device, shard_index, len(all_devices), | 118 test_options, device, affinities, steps_dict, flaky_steps) |
| 95 steps_dict, flaky_steps) | |
| 96 | 119 |
| 97 return (TestRunnerFactory, sorted_step_names, all_devices) | 120 return (TestRunnerFactory, sorted_step_names, all_devices) |
| OLD | NEW |