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 |