1 /* 2 * Copyright (C) 2018 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.postprocessor; 17 18 import com.android.tradefed.config.Option; 19 import com.android.tradefed.invoker.IInvocationContext; 20 import com.android.tradefed.log.LogUtil.CLog; 21 import com.android.tradefed.metrics.proto.MetricMeasurement.DataType; 22 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 23 import com.android.tradefed.result.FailureDescription; 24 import com.android.tradefed.result.ILogSaver; 25 import com.android.tradefed.result.ILogSaverListener; 26 import com.android.tradefed.result.ITestInvocationListener; 27 import com.android.tradefed.result.InputStreamSource; 28 import com.android.tradefed.result.LogDataType; 29 import com.android.tradefed.result.LogFile; 30 import com.android.tradefed.result.TestDescription; 31 import com.android.tradefed.util.proto.TfMetricProtoUtil; 32 33 import com.google.common.collect.ArrayListMultimap; 34 import com.google.common.collect.ListMultimap; 35 36 import java.util.HashMap; 37 import java.util.LinkedHashMap; 38 import java.util.Map; 39 import java.util.Map.Entry; 40 41 /** 42 * The base {@link IPostProcessor} that every implementation should extend. Ensure that the post 43 * processing methods are called before the final result reporters. 44 * 45 * <p>TODO: expand to file post-processing too if needed. 46 */ 47 public abstract class BasePostProcessor implements IPostProcessor { 48 49 @Option(name = "disable", description = "disables the post processor.") 50 private boolean mDisable = false; 51 52 private ITestInvocationListener mForwarder; 53 private ArrayListMultimap<String, Metric> storedTestMetrics = ArrayListMultimap.create(); 54 private Map<TestDescription, Map<String, LogFile>> mTestLogs = new LinkedHashMap<>(); 55 private Map<String, LogFile> mRunLogs = new HashMap<>(); 56 // Keeps track of the current test; takes null value when the post processor is not in the scope 57 // of any test (i.e. before the first test, in-between tests and after the last test). 58 private TestDescription mCurrentTest = null; 59 60 /** {@inheritDoc} */ 61 @Override processRunMetricsAndLogs( HashMap<String, Metric> rawMetrics, Map<String, LogFile> runLogs)62 public abstract Map<String, Metric.Builder> processRunMetricsAndLogs( 63 HashMap<String, Metric> rawMetrics, Map<String, LogFile> runLogs); 64 65 /** {@inheritDoc} */ 66 @Override processTestMetricsAndLogs( TestDescription testDescription, HashMap<String, Metric> testMetrics, Map<String, LogFile> testLogs)67 public Map<String, Metric.Builder> processTestMetricsAndLogs( 68 TestDescription testDescription, 69 HashMap<String, Metric> testMetrics, 70 Map<String, LogFile> testLogs) { 71 return new HashMap<String, Metric.Builder>(); 72 } 73 74 /** {@inheritDoc} */ 75 @Override processAllTestMetricsAndLogs( ListMultimap<String, Metric> allTestMetrics, Map<TestDescription, Map<String, LogFile>> allTestLogs)76 public Map<String, Metric.Builder> processAllTestMetricsAndLogs( 77 ListMultimap<String, Metric> allTestMetrics, 78 Map<TestDescription, Map<String, LogFile>> allTestLogs) { 79 return new HashMap<String, Metric.Builder>(); 80 } 81 82 /** =================================== */ 83 /** {@inheritDoc} */ 84 @Override init(ITestInvocationListener listener)85 public final ITestInvocationListener init(ITestInvocationListener listener) { 86 mForwarder = listener; 87 return this; 88 } 89 90 /** =================================== */ 91 /** {@inheritDoc} */ 92 @Override isDisabled()93 public final boolean isDisabled() { 94 return mDisable; 95 } 96 97 /** =================================== */ 98 /** Invocation Listeners for forwarding */ 99 @Override invocationStarted(IInvocationContext context)100 public final void invocationStarted(IInvocationContext context) { 101 mForwarder.invocationStarted(context); 102 } 103 104 @Override invocationFailed(Throwable cause)105 public final void invocationFailed(Throwable cause) { 106 mForwarder.invocationFailed(cause); 107 } 108 109 @Override invocationFailed(FailureDescription failure)110 public final void invocationFailed(FailureDescription failure) { 111 mForwarder.invocationFailed(failure); 112 } 113 114 @Override invocationEnded(long elapsedTime)115 public final void invocationEnded(long elapsedTime) { 116 mForwarder.invocationEnded(elapsedTime); 117 } 118 119 @Override testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)120 public final void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { 121 mForwarder.testLog(dataName, dataType, dataStream); 122 } 123 124 @Override testModuleStarted(IInvocationContext moduleContext)125 public final void testModuleStarted(IInvocationContext moduleContext) { 126 mForwarder.testModuleStarted(moduleContext); 127 } 128 129 @Override testModuleEnded()130 public final void testModuleEnded() { 131 mForwarder.testModuleEnded(); 132 } 133 134 /** Test run callbacks */ 135 @Override testRunStarted(String runName, int testCount)136 public final void testRunStarted(String runName, int testCount) { 137 mForwarder.testRunStarted(runName, testCount); 138 } 139 140 @Override testRunStarted(String runName, int testCount, int attemptNumber)141 public final void testRunStarted(String runName, int testCount, int attemptNumber) { 142 mForwarder.testRunStarted(runName, testCount, attemptNumber); 143 } 144 145 @Override testRunFailed(String errorMessage)146 public final void testRunFailed(String errorMessage) { 147 mForwarder.testRunFailed(errorMessage); 148 } 149 150 @Override testRunFailed(FailureDescription failure)151 public final void testRunFailed(FailureDescription failure) { 152 mForwarder.testRunFailed(failure); 153 } 154 155 @Override testRunStopped(long elapsedTime)156 public final void testRunStopped(long elapsedTime) { 157 mForwarder.testRunStopped(elapsedTime); 158 } 159 160 @Override testRunEnded(long elapsedTime, Map<String, String> runMetrics)161 public final void testRunEnded(long elapsedTime, Map<String, String> runMetrics) { 162 testRunEnded(elapsedTime, TfMetricProtoUtil.upgradeConvert(runMetrics)); 163 } 164 165 @Override testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)166 public final void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) { 167 try { 168 HashMap<String, Metric> rawValues = getRawMetricsOnly(runMetrics); 169 // Add post-processed run metrics. 170 Map<String, Metric.Builder> postprocessedResults = 171 processRunMetricsAndLogs(rawValues, mRunLogs); 172 addProcessedMetricsToExistingMetrics(postprocessedResults, runMetrics); 173 // Add aggregated test metrics (results from post-processing all test metrics). 174 Map<String, Metric.Builder> aggregateResults = 175 processAllTestMetricsAndLogs(storedTestMetrics, mTestLogs); 176 addProcessedMetricsToExistingMetrics(aggregateResults, runMetrics); 177 } catch (RuntimeException e) { 178 // Prevent exception from messing up the status reporting. 179 CLog.e(e); 180 } finally { 181 // Clear out the stored test metrics. 182 storedTestMetrics.clear(); 183 // Clear out the stored test and run logs. 184 mTestLogs.clear(); 185 mRunLogs.clear(); 186 } 187 mForwarder.testRunEnded(elapsedTime, runMetrics); 188 } 189 190 /** Test cases callbacks */ 191 @Override testStarted(TestDescription test)192 public final void testStarted(TestDescription test) { 193 testStarted(test, System.currentTimeMillis()); 194 } 195 196 @Override testStarted(TestDescription test, long startTime)197 public final void testStarted(TestDescription test, long startTime) { 198 mCurrentTest = test; 199 if (mTestLogs.containsKey(test)) { 200 mTestLogs.get(test).clear(); 201 } else { 202 mTestLogs.put(test, new HashMap<String, LogFile>()); 203 } 204 205 mForwarder.testStarted(test, startTime); 206 } 207 208 @Override testFailed(TestDescription test, String trace)209 public final void testFailed(TestDescription test, String trace) { 210 mForwarder.testFailed(test, trace); 211 } 212 213 @Override testFailed(TestDescription test, FailureDescription failure)214 public final void testFailed(TestDescription test, FailureDescription failure) { 215 mForwarder.testFailed(test, failure); 216 } 217 218 @Override testEnded(TestDescription test, Map<String, String> testMetrics)219 public final void testEnded(TestDescription test, Map<String, String> testMetrics) { 220 testEnded(test, System.currentTimeMillis(), testMetrics); 221 } 222 223 @Override testEnded( TestDescription test, long endTime, Map<String, String> testMetrics)224 public final void testEnded( 225 TestDescription test, long endTime, Map<String, String> testMetrics) { 226 testEnded(test, endTime, TfMetricProtoUtil.upgradeConvert(testMetrics)); 227 } 228 229 @Override testEnded(TestDescription test, HashMap<String, Metric> testMetrics)230 public final void testEnded(TestDescription test, HashMap<String, Metric> testMetrics) { 231 testEnded(test, System.currentTimeMillis(), testMetrics); 232 } 233 234 @Override testEnded( TestDescription test, long endTime, HashMap<String, Metric> testMetrics)235 public final void testEnded( 236 TestDescription test, long endTime, HashMap<String, Metric> testMetrics) { 237 mCurrentTest = null; 238 try { 239 HashMap<String, Metric> rawValues = getRawMetricsOnly(testMetrics); 240 // Store the raw metrics from the test in storedTestMetrics for potential aggregation. 241 for (Map.Entry<String, Metric> entry : rawValues.entrySet()) { 242 storedTestMetrics.put(entry.getKey(), entry.getValue()); 243 } 244 Map<String, Metric.Builder> results = 245 processTestMetricsAndLogs( 246 test, 247 rawValues, 248 mTestLogs.containsKey(test) 249 ? mTestLogs.get(test) 250 : new HashMap<String, LogFile>()); 251 for (Entry<String, Metric.Builder> newEntry : results.entrySet()) { 252 String newKey = newEntry.getKey(); 253 if (testMetrics.containsKey(newKey)) { 254 CLog.e( 255 "Key '%s' is already asssociated with a metric and will not be " 256 + "replaced.", 257 newKey); 258 continue; 259 } 260 // Force the metric to 'processed' since generated in a post-processor. 261 Metric newMetric = 262 newEntry.getValue() 263 .setType(getMetricType()) 264 .build(); 265 testMetrics.put(newKey, newMetric); 266 } 267 } catch (RuntimeException e) { 268 // Prevent exception from messing up the status reporting. 269 CLog.e(e); 270 } 271 mForwarder.testEnded(test, endTime, testMetrics); 272 } 273 274 @Override testAssumptionFailure(TestDescription test, String trace)275 public final void testAssumptionFailure(TestDescription test, String trace) { 276 mForwarder.testAssumptionFailure(test, trace); 277 } 278 279 @Override testAssumptionFailure(TestDescription test, FailureDescription failure)280 public final void testAssumptionFailure(TestDescription test, FailureDescription failure) { 281 mForwarder.testAssumptionFailure(test, failure); 282 } 283 284 @Override testIgnored(TestDescription test)285 public final void testIgnored(TestDescription test) { 286 mForwarder.testIgnored(test); 287 } 288 289 @Override setLogSaver(ILogSaver logSaver)290 public final void setLogSaver(ILogSaver logSaver) { 291 if (mForwarder instanceof ILogSaverListener) { 292 ((ILogSaverListener) mForwarder).setLogSaver(logSaver); 293 } 294 } 295 296 @Override testLogSaved( String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile)297 public final void testLogSaved( 298 String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile) { 299 if (mForwarder instanceof ILogSaverListener) { 300 ((ILogSaverListener) mForwarder).testLogSaved(dataName, dataType, dataStream, logFile); 301 } 302 } 303 304 /** 305 * {@inheritDoc} 306 * 307 * <p>Updates the log-to-test assocation. If this method is called during a test, then the log 308 * belongs to the test; otherwise it will be a run log. 309 */ 310 @Override logAssociation(String dataName, LogFile logFile)311 public final void logAssociation(String dataName, LogFile logFile) { 312 // mCurrentTest is null only outside the scope of a test. 313 if (mCurrentTest != null) { 314 mTestLogs.get(mCurrentTest).put(dataName, logFile); 315 } else { 316 mRunLogs.put(dataName, logFile); 317 } 318 319 if (mForwarder instanceof ILogSaverListener) { 320 ((ILogSaverListener) mForwarder).logAssociation(dataName, logFile); 321 } 322 } 323 324 // Internal utilities 325 326 /** 327 * We only allow post-processing of raw values. Already processed values will not be considered. 328 */ getRawMetricsOnly(HashMap<String, Metric> runMetrics)329 private HashMap<String, Metric> getRawMetricsOnly(HashMap<String, Metric> runMetrics) { 330 HashMap<String, Metric> rawMetrics = new HashMap<>(); 331 for (Entry<String, Metric> entry : runMetrics.entrySet()) { 332 if (DataType.RAW.equals(entry.getValue().getType())) { 333 rawMetrics.put(entry.getKey(), entry.getValue()); 334 } 335 } 336 return rawMetrics; 337 } 338 339 /** Add processed metrics to the metrics to be reported. */ addProcessedMetricsToExistingMetrics( Map<String, Metric.Builder> processed, Map<String, Metric> existing)340 private void addProcessedMetricsToExistingMetrics( 341 Map<String, Metric.Builder> processed, Map<String, Metric> existing) { 342 for (Entry<String, Metric.Builder> newEntry : processed.entrySet()) { 343 String newKey = newEntry.getKey(); 344 if (existing.containsKey(newKey)) { 345 CLog.e( 346 "Key '%s' is already asssociated with a metric and will not be " 347 + "replaced.", 348 newKey); 349 continue; 350 } 351 // Force the metric to 'processed' since generated in a post-processor. 352 Metric newMetric = 353 newEntry.getValue() 354 .setType(getMetricType()) 355 .build(); 356 existing.put(newKey, newMetric); 357 } 358 } 359 360 /** 361 * Override this method to change the metric type if needed. By default metric is set to 362 * processed type. 363 */ getMetricType()364 protected DataType getMetricType() { 365 return DataType.PROCESSED; 366 } 367 } 368