1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.testtype.suite; 17 18 import com.android.tradefed.config.Configuration; 19 import com.android.tradefed.config.ConfigurationException; 20 import com.android.tradefed.config.IConfiguration; 21 import com.android.tradefed.config.IDeviceConfiguration; 22 import com.android.tradefed.config.OptionCopier; 23 import com.android.tradefed.invoker.TestInformation; 24 import com.android.tradefed.log.LogUtil.CLog; 25 import com.android.tradefed.targetprep.ITargetPreparer; 26 import com.android.tradefed.targetprep.multi.IMultiTargetPreparer; 27 import com.android.tradefed.testtype.IAbiReceiver; 28 import com.android.tradefed.testtype.IRemoteTest; 29 import com.android.tradefed.testtype.IShardableTest; 30 31 import java.lang.reflect.InvocationTargetException; 32 import java.util.ArrayList; 33 import java.util.Collection; 34 import java.util.LinkedHashMap; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Map.Entry; 38 39 /** 40 * Helper to split a list of modules represented by {@link IConfiguration} into a list of execution 41 * units represented by {@link ModuleDefinition}. 42 * 43 * <p>Each configuration may generate 1 or more {@link ModuleDefinition} depending on its options 44 * and test types: 45 * 46 * <ul> 47 * <li>A non-shardable {@link IConfiguration} will generate a single {@link ModuleDefinition}. 48 * <li>A shardable {@link IConfiguration} will generate a number of ModuleDefinition linked to the 49 * {@link IRemoteTest} properties: 50 * <ul> 51 * <li>A non - {@link IShardableTest} will generate a single ModuleDefinition. 52 * <li>A {@link IShardableTest} generates one ModuleDefinition per tests returned by {@link 53 * IShardableTest#split()}. 54 * </ul> 55 * 56 * </ul> 57 */ 58 public class ModuleSplitter { 59 60 /** 61 * Set an upper limit to how much a given module can be sharded. This can avoid over-granular 62 * intra-module sharding. 63 */ 64 private static final int MAX_MODULE_LOCAL_SHARDING = 8; 65 66 /** 67 * Create a List of executable unit {@link ModuleDefinition}s based on the map of configuration 68 * that was loaded. 69 * 70 * @param testInfo the current {@link TestInformation} to proceed with sharding. 71 * @param runConfig {@link LinkedHashMap} loaded from {@link ITestSuite#loadTests()}. 72 * @param suitePreparersPerDevice map of suite level preparers per test device. 73 * @param shardCount a shard count hint to help with sharding. 74 * @param dynamicModule Whether or not module can be shared in pool or must be independent 75 * (strict sharding). 76 * @param intraModuleSharding Whether or not to shard within the modules. 77 * @return List of {@link ModuleDefinition} 78 */ splitConfiguration( TestInformation testInfo, LinkedHashMap<String, IConfiguration> runConfig, Map<String, List<ITargetPreparer>> suitePreparersPerDevice, int shardCount, boolean dynamicModule, boolean intraModuleSharding)79 public static List<ModuleDefinition> splitConfiguration( 80 TestInformation testInfo, 81 LinkedHashMap<String, IConfiguration> runConfig, 82 Map<String, List<ITargetPreparer>> suitePreparersPerDevice, 83 int shardCount, 84 boolean dynamicModule, 85 boolean intraModuleSharding) { 86 if (dynamicModule) { 87 // We maximize the sharding for dynamic to reduce time difference between first and 88 // last shard as much as possible. Overhead is low due to our test pooling. 89 shardCount *= 2; 90 } 91 List<ModuleDefinition> runModules = new ArrayList<>(); 92 for (Entry<String, IConfiguration> configMap : runConfig.entrySet()) { 93 // Check that it's a valid configuration for suites, throw otherwise. 94 ValidateSuiteConfigHelper.validateConfig(configMap.getValue()); 95 96 try { 97 createAndAddModule( 98 testInfo, 99 runModules, 100 configMap.getKey(), 101 configMap.getValue(), 102 shardCount, 103 dynamicModule, 104 intraModuleSharding, 105 suitePreparersPerDevice); 106 } catch (RuntimeException e) { 107 CLog.e("Exception while creating module for '%s'", configMap.getKey()); 108 throw e; 109 } 110 } 111 return runModules; 112 } 113 createAndAddModule( TestInformation testInfo, List<ModuleDefinition> currentList, String moduleName, IConfiguration config, int shardCount, boolean dynamicModule, boolean intraModuleSharding, Map<String, List<ITargetPreparer>> suitePreparersPerDevice)114 private static void createAndAddModule( 115 TestInformation testInfo, 116 List<ModuleDefinition> currentList, 117 String moduleName, 118 IConfiguration config, 119 int shardCount, 120 boolean dynamicModule, 121 boolean intraModuleSharding, 122 Map<String, List<ITargetPreparer>> suitePreparersPerDevice) { 123 List<IRemoteTest> tests = config.getTests(); 124 // Get rid of the IRemoteTest reference on the shared configuration. It will not be used 125 // to run. 126 config.setTests(new ArrayList<>()); 127 128 if (config.getConfigurationDescription().isNotIRemoteTestShardable()) { 129 // If it cannot be sharded at all, then put all the tests together. 130 ModuleDefinition module = 131 new ModuleDefinition( 132 moduleName, 133 tests, 134 clonePreparersMap(config), 135 clonePreparersMap(suitePreparersPerDevice), 136 clonePreparers(config.getMultiTargetPreparers()), 137 config); 138 currentList.add(module); 139 clearPreparersFromConfig(config); 140 return; 141 } 142 143 // If this particular configuration module is declared as 'not shardable' we take it whole 144 // but still split the individual IRemoteTest in a pool. 145 if (!intraModuleSharding 146 || config.getConfigurationDescription().isNotShardable() 147 || (!dynamicModule 148 && config.getConfigurationDescription().isNotStrictShardable())) { 149 for (int i = 0; i < tests.size(); i++) { 150 if (dynamicModule) { 151 ModuleDefinition module = 152 new ModuleDefinition( 153 moduleName, 154 tests, 155 clonePreparersMap(config), 156 clonePreparersMap(suitePreparersPerDevice), 157 clonePreparers(config.getMultiTargetPreparers()), 158 config); 159 currentList.add(module); 160 } else { 161 addModuleToListFromSingleTest( 162 currentList, tests.get(i), moduleName, config, suitePreparersPerDevice); 163 } 164 } 165 clearPreparersFromConfig(config); 166 return; 167 } 168 169 // If configuration is possibly shardable we attempt to shard it. 170 for (IRemoteTest test : tests) { 171 if (test instanceof IShardableTest) { 172 int shard = Math.min(MAX_MODULE_LOCAL_SHARDING, shardCount); 173 Collection<IRemoteTest> shardedTests = 174 ((IShardableTest) test).split(shard, testInfo); 175 if (shardedTests != null) { 176 // Test did shard we put the shard pool in ModuleDefinition which has a polling 177 // behavior on the pool. 178 if (dynamicModule) { 179 for (int i = 0; i < shardedTests.size(); i++) { 180 ModuleDefinition module = 181 new ModuleDefinition( 182 moduleName, 183 shardedTests, 184 clonePreparersMap(config), 185 clonePreparersMap(suitePreparersPerDevice), 186 clonePreparers(config.getMultiTargetPreparers()), 187 config); 188 currentList.add(module); 189 } 190 } else { 191 // We create independent modules with each sharded test. 192 for (IRemoteTest moduleTest : shardedTests) { 193 addModuleToListFromSingleTest( 194 currentList, 195 moduleTest, 196 moduleName, 197 config, 198 suitePreparersPerDevice); 199 } 200 } 201 continue; 202 } 203 } 204 // test is not shardable or did not shard 205 addModuleToListFromSingleTest( 206 currentList, test, moduleName, config, suitePreparersPerDevice); 207 } 208 clearPreparersFromConfig(config); 209 } 210 211 /** 212 * Helper to add a new {@link ModuleDefinition} to our list of Modules from a single {@link 213 * IRemoteTest}. 214 */ addModuleToListFromSingleTest( List<ModuleDefinition> currentList, IRemoteTest test, String moduleName, IConfiguration config, Map<String, List<ITargetPreparer>> suitePreparersPerDevice)215 private static void addModuleToListFromSingleTest( 216 List<ModuleDefinition> currentList, 217 IRemoteTest test, 218 String moduleName, 219 IConfiguration config, 220 Map<String, List<ITargetPreparer>> suitePreparersPerDevice) { 221 List<IRemoteTest> testList = new ArrayList<>(); 222 testList.add(test); 223 ModuleDefinition module = 224 new ModuleDefinition( 225 moduleName, 226 testList, 227 clonePreparersMap(config), 228 clonePreparersMap(suitePreparersPerDevice), 229 clonePreparers(config.getMultiTargetPreparers()), 230 config); 231 currentList.add(module); 232 } 233 234 /** 235 * Deep clone a list of {@link ITargetPreparer} or {@link IMultiTargetPreparer}. We are ensured 236 * to find a default constructor with no arguments since that's the expectation from Tradefed 237 * when loading configuration. Cloning preparers is required since they may be stateful and we 238 * cannot share instance across devices. 239 */ clonePreparers(List<T> preparerList)240 private static <T> List<T> clonePreparers(List<T> preparerList) { 241 List<T> clones = new ArrayList<>(); 242 for (T prep : preparerList) { 243 try { 244 @SuppressWarnings("unchecked") 245 T clone = (T) prep.getClass().getDeclaredConstructor().newInstance(); 246 OptionCopier.copyOptions(prep, clone); 247 // Ensure we copy the Abi too. 248 if (clone instanceof IAbiReceiver) { 249 ((IAbiReceiver) clone).setAbi(((IAbiReceiver) prep).getAbi()); 250 } 251 clones.add(clone); 252 } catch (InstantiationException 253 | IllegalAccessException 254 | ConfigurationException 255 | InvocationTargetException 256 | NoSuchMethodException e) { 257 throw new RuntimeException(e); 258 } 259 } 260 return clones; 261 } 262 263 /** Deep cloning of potentially multi-device preparers. */ clonePreparersMap(IConfiguration config)264 private static Map<String, List<ITargetPreparer>> clonePreparersMap(IConfiguration config) { 265 Map<String, List<ITargetPreparer>> res = new LinkedHashMap<>(); 266 for (IDeviceConfiguration holder : config.getDeviceConfig()) { 267 List<ITargetPreparer> preparers = new ArrayList<>(); 268 res.put(holder.getDeviceName(), preparers); 269 preparers.addAll(clonePreparers(holder.getTargetPreparers())); 270 } 271 return res; 272 } 273 274 /** Deep cloning of potentially multi-device preparers. */ clonePreparersMap( Map<String, List<ITargetPreparer>> suitePreparersPerDevice)275 private static Map<String, List<ITargetPreparer>> clonePreparersMap( 276 Map<String, List<ITargetPreparer>> suitePreparersPerDevice) { 277 Map<String, List<ITargetPreparer>> res = new LinkedHashMap<>(); 278 for (String device : suitePreparersPerDevice.keySet()) { 279 List<ITargetPreparer> preparers = new ArrayList<>(); 280 res.put(device, preparers); 281 preparers.addAll(clonePreparers(suitePreparersPerDevice.get(device))); 282 } 283 return res; 284 } 285 clearPreparersFromConfig(IConfiguration config)286 private static void clearPreparersFromConfig(IConfiguration config) { 287 try { 288 for (IDeviceConfiguration holder : config.getDeviceConfig()) { 289 holder.removeObjectType(Configuration.TARGET_PREPARER_TYPE_NAME); 290 } 291 config.setMultiTargetPreparers(new ArrayList<>()); 292 } catch (ConfigurationException e) { 293 throw new RuntimeException(e); 294 } 295 } 296 } 297