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

Side by Side Diff: build/android/pylib/perf/setup.py

Issue 1148873007: Fix last_devices to be quieter, and improve device affinity. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Sort devices again for reboot-stability Created 5 years, 6 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 | « build/android/pylib/device/device_list.py ('k') | build/android/pylib/perf/test_runner.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
10 import itertools
9 import logging 11 import logging
10 import os 12 import os
11 import shutil 13 import shutil
12 14
13 from pylib import constants 15 from pylib import constants
14 from pylib import forwarder 16 from pylib import forwarder
15 from pylib.device import device_list 17 from pylib.device import device_list
16 from pylib.device import device_utils 18 from pylib.device import device_utils
17 from pylib.perf import test_runner 19 from pylib.perf import test_runner
18 from pylib.utils import test_environment 20 from pylib.utils import test_environment
19 21
20 22
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_utils.DeviceUtils(s)
26 for s in device_list.GetPersistentDeviceList(devices_path)]
27 except IOError as e:
28 logging.error('Unable to find %s [%s]', devices_path, e)
29 devices = device_utils.DeviceUtils.HealthyDevices()
30 return sorted(devices)
31
32
33 def _GetStepsDictFromSingleStep(test_options): 23 def _GetStepsDictFromSingleStep(test_options):
34 # Running a single command, build the tests structure. 24 # Running a single command, build the tests structure.
35 steps_dict = { 25 steps_dict = {
36 'version': 1, 26 'version': 1,
37 'steps': { 27 'steps': {
38 'single_step': { 28 'single_step': {
39 'device_affinity': 0, 29 'device_affinity': 0,
40 'cmd': test_options.single_step 30 'cmd': test_options.single_step
41 }, 31 },
42 } 32 }
43 } 33 }
44 return steps_dict 34 return steps_dict
45 35
46 36
47 def _GetStepsDict(test_options): 37 def _GetStepsDict(test_options):
48 if test_options.single_step: 38 if test_options.single_step:
49 return _GetStepsDictFromSingleStep(test_options) 39 return _GetStepsDictFromSingleStep(test_options)
50 if test_options.steps: 40 if test_options.steps:
51 with file(test_options.steps, 'r') as f: 41 with file(test_options.steps, 'r') as f:
52 steps = json.load(f) 42 steps = json.load(f)
53 43
54 # Already using the new format. 44 # Already using the new format.
55 assert steps['version'] == 1 45 assert steps['version'] == 1
56 return steps 46 return steps
57 47
58 48
49 def DistributeAffinities(devices):
50 """Create a mapping from device to affinity using a round-robin hash scheme.
51
52 Args:
53 devices: a list of device serials.
54
55 Returns: a dictionary mapping each of the given device serials to a list
56 of affinities (numbers between 0 and NUM_DEVICE_AFFINITIES-1) it should
57 run.
58
59 This function implements an algorithm to assign affinities to devices in a
60 relatively stable way while still maximizing the use of resources.
61
62 We begin with all affinities unassigned. We cycle through the list of device
63 serials, and for each one we assign a yet-unassigned affinity. We choose the
64 affinity to assign by hashing the device serial, so that changes in the set of
65 devices will produce minimal changes in the assignments. We resolve
66 collisions by incrementing the affinity until we find one that is free.
67 """
68 affinities = test_runner.NUM_DEVICE_AFFINITIES
69
70 if not devices:
71 return {}
72
73 affinity_to_device = {}
74
75 devices = sorted(devices)
76 device_cycle = itertools.islice(itertools.cycle(devices), affinities)
77 for device in device_cycle:
78 # Hash the device ID to get a unique affinity.
79 start_affinity = int(int(hashlib.md5(device).hexdigest(), 16) % affinities)
80 affinity_cycle = itertools.islice(
81 itertools.cycle(range(affinities)),
82 start_affinity, start_affinity+affinities)
83
84 # "Increment" the affinity repeatedly until we find an available slot.
85 for affinity in affinity_cycle:
86 if affinity not in affinity_to_device:
87 affinity_to_device[affinity] = device
88 break
89 else:
90 raise Exception('Impossibly failed to find unassigned affinity')
91
92 # Invert the dictionary we just built.
93 device_to_affinity = {}
94 for k, v in affinity_to_device.iteritems():
95 if v not in device_to_affinity:
96 device_to_affinity[v] = []
97 device_to_affinity[v].append(k)
98
99 return device_to_affinity
100
59 def Setup(test_options): 101 def Setup(test_options):
60 """Create and return the test runner factory and tests. 102 """Create and return the test runner factory and tests.
61 103
62 Args: 104 Args:
63 test_options: A PerformanceOptions object. 105 test_options: A PerformanceOptions object.
64 106
65 Returns: 107 Returns:
66 A tuple of (TestRunnerFactory, tests, devices). 108 A tuple of (TestRunnerFactory, tests, devices).
67 """ 109 """
68 # TODO(bulach): remove this once the bot side lands. BUG=318369 110 # TODO(bulach): remove this once the bot side lands. BUG=318369
69 constants.SetBuildType('Release') 111 constants.SetBuildType('Release')
70 if os.path.exists(constants.PERF_OUTPUT_DIR): 112 if os.path.exists(constants.PERF_OUTPUT_DIR):
71 shutil.rmtree(constants.PERF_OUTPUT_DIR) 113 shutil.rmtree(constants.PERF_OUTPUT_DIR)
72 os.makedirs(constants.PERF_OUTPUT_DIR) 114 os.makedirs(constants.PERF_OUTPUT_DIR)
73 115
74 # Before running the tests, kill any leftover server. 116 # Before running the tests, kill any leftover server.
75 test_environment.CleanupLeftoverProcesses() 117 test_environment.CleanupLeftoverProcesses()
76 118
77 # We want to keep device affinity, so return all devices ever seen. 119 all_devices = [d.adb.GetDeviceSerial()
78 all_devices = _GetAllDevices() 120 for d in device_utils.DeviceUtils.HealthyDevices()]
121 affinity_map = DistributeAffinities(all_devices)
79 122
80 steps_dict = _GetStepsDict(test_options) 123 steps_dict = _GetStepsDict(test_options)
81 sorted_step_names = sorted(steps_dict['steps'].keys()) 124 sorted_step_names = sorted(steps_dict['steps'].keys())
82 125
83 if test_options.test_filter: 126 if test_options.test_filter:
84 sorted_step_names = fnmatch.filter(sorted_step_names, 127 sorted_step_names = fnmatch.filter(sorted_step_names,
85 test_options.test_filter) 128 test_options.test_filter)
86 129
87 flaky_steps = [] 130 flaky_steps = []
88 if test_options.flaky_steps: 131 if test_options.flaky_steps:
89 with file(test_options.flaky_steps, 'r') as f: 132 with file(test_options.flaky_steps, 'r') as f:
90 flaky_steps = json.load(f) 133 flaky_steps = json.load(f)
91 134
92 def TestRunnerFactory(device, shard_index): 135 def TestRunnerFactory(device, _):
136 affinities = affinity_map[device]
93 return test_runner.TestRunner( 137 return test_runner.TestRunner(
94 test_options, device, shard_index, len(all_devices), 138 test_options, device, affinities, steps_dict, flaky_steps)
95 steps_dict, flaky_steps)
96 139
97 return (TestRunnerFactory, sorted_step_names, all_devices) 140 return (TestRunnerFactory, sorted_step_names, all_devices)
OLDNEW
« no previous file with comments | « build/android/pylib/device/device_list.py ('k') | build/android/pylib/perf/test_runner.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698