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