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.ILogSaver; 24 import com.android.tradefed.result.ILogSaverListener; 25 import com.android.tradefed.result.ITestInvocationListener; 26 import com.android.tradefed.result.InputStreamSource; 27 import com.android.tradefed.result.LogDataType; 28 import com.android.tradefed.result.LogFile; 29 import com.android.tradefed.result.TestDescription; 30 import com.android.tradefed.util.proto.TfMetricProtoUtil; 31 32 import com.google.common.collect.ArrayListMultimap; 33 import com.google.common.collect.ListMultimap; 34 35 import java.util.HashMap; 36 import java.util.Map; 37 import java.util.Map.Entry; 38 39 /** 40 * The base {@link IPostProcessor} that every implementation should extend. Ensure that the post 41 * processing methods are called before the final result reporters. 42 * 43 * <p>TODO: expand to file post-processing too if needed. 44 */ 45 public abstract class BasePostProcessor implements IPostProcessor { 46 47 @Option(name = "disable", description = "disables the post processor.") 48 private boolean mDisable = false; 49 50 private ITestInvocationListener mForwarder; 51 private ArrayListMultimap<String, Metric> storedTestMetrics = ArrayListMultimap.create(); 52 53 /** {@inheritDoc} */ 54 @Override processRunMetrics( HashMap<String, Metric> rawMetrics)55 public abstract Map<String, Metric.Builder> processRunMetrics( 56 HashMap<String, Metric> rawMetrics); 57 58 /** {@inhericDoc} */ 59 @Override processTestMetrics( TestDescription testDescription, HashMap<String, Metric> testMetrics)60 public Map<String, Metric.Builder> processTestMetrics( 61 TestDescription testDescription, HashMap<String, Metric> testMetrics) { 62 return new HashMap<String, Metric.Builder>(); 63 } 64 65 /** {@inheritDoc} */ 66 @Override processAllTestMetrics( ListMultimap<String, Metric> allTestMetrics)67 public Map<String, Metric.Builder> processAllTestMetrics( 68 ListMultimap<String, Metric> allTestMetrics) { 69 return new HashMap<String, Metric.Builder>(); 70 } 71 72 /** =================================== */ 73 /** {@inheritDoc} */ 74 @Override init(ITestInvocationListener listener)75 public final ITestInvocationListener init(ITestInvocationListener listener) { 76 mForwarder = listener; 77 return this; 78 } 79 80 /** =================================== */ 81 /** {@inheritDoc} */ 82 @Override isDisabled()83 public final boolean isDisabled() { 84 return mDisable; 85 } 86 87 /** =================================== */ 88 /** Invocation Listeners for forwarding */ 89 @Override invocationStarted(IInvocationContext context)90 public final void invocationStarted(IInvocationContext context) { 91 mForwarder.invocationStarted(context); 92 } 93 94 @Override invocationFailed(Throwable cause)95 public final void invocationFailed(Throwable cause) { 96 mForwarder.invocationFailed(cause); 97 } 98 99 @Override invocationEnded(long elapsedTime)100 public final void invocationEnded(long elapsedTime) { 101 mForwarder.invocationEnded(elapsedTime); 102 } 103 104 @Override testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)105 public final void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { 106 mForwarder.testLog(dataName, dataType, dataStream); 107 } 108 109 /** Test run callbacks */ 110 @Override testRunStarted(String runName, int testCount)111 public final void testRunStarted(String runName, int testCount) { 112 mForwarder.testRunStarted(runName, testCount); 113 } 114 115 @Override testRunStarted(String runName, int testCount, int attemptNumber)116 public final void testRunStarted(String runName, int testCount, int attemptNumber) { 117 mForwarder.testRunStarted(runName, testCount, attemptNumber); 118 } 119 120 @Override testRunFailed(String errorMessage)121 public final void testRunFailed(String errorMessage) { 122 mForwarder.testRunFailed(errorMessage); 123 } 124 125 @Override testRunStopped(long elapsedTime)126 public final void testRunStopped(long elapsedTime) { 127 mForwarder.testRunStopped(elapsedTime); 128 } 129 130 @Override testRunEnded(long elapsedTime, Map<String, String> runMetrics)131 public final void testRunEnded(long elapsedTime, Map<String, String> runMetrics) { 132 testRunEnded(elapsedTime, TfMetricProtoUtil.upgradeConvert(runMetrics)); 133 } 134 135 @Override testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)136 public final void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) { 137 try { 138 HashMap<String, Metric> rawValues = getRawMetricsOnly(runMetrics); 139 // Add post-processed run metrics. 140 Map<String, Metric.Builder> postprocessedResults = processRunMetrics(rawValues); 141 addProcessedMetricsToExistingMetrics(postprocessedResults, runMetrics); 142 // Add aggregated test metrics (results from post-processing all test metrics). 143 Map<String, Metric.Builder> aggregateResults = processAllTestMetrics(storedTestMetrics); 144 addProcessedMetricsToExistingMetrics(aggregateResults, runMetrics); 145 } catch (RuntimeException e) { 146 // Prevent exception from messing up the status reporting. 147 CLog.e(e); 148 } finally { 149 // Clear out the stored test metrics. 150 storedTestMetrics.clear(); 151 } 152 mForwarder.testRunEnded(elapsedTime, runMetrics); 153 } 154 155 /** Test cases callbacks */ 156 @Override testStarted(TestDescription test)157 public final void testStarted(TestDescription test) { 158 testStarted(test, System.currentTimeMillis()); 159 } 160 161 @Override testStarted(TestDescription test, long startTime)162 public final void testStarted(TestDescription test, long startTime) { 163 mForwarder.testStarted(test, startTime); 164 } 165 166 @Override testFailed(TestDescription test, String trace)167 public final void testFailed(TestDescription test, String trace) { 168 mForwarder.testFailed(test, trace); 169 } 170 171 @Override testEnded(TestDescription test, Map<String, String> testMetrics)172 public final void testEnded(TestDescription test, Map<String, String> testMetrics) { 173 testEnded(test, System.currentTimeMillis(), testMetrics); 174 } 175 176 @Override testEnded( TestDescription test, long endTime, Map<String, String> testMetrics)177 public final void testEnded( 178 TestDescription test, long endTime, Map<String, String> testMetrics) { 179 testEnded(test, endTime, TfMetricProtoUtil.upgradeConvert(testMetrics)); 180 } 181 182 @Override testEnded(TestDescription test, HashMap<String, Metric> testMetrics)183 public final void testEnded(TestDescription test, HashMap<String, Metric> testMetrics) { 184 testEnded(test, System.currentTimeMillis(), testMetrics); 185 } 186 187 @Override testEnded( TestDescription test, long endTime, HashMap<String, Metric> testMetrics)188 public final void testEnded( 189 TestDescription test, long endTime, HashMap<String, Metric> testMetrics) { 190 try { 191 HashMap<String, Metric> rawValues = getRawMetricsOnly(testMetrics); 192 // Store the raw metrics from the test in storedTestMetrics for potential aggregation. 193 for (Map.Entry<String, Metric> entry : rawValues.entrySet()) { 194 storedTestMetrics.put(entry.getKey(), entry.getValue()); 195 } 196 Map<String, Metric.Builder> results = processTestMetrics(test, rawValues); 197 for (Entry<String, Metric.Builder> newEntry : results.entrySet()) { 198 String newKey = newEntry.getKey(); 199 if (testMetrics.containsKey(newKey)) { 200 CLog.e( 201 "Key '%s' is already asssociated with a metric and will not be " 202 + "replaced.", 203 newKey); 204 continue; 205 } 206 // Force the metric to 'processed' since generated in a post-processor. 207 Metric newMetric = newEntry.getValue().setType(DataType.PROCESSED).build(); 208 testMetrics.put(newKey, newMetric); 209 } 210 } catch (RuntimeException e) { 211 // Prevent exception from messing up the status reporting. 212 CLog.e(e); 213 } 214 mForwarder.testEnded(test, endTime, testMetrics); 215 } 216 217 @Override testAssumptionFailure(TestDescription test, String trace)218 public final void testAssumptionFailure(TestDescription test, String trace) { 219 mForwarder.testAssumptionFailure(test, trace); 220 } 221 222 @Override testIgnored(TestDescription test)223 public final void testIgnored(TestDescription test) { 224 mForwarder.testIgnored(test); 225 } 226 227 @Override setLogSaver(ILogSaver logSaver)228 public final void setLogSaver(ILogSaver logSaver) { 229 if (mForwarder instanceof ILogSaverListener) { 230 ((ILogSaverListener) mForwarder).setLogSaver(logSaver); 231 } 232 } 233 234 @Override testLogSaved( String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile)235 public final void testLogSaved( 236 String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile) { 237 if (mForwarder instanceof ILogSaverListener) { 238 ((ILogSaverListener) mForwarder).testLogSaved(dataName, dataType, dataStream, logFile); 239 } 240 } 241 242 @Override logAssociation(String dataName, LogFile logFile)243 public final void logAssociation(String dataName, LogFile logFile) { 244 if (mForwarder instanceof ILogSaverListener) { 245 ((ILogSaverListener) mForwarder).logAssociation(dataName, logFile); 246 } 247 } 248 249 // Internal utilities 250 251 /** 252 * We only allow post-processing of raw values. Already processed values will not be considered. 253 */ getRawMetricsOnly(HashMap<String, Metric> runMetrics)254 private HashMap<String, Metric> getRawMetricsOnly(HashMap<String, Metric> runMetrics) { 255 HashMap<String, Metric> rawMetrics = new HashMap<>(); 256 for (Entry<String, Metric> entry : runMetrics.entrySet()) { 257 if (DataType.RAW.equals(entry.getValue().getType())) { 258 rawMetrics.put(entry.getKey(), entry.getValue()); 259 } 260 } 261 return rawMetrics; 262 } 263 264 /** Add processed metrics to the metrics to be reported. */ addProcessedMetricsToExistingMetrics( Map<String, Metric.Builder> processed, Map<String, Metric> existing)265 private void addProcessedMetricsToExistingMetrics( 266 Map<String, Metric.Builder> processed, Map<String, Metric> existing) { 267 for (Entry<String, Metric.Builder> newEntry : processed.entrySet()) { 268 String newKey = newEntry.getKey(); 269 if (existing.containsKey(newKey)) { 270 CLog.e( 271 "Key '%s' is already asssociated with a metric and will not be " 272 + "replaced.", 273 newKey); 274 continue; 275 } 276 // Force the metric to 'processed' since generated in a post-processor. 277 Metric newMetric = newEntry.getValue().setType(DataType.PROCESSED).build(); 278 existing.put(newKey, newMetric); 279 } 280 } 281 } 282