| Index: build/android/pylib/perf/setup.py
|
| diff --git a/build/android/pylib/perf/setup.py b/build/android/pylib/perf/setup.py
|
| index 8e1fc28a7e651bef567b336e5593d8be64c75701..371d5a443931252e9759c2484e22ed960cabbde2 100644
|
| --- a/build/android/pylib/perf/setup.py
|
| +++ b/build/android/pylib/perf/setup.py
|
| @@ -6,6 +6,8 @@
|
|
|
| import json
|
| import fnmatch
|
| +import hashlib
|
| +import itertools
|
| import logging
|
| import os
|
| import shutil
|
| @@ -18,18 +20,6 @@ from pylib.perf import test_runner
|
| from pylib.utils import test_environment
|
|
|
|
|
| -def _GetAllDevices():
|
| - devices_path = os.path.join(os.environ.get('CHROMIUM_OUT_DIR', 'out'),
|
| - device_list.LAST_DEVICES_FILENAME)
|
| - try:
|
| - devices = [device_utils.DeviceUtils(s)
|
| - for s in device_list.GetPersistentDeviceList(devices_path)]
|
| - except IOError as e:
|
| - logging.error('Unable to find %s [%s]', devices_path, e)
|
| - devices = device_utils.DeviceUtils.HealthyDevices()
|
| - return sorted(devices)
|
| -
|
| -
|
| def _GetStepsDictFromSingleStep(test_options):
|
| # Running a single command, build the tests structure.
|
| steps_dict = {
|
| @@ -56,6 +46,58 @@ def _GetStepsDict(test_options):
|
| return steps
|
|
|
|
|
| +def DistributeAffinities(devices):
|
| + """Create a mapping from device to affinity using a round-robin hash scheme.
|
| +
|
| + Args:
|
| + devices: a list of device serials.
|
| +
|
| + Returns: a dictionary mapping each of the given device serials to a list
|
| + of affinities (numbers between 0 and NUM_DEVICE_AFFINITIES-1) it should
|
| + run.
|
| +
|
| + This function implements an algorithm to assign affinities to devices in a
|
| + relatively stable way while still maximizing the use of resources.
|
| +
|
| + We begin with all affinities unassigned. We cycle through the list of device
|
| + serials, and for each one we assign a yet-unassigned affinity. We choose the
|
| + affinity to assign by hashing the device serial, so that changes in the set of
|
| + devices will produce minimal changes in the assignments. We resolve
|
| + collisions by incrementing the affinity until we find one that is free.
|
| + """
|
| + affinities = test_runner.NUM_DEVICE_AFFINITIES
|
| +
|
| + if not devices:
|
| + return {}
|
| +
|
| + affinity_to_device = {}
|
| +
|
| + devices = sorted(devices)
|
| + device_cycle = itertools.islice(itertools.cycle(devices), affinities)
|
| + for device in device_cycle:
|
| + # Hash the device ID to get a unique affinity.
|
| + start_affinity = int(int(hashlib.md5(device).hexdigest(), 16) % affinities)
|
| + affinity_cycle = itertools.islice(
|
| + itertools.cycle(range(affinities)),
|
| + start_affinity, start_affinity+affinities)
|
| +
|
| + # "Increment" the affinity repeatedly until we find an available slot.
|
| + for affinity in affinity_cycle:
|
| + if affinity not in affinity_to_device:
|
| + affinity_to_device[affinity] = device
|
| + break
|
| + else:
|
| + raise Exception('Impossibly failed to find unassigned affinity')
|
| +
|
| + # Invert the dictionary we just built.
|
| + device_to_affinity = {}
|
| + for k, v in affinity_to_device.iteritems():
|
| + if v not in device_to_affinity:
|
| + device_to_affinity[v] = []
|
| + device_to_affinity[v].append(k)
|
| +
|
| + return device_to_affinity
|
| +
|
| def Setup(test_options):
|
| """Create and return the test runner factory and tests.
|
|
|
| @@ -74,8 +116,9 @@ def Setup(test_options):
|
| # Before running the tests, kill any leftover server.
|
| test_environment.CleanupLeftoverProcesses()
|
|
|
| - # We want to keep device affinity, so return all devices ever seen.
|
| - all_devices = _GetAllDevices()
|
| + all_devices = [d.adb.GetDeviceSerial()
|
| + for d in device_utils.DeviceUtils.HealthyDevices()]
|
| + affinity_map = DistributeAffinities(all_devices)
|
|
|
| steps_dict = _GetStepsDict(test_options)
|
| sorted_step_names = sorted(steps_dict['steps'].keys())
|
| @@ -89,9 +132,9 @@ def Setup(test_options):
|
| with file(test_options.flaky_steps, 'r') as f:
|
| flaky_steps = json.load(f)
|
|
|
| - def TestRunnerFactory(device, shard_index):
|
| + def TestRunnerFactory(device, _):
|
| + affinities = affinity_map[device]
|
| return test_runner.TestRunner(
|
| - test_options, device, shard_index, len(all_devices),
|
| - steps_dict, flaky_steps)
|
| + test_options, device, affinities, steps_dict, flaky_steps)
|
|
|
| return (TestRunnerFactory, sorted_step_names, all_devices)
|
|
|