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.result.suite; 17 18 import com.android.ddmlib.Log.LogLevel; 19 import com.android.ddmlib.testrunner.TestResult.TestStatus; 20 import com.android.tradefed.invoker.IInvocationContext; 21 import com.android.tradefed.log.LogUtil.CLog; 22 import com.android.tradefed.result.CollectingTestListener; 23 import com.android.tradefed.result.TestRunResult; 24 import com.android.tradefed.result.TestSummary; 25 import com.android.tradefed.result.TestSummary.Type; 26 import com.android.tradefed.result.TestSummary.TypedString; 27 import com.android.tradefed.testtype.Abi; 28 import com.android.tradefed.testtype.IAbi; 29 import com.android.tradefed.testtype.suite.ITestSuite; 30 import com.android.tradefed.testtype.suite.ModuleDefinition; 31 import com.android.tradefed.util.AbiUtils; 32 import com.android.tradefed.util.TimeUtil; 33 34 import java.util.ArrayList; 35 import java.util.Collection; 36 import java.util.Collections; 37 import java.util.Comparator; 38 import java.util.HashMap; 39 import java.util.LinkedHashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Map.Entry; 43 44 /** Collect test results for an entire suite invocation and output the final results. */ 45 public class SuiteResultReporter extends CollectingTestListener { 46 47 public static final String SUITE_REPORTER_SOURCE = SuiteResultReporter.class.getName(); 48 49 private long mStartTime = 0l; 50 private long mElapsedTime = 0l; 51 52 private int mTotalModules = 0; 53 private int mCompleteModules = 0; 54 55 private long mTotalTests = 0l; 56 private long mPassedTests = 0l; 57 private long mFailedTests = 0l; 58 private long mSkippedTests = 0l; 59 private long mAssumeFailureTests = 0l; 60 61 private Map<String, Integer> mModuleExpectedTests = new HashMap<>(); 62 private Map<String, String> mFailedModule = new HashMap<>(); 63 // Map holding the preparation time for each Module. 64 private Map<String, ModulePrepTimes> mPreparationMap = new HashMap<>(); 65 66 private Map<String, IAbi> mModuleAbi = new LinkedHashMap<>(); 67 68 private StringBuilder mSummary; 69 70 public SuiteResultReporter() { 71 super(); 72 // force aggregate true to get full metrics. 73 setIsAggregrateMetrics(true); 74 mSummary = new StringBuilder(); 75 } 76 77 @Override 78 public void invocationStarted(IInvocationContext context) { 79 super.invocationStarted(context); 80 mStartTime = System.currentTimeMillis(); 81 } 82 83 @Override 84 public void testModuleStarted(IInvocationContext moduleContext) { 85 super.testModuleStarted(moduleContext); 86 // Keep track of the module abi if it has one. 87 List<String> abiName = moduleContext.getAttributes().get(ModuleDefinition.MODULE_ABI); 88 if (abiName != null) { 89 IAbi abi = new Abi(abiName.get(0), AbiUtils.getBitness(abiName.get(0))); 90 mModuleAbi.put( 91 moduleContext.getAttributes().get(ModuleDefinition.MODULE_ID).get(0), abi); 92 } 93 } 94 95 @Override 96 public void testRunStarted(String name, int numTests) { 97 super.testRunStarted(name, numTests); 98 if (mModuleExpectedTests.get(name) == null) { 99 mModuleExpectedTests.put(name, numTests); 100 } else { 101 mModuleExpectedTests.put(name, mModuleExpectedTests.get(name) + numTests); 102 } 103 } 104 105 /** Helper to remove the module checker results from the final list of real module results. */ 106 private List<TestRunResult> extractModuleCheckers(Collection<TestRunResult> results) { 107 List<TestRunResult> moduleCheckers = new ArrayList<TestRunResult>(); 108 for (TestRunResult t : results) { 109 if (t.getName().startsWith(ITestSuite.MODULE_CHECKER_POST) 110 || t.getName().startsWith(ITestSuite.MODULE_CHECKER_PRE)) { 111 moduleCheckers.add(t); 112 } 113 } 114 results.removeAll(moduleCheckers); 115 return moduleCheckers; 116 } 117 118 @Override 119 public void invocationEnded(long elapsedTime) { 120 super.invocationEnded(elapsedTime); 121 mElapsedTime = elapsedTime; 122 123 // finalize and print results - general 124 Collection<TestRunResult> results = getRunResults(); 125 List<TestRunResult> moduleCheckers = extractModuleCheckers(results); 126 127 mTotalModules = results.size(); 128 129 for (TestRunResult moduleResult : results) { 130 if (!moduleResult.isRunFailure()) { 131 mCompleteModules++; 132 } else { 133 mFailedModule.put(moduleResult.getName(), moduleResult.getRunFailureMessage()); 134 } 135 mTotalTests += mModuleExpectedTests.get(moduleResult.getName()); 136 mPassedTests += moduleResult.getNumTestsInState(TestStatus.PASSED); 137 mFailedTests += moduleResult.getNumAllFailedTests(); 138 mSkippedTests += moduleResult.getNumTestsInState(TestStatus.IGNORED); 139 mAssumeFailureTests += moduleResult.getNumTestsInState(TestStatus.ASSUMPTION_FAILURE); 140 141 // Get the module metrics for target preparation 142 String prepTime = moduleResult.getRunMetrics().get(ModuleDefinition.PREPARATION_TIME); 143 String tearTime = moduleResult.getRunMetrics().get(ModuleDefinition.TEAR_DOWN_TIME); 144 if (prepTime != null && tearTime != null) { 145 mPreparationMap.put( 146 moduleResult.getName(), 147 new ModulePrepTimes(Long.parseLong(prepTime), Long.parseLong(tearTime))); 148 } 149 } 150 // print a short report summary 151 mSummary.append("\n============================================\n"); 152 mSummary.append("================= Results ==================\n"); 153 printModuleTestTime(results); 154 printTopSlowModules(results); 155 printPreparationMetrics(mPreparationMap); 156 printModuleCheckersMetric(moduleCheckers); 157 mSummary.append("=============== Summary ===============\n"); 158 mSummary.append( 159 String.format("Total Run time: %s\n", TimeUtil.formatElapsedTime(mElapsedTime))); 160 mSummary.append( 161 String.format("%s/%s modules completed\n", mCompleteModules, mTotalModules)); 162 if (!mFailedModule.isEmpty()) { 163 mSummary.append("Module(s) with run failure(s):\n"); 164 for (Entry<String, String> e : mFailedModule.entrySet()) { 165 mSummary.append(String.format(" %s: %s\n", e.getKey(), e.getValue())); 166 } 167 } 168 mSummary.append(String.format("Total Tests : %s\n", mTotalTests)); 169 mSummary.append(String.format("PASSED : %s\n", mPassedTests)); 170 mSummary.append(String.format("FAILED : %s\n", mFailedTests)); 171 172 if (mSkippedTests > 0l) { 173 mSummary.append(String.format("IGNORED : %s\n", mSkippedTests)); 174 } 175 if (mAssumeFailureTests > 0l) { 176 mSummary.append(String.format("ASSUMPTION_FAILURE: %s\n", mAssumeFailureTests)); 177 } 178 179 if (mCompleteModules != mTotalModules) { 180 mSummary.append( 181 "IMPORTANT: Some modules failed to run to completion, tests counts " 182 + "may be inaccurate.\n"); 183 } 184 185 for (Entry<Integer, List<String>> shard : 186 getInvocationContext().getShardsSerials().entrySet()) { 187 mSummary.append(String.format("Shard %s used: %s\n", shard.getKey(), shard.getValue())); 188 } 189 mSummary.append("============== End of Results ==============\n"); 190 mSummary.append("============================================\n"); 191 CLog.logAndDisplay(LogLevel.INFO, mSummary.toString()); 192 } 193 194 /** Displays the time consumed by each module to run. */ 195 private void printModuleTestTime(Collection<TestRunResult> results) { 196 List<TestRunResult> moduleTime = new ArrayList<>(); 197 moduleTime.addAll(results); 198 Collections.sort( 199 moduleTime, 200 new Comparator<TestRunResult>() { 201 @Override 202 public int compare(TestRunResult o1, TestRunResult o2) { 203 return (int) (o2.getElapsedTime() - o1.getElapsedTime()); 204 } 205 }); 206 long totalRunTime = 0l; 207 mSummary.append("=============== Consumed Time ==============\n"); 208 for (int i = 0; i < moduleTime.size(); i++) { 209 mSummary.append( 210 String.format( 211 " %s: %s\n", 212 moduleTime.get(i).getName(), 213 TimeUtil.formatElapsedTime(moduleTime.get(i).getElapsedTime()))); 214 totalRunTime += moduleTime.get(i).getElapsedTime(); 215 } 216 mSummary.append( 217 String.format( 218 "Total aggregated tests run time: %s\n", 219 TimeUtil.formatElapsedTime(totalRunTime))); 220 } 221 222 /** 223 * Display the average tests / second of modules because elapsed is not always relevant. (Some 224 * modules have way more test cases than others so only looking at elapsed time is not a good 225 * metric for slow modules). 226 */ 227 private void printTopSlowModules(Collection<TestRunResult> results) { 228 List<TestRunResult> moduleTime = new ArrayList<>(); 229 moduleTime.addAll(results); 230 // We don't consider module which runs in less than 5 sec. 231 for (TestRunResult t : results) { 232 if (t.getElapsedTime() < 5000) { 233 moduleTime.remove(t); 234 } 235 } 236 Collections.sort( 237 moduleTime, 238 new Comparator<TestRunResult>() { 239 @Override 240 public int compare(TestRunResult o1, TestRunResult o2) { 241 Float rate1 = ((float) o1.getNumTests() / o1.getElapsedTime()); 242 Float rate2 = ((float) o2.getNumTests() / o2.getElapsedTime()); 243 return rate1.compareTo(rate2); 244 } 245 }); 246 int maxModuleDisplay = moduleTime.size(); 247 if (maxModuleDisplay == 0) { 248 return; 249 } 250 mSummary.append( 251 String.format( 252 "============== TOP %s Slow Modules ==============\n", maxModuleDisplay)); 253 for (int i = 0; i < maxModuleDisplay; i++) { 254 mSummary.append( 255 String.format( 256 " %s: %.02f tests/sec [%s tests / %s msec]\n", 257 moduleTime.get(i).getName(), 258 (moduleTime.get(i).getNumTests() 259 / (moduleTime.get(i).getElapsedTime() / 1000f)), 260 moduleTime.get(i).getNumTests(), 261 moduleTime.get(i).getElapsedTime())); 262 } 263 } 264 265 /** Print the collected times for Module preparation and tear Down. */ 266 private void printPreparationMetrics(Map<String, ModulePrepTimes> metrics) { 267 if (metrics.isEmpty()) { 268 return; 269 } 270 mSummary.append("============== Modules Preparation Times ==============\n"); 271 long totalPrep = 0l; 272 long totalTear = 0l; 273 274 for (String moduleName : metrics.keySet()) { 275 mSummary.append( 276 String.format( 277 " %s => %s\n", moduleName, metrics.get(moduleName).toString())); 278 totalPrep += metrics.get(moduleName).mPrepTime; 279 totalTear += metrics.get(moduleName).mTearDownTime; 280 } 281 mSummary.append( 282 String.format( 283 "Total preparation time: %s || Total tear down time: %s\n", 284 TimeUtil.formatElapsedTime(totalPrep), 285 TimeUtil.formatElapsedTime(totalTear))); 286 mSummary.append("=======================================================\n"); 287 } 288 289 private void printModuleCheckersMetric(List<TestRunResult> moduleCheckerResults) { 290 if (moduleCheckerResults.isEmpty()) { 291 return; 292 } 293 mSummary.append("============== Modules Checkers Times ==============\n"); 294 long totalTime = 0l; 295 for (TestRunResult t : moduleCheckerResults) { 296 mSummary.append( 297 String.format( 298 " %s: %s\n", 299 t.getName(), TimeUtil.formatElapsedTime(t.getElapsedTime()))); 300 totalTime += t.getElapsedTime(); 301 } 302 mSummary.append( 303 String.format( 304 "Total module checkers time: %s\n", TimeUtil.formatElapsedTime(totalTime))); 305 mSummary.append("====================================================\n"); 306 } 307 308 /** Returns a map of modules abi: <module id, abi>. */ 309 public Map<String, IAbi> getModulesAbi() { 310 return mModuleAbi; 311 } 312 313 public int getTotalModules() { 314 return mTotalModules; 315 } 316 317 public int getCompleteModules() { 318 return mCompleteModules; 319 } 320 321 public long getTotalTests() { 322 return mTotalTests; 323 } 324 325 public long getPassedTests() { 326 return mPassedTests; 327 } 328 329 public long getFailedTests() { 330 return mFailedTests; 331 } 332 333 /** Object holder for the preparation and tear down time of one module. */ 334 public static class ModulePrepTimes { 335 336 public final long mPrepTime; 337 public final long mTearDownTime; 338 339 public ModulePrepTimes(long prepTime, long tearTime) { 340 mPrepTime = prepTime; 341 mTearDownTime = tearTime; 342 } 343 344 @Override 345 public String toString() { 346 return String.format("prep = %s ms || clean = %s ms", mPrepTime, mTearDownTime); 347 } 348 } 349 350 @Override 351 public TestSummary getSummary() { 352 TestSummary summary = new TestSummary(new TypedString(mSummary.toString(), Type.TEXT)); 353 summary.setSource(SUITE_REPORTER_SOURCE); 354 return summary; 355 } 356 357 /** Returns the start time of the run. */ 358 protected long getStartTime() { 359 return mStartTime; 360 } 361 362 /** Returns the elapsed time of the full run. */ 363 protected long getElapsedTime() { 364 return mElapsedTime; 365 } 366 } 367