• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.invoker.logger.CurrentInvocation;
21 import com.android.tradefed.invoker.logger.InvocationMetricLogger;
22 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
23 import com.android.tradefed.invoker.tracing.CloseableTraceScope;
24 import com.android.tradefed.log.LogUtil.CLog;
25 import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
26 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
27 import com.android.tradefed.result.FailureDescription;
28 import com.android.tradefed.result.FileInputStreamSource;
29 import com.android.tradefed.result.ILogSaver;
30 import com.android.tradefed.result.ILogSaverListener;
31 import com.android.tradefed.result.ITestInvocationListener;
32 import com.android.tradefed.result.InputStreamSource;
33 import com.android.tradefed.result.LogDataType;
34 import com.android.tradefed.result.LogFile;
35 import com.android.tradefed.result.TestDescription;
36 import com.android.tradefed.result.skipped.SkipReason;
37 import com.android.tradefed.util.FileUtil;
38 import com.android.tradefed.util.proto.TfMetricProtoUtil;
39 
40 import com.google.common.base.Strings;
41 import com.google.common.collect.ArrayListMultimap;
42 import com.google.common.collect.ListMultimap;
43 
44 import java.io.File;
45 import java.io.IOException;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.LinkedHashMap;
49 import java.util.Map;
50 import java.util.Map.Entry;
51 import java.util.Set;
52 
53 /**
54  * The base {@link IPostProcessor} that every implementation should extend. Ensure that the post
55  * processing methods are called before the final result reporters.
56  */
57 public abstract class BasePostProcessor implements IPostProcessor {
58 
59     @Option(name = "disable", description = "disables the post processor.")
60     private boolean mDisable = false;
61 
62     private ITestInvocationListener mForwarder;
63     private ArrayListMultimap<String, Metric> storedTestMetrics = ArrayListMultimap.create();
64     private Map<TestDescription, Map<String, LogFile>> mTestLogs = new LinkedHashMap<>();
65     private Map<String, LogFile> mRunLogs = new HashMap<>();
66 
67     private Map<String, LogFile> mCopiedLogs = new HashMap<>();
68     private Set<LogFile> mToDelete = new HashSet<>();
69 
70     // Keeps track of the current test; takes null value when the post processor is not in the scope
71     // of any test (i.e. before the first test, in-between tests and after the last test).
72     private TestDescription mCurrentTest = null;
73 
74     private ILogSaver mLogSaver = null;
75 
76     // Marks whether the post processor is actively post processing, which changes some event
77     // forwarding behavior.
78     private boolean mIsPostProcessing = false;
79 
80     private String mRunName;
81 
82     /** {@inheritDoc} */
83     @Override
processRunMetricsAndLogs( HashMap<String, Metric> rawMetrics, Map<String, LogFile> runLogs)84     public abstract Map<String, Metric.Builder> processRunMetricsAndLogs(
85             HashMap<String, Metric> rawMetrics, Map<String, LogFile> runLogs);
86 
87     /** {@inheritDoc} */
88     @Override
processTestMetricsAndLogs( TestDescription testDescription, HashMap<String, Metric> testMetrics, Map<String, LogFile> testLogs)89     public Map<String, Metric.Builder> processTestMetricsAndLogs(
90             TestDescription testDescription,
91             HashMap<String, Metric> testMetrics,
92             Map<String, LogFile> testLogs) {
93         return new HashMap<String, Metric.Builder>();
94     }
95 
96     /** {@inheritDoc} */
97     @Override
processAllTestMetricsAndLogs( ListMultimap<String, Metric> allTestMetrics, Map<TestDescription, Map<String, LogFile>> allTestLogs)98     public Map<String, Metric.Builder> processAllTestMetricsAndLogs(
99             ListMultimap<String, Metric> allTestMetrics,
100             Map<TestDescription, Map<String, LogFile>> allTestLogs) {
101         return new HashMap<String, Metric.Builder>();
102     }
103 
104     /** =================================== */
105     /** {@inheritDoc} */
106     @Override
init(ITestInvocationListener listener)107     public final ITestInvocationListener init(ITestInvocationListener listener) {
108         long start = System.currentTimeMillis();
109         setUp();
110         InvocationMetricLogger.addInvocationMetrics(
111                 InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start);
112         mForwarder = listener;
113         return this;
114     }
115 
116     /** =================================== */
117     /** {@inheritDoc} */
118     @Override
isDisabled()119     public final boolean isDisabled() {
120         return mDisable;
121     }
122 
123     /** =================================== */
124     /** Invocation Listeners for forwarding */
125     @Override
invocationStarted(IInvocationContext context)126     public final void invocationStarted(IInvocationContext context) {
127         mForwarder.invocationStarted(context);
128     }
129 
130     @Override
invocationFailed(Throwable cause)131     public final void invocationFailed(Throwable cause) {
132         mForwarder.invocationFailed(cause);
133     }
134 
135     @Override
invocationFailed(FailureDescription failure)136     public final void invocationFailed(FailureDescription failure) {
137         mForwarder.invocationFailed(failure);
138     }
139 
140     @Override
invocationSkipped(SkipReason reason)141     public void invocationSkipped(SkipReason reason) {
142         mForwarder.invocationSkipped(reason);
143     }
144 
145     @Override
invocationEnded(long elapsedTime)146     public final void invocationEnded(long elapsedTime) {
147         mForwarder.invocationEnded(elapsedTime);
148     }
149 
150     /** Use this method to log a file from the PostProcessor implementation. */
151     @Override
testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)152     public final void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) {
153         // If currently post processing, the file should be saved; otherwise, the file should have
154         // been logged elsewhere and this call only needs to be forwarded.
155         if (mIsPostProcessing) {
156             CLog.i("Saving file with data name %s in post processor.", dataName);
157             if (mLogSaver != null) {
158                 try {
159                     LogFile log = null;
160                     if (dataStream instanceof FileInputStreamSource) {
161                         log =
162                                 mLogSaver.saveLogFile(
163                                         dataName,
164                                         dataType,
165                                         ((FileInputStreamSource) dataStream).getFile());
166                     } else {
167                         log =
168                                 mLogSaver.saveLogData(
169                                         dataName, dataType, dataStream.createInputStream());
170                     }
171                     testLogSaved(dataName, dataType, dataStream, log);
172                     logAssociation(dataName, log);
173                     // do not forward if log saved
174                     return;
175                 } catch (IOException e) {
176                     CLog.e("Failed to save log file %s.", dataName);
177                     CLog.e(e);
178                 }
179             } else {
180                 CLog.e("Attempting to save log in post processor when its log saver is not set.");
181             }
182         }
183         mForwarder.testLog(dataName, dataType, dataStream);
184     }
185 
186     @Override
testModuleStarted(IInvocationContext moduleContext)187     public final void testModuleStarted(IInvocationContext moduleContext) {
188         mForwarder.testModuleStarted(moduleContext);
189     }
190 
191     @Override
testModuleEnded()192     public final void testModuleEnded() {
193         mForwarder.testModuleEnded();
194     }
195 
196     /** Test run callbacks */
197     @Override
testRunStarted(String runName, int testCount)198     public final void testRunStarted(String runName, int testCount) {
199         testRunStarted(runName, testCount, 0, System.currentTimeMillis());
200     }
201 
202     @Override
testRunStarted(String runName, int testCount, int attemptNumber)203     public final void testRunStarted(String runName, int testCount, int attemptNumber) {
204         testRunStarted(runName, testCount, attemptNumber, System.currentTimeMillis());
205     }
206 
207     @Override
testRunStarted( String runName, int testCount, int attemptNumber, long startTime)208     public final void testRunStarted(
209             String runName, int testCount, int attemptNumber, long startTime) {
210         mRunName = runName;
211         mForwarder.testRunStarted(runName, testCount, attemptNumber, startTime);
212     }
213 
214     @Override
testRunFailed(String errorMessage)215     public final void testRunFailed(String errorMessage) {
216         mForwarder.testRunFailed(errorMessage);
217     }
218 
219     @Override
testRunFailed(FailureDescription failure)220     public final void testRunFailed(FailureDescription failure) {
221         mForwarder.testRunFailed(failure);
222     }
223 
224     @Override
testRunStopped(long elapsedTime)225     public final void testRunStopped(long elapsedTime) {
226         mForwarder.testRunStopped(elapsedTime);
227     }
228 
229     @Override
testRunEnded(long elapsedTime, Map<String, String> runMetrics)230     public final void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
231         testRunEnded(elapsedTime, TfMetricProtoUtil.upgradeConvert(runMetrics));
232     }
233 
234     @Override
testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)235     public final void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
236         long start = System.currentTimeMillis();
237         mIsPostProcessing = true;
238         try (CloseableTraceScope ignored =
239                 new CloseableTraceScope("run_processor_" + this.getClass().getSimpleName())) {
240             HashMap<String, Metric> rawValues = getRawMetricsOnly(runMetrics);
241             // Add post-processed run metrics.
242             Map<String, Metric.Builder> postprocessedResults =
243                     processRunMetricsAndLogs(rawValues, mRunLogs);
244             addProcessedMetricsToExistingMetrics(postprocessedResults, runMetrics);
245             // Add aggregated test metrics (results from post-processing all test metrics).
246             Map<String, Metric.Builder> aggregateResults =
247                     processAllTestMetricsAndLogs(storedTestMetrics, mTestLogs);
248             addProcessedMetricsToExistingMetrics(aggregateResults, runMetrics);
249         } catch (RuntimeException e) {
250             // Prevent exception from messing up the status reporting.
251             CLog.e(e);
252         } finally {
253             // Clear out the stored test metrics.
254             storedTestMetrics.clear();
255             // Clear out the stored test and run logs.
256             mTestLogs.clear();
257             mRunLogs.clear();
258             // Delete all the copies
259             cleanUp();
260             InvocationMetricLogger.addInvocationMetrics(
261                     InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start);
262         }
263         mIsPostProcessing = false;
264         mForwarder.testRunEnded(elapsedTime, runMetrics);
265     }
266 
267     /** Test cases callbacks */
268     @Override
testStarted(TestDescription test)269     public final void testStarted(TestDescription test) {
270         testStarted(test, System.currentTimeMillis());
271     }
272 
273     @Override
testStarted(TestDescription test, long startTime)274     public final void testStarted(TestDescription test, long startTime) {
275         mCurrentTest = test;
276         if (mTestLogs.containsKey(test)) {
277             mTestLogs.get(test).clear();
278         } else {
279             mTestLogs.put(test, new HashMap<String, LogFile>());
280         }
281         mForwarder.testStarted(test, startTime);
282     }
283 
284     @Override
testFailed(TestDescription test, String trace)285     public final void testFailed(TestDescription test, String trace) {
286         mForwarder.testFailed(test, trace);
287     }
288 
289     @Override
testFailed(TestDescription test, FailureDescription failure)290     public final void testFailed(TestDescription test, FailureDescription failure) {
291         mForwarder.testFailed(test, failure);
292     }
293 
294     @Override
testEnded(TestDescription test, Map<String, String> testMetrics)295     public final void testEnded(TestDescription test, Map<String, String> testMetrics) {
296         testEnded(test, System.currentTimeMillis(), testMetrics);
297     }
298 
299     @Override
testEnded( TestDescription test, long endTime, Map<String, String> testMetrics)300     public final void testEnded(
301             TestDescription test, long endTime, Map<String, String> testMetrics) {
302         testEnded(test, endTime, TfMetricProtoUtil.upgradeConvert(testMetrics));
303     }
304 
305     @Override
testEnded(TestDescription test, HashMap<String, Metric> testMetrics)306     public final void testEnded(TestDescription test, HashMap<String, Metric> testMetrics) {
307         testEnded(test, System.currentTimeMillis(), testMetrics);
308     }
309 
310     @Override
testEnded( TestDescription test, long endTime, HashMap<String, Metric> testMetrics)311     public final void testEnded(
312             TestDescription test, long endTime, HashMap<String, Metric> testMetrics) {
313         mIsPostProcessing = true;
314         long start = System.currentTimeMillis();
315         try {
316             HashMap<String, Metric> rawValues = getRawMetricsOnly(testMetrics);
317             // Store the raw metrics from the test in storedTestMetrics for potential aggregation.
318             for (Map.Entry<String, Metric> entry : rawValues.entrySet()) {
319                 storedTestMetrics.put(entry.getKey(), entry.getValue());
320             }
321             Map<String, Metric.Builder> results =
322                     processTestMetricsAndLogs(
323                             test,
324                             rawValues,
325                             mTestLogs.containsKey(test)
326                                     ? mTestLogs.get(test)
327                                     : new HashMap<String, LogFile>());
328             for (Entry<String, Metric.Builder> newEntry : results.entrySet()) {
329                 String newKey = newEntry.getKey();
330                 if (testMetrics.containsKey(newKey)) {
331                     CLog.e(
332                             "Key '%s' is already asssociated with a metric and will not be "
333                                     + "replaced.",
334                             newKey);
335                     continue;
336                 }
337                 // Force the metric to 'processed' since generated in a post-processor.
338                 Metric newMetric =
339                         newEntry.getValue()
340                                 .setType(getMetricType())
341                                 .build();
342                 testMetrics.put(newKey, newMetric);
343             }
344         } catch (RuntimeException e) {
345             // Prevent exception from messing up the status reporting.
346             CLog.e(e);
347         } finally {
348             InvocationMetricLogger.addInvocationMetrics(
349                     InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start);
350         }
351         mIsPostProcessing = false;
352         mCurrentTest = null;
353         mForwarder.testEnded(test, endTime, testMetrics);
354     }
355 
356     @Override
testAssumptionFailure(TestDescription test, String trace)357     public final void testAssumptionFailure(TestDescription test, String trace) {
358         mForwarder.testAssumptionFailure(test, trace);
359     }
360 
361     @Override
testAssumptionFailure(TestDescription test, FailureDescription failure)362     public final void testAssumptionFailure(TestDescription test, FailureDescription failure) {
363         mForwarder.testAssumptionFailure(test, failure);
364     }
365 
366     @Override
testIgnored(TestDescription test)367     public final void testIgnored(TestDescription test) {
368         mForwarder.testIgnored(test);
369     }
370 
371     @Override
testSkipped(TestDescription test, SkipReason reason)372     public final void testSkipped(TestDescription test, SkipReason reason) {
373         mForwarder.testSkipped(test, reason);
374     }
375 
376     /**
377      * Override this method in the child post processors to initialize before the test runs.
378      */
setUp()379     public void setUp() {
380         // NO-OP by default
381     }
382 
383     @Override
setLogSaver(ILogSaver logSaver)384     public final void setLogSaver(ILogSaver logSaver) {
385         mLogSaver = logSaver;
386         if (mForwarder instanceof ILogSaverListener) {
387             ((ILogSaverListener) mForwarder).setLogSaver(logSaver);
388         }
389     }
390 
391     @Override
testLogSaved( String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile)392     public final void testLogSaved(
393             String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile) {
394         if (!mIsPostProcessing) {
395             mCopiedLogs.put(dataName, copyLogFile(logFile));
396         }
397         if (mForwarder instanceof ILogSaverListener) {
398             ((ILogSaverListener) mForwarder).testLogSaved(dataName, dataType, dataStream, logFile);
399         }
400     }
401 
402     /**
403      * {@inheritDoc}
404      *
405      * <p>Updates the log-to-test assocation. If this method is called during a test, then the log
406      * belongs to the test; otherwise it will be a run log.
407      */
408     @Override
logAssociation(String dataName, LogFile logFile)409     public final void logAssociation(String dataName, LogFile logFile) {
410         // Only associate files created outside of the current post processor.
411         if (!mIsPostProcessing) {
412             LogFile copyFile = mCopiedLogs.remove(dataName);
413             if (copyFile != null) {
414                 mToDelete.add(copyFile);
415                 // mCurrentTest is null only outside the scope of a test.
416                 if (mCurrentTest != null) {
417                     mTestLogs.get(mCurrentTest).put(dataName, copyFile);
418                 } else {
419                     mRunLogs.put(dataName, copyFile);
420                 }
421             }
422         }
423 
424         if (mForwarder instanceof ILogSaverListener) {
425             ((ILogSaverListener) mForwarder).logAssociation(dataName, logFile);
426         }
427     }
428 
429     // Internal utilities
430 
431     /**
432      * We only allow post-processing of raw values. Already processed values will not be considered.
433      */
getRawMetricsOnly(HashMap<String, Metric> runMetrics)434     private HashMap<String, Metric> getRawMetricsOnly(HashMap<String, Metric> runMetrics) {
435         HashMap<String, Metric> rawMetrics = new HashMap<>();
436         for (Entry<String, Metric> entry : runMetrics.entrySet()) {
437             if (DataType.RAW.equals(entry.getValue().getType())) {
438                 rawMetrics.put(entry.getKey(), entry.getValue());
439             }
440         }
441         return rawMetrics;
442     }
443 
444     /** Add processed metrics to the metrics to be reported. */
addProcessedMetricsToExistingMetrics( Map<String, Metric.Builder> processed, Map<String, Metric> existing)445     private void addProcessedMetricsToExistingMetrics(
446             Map<String, Metric.Builder> processed, Map<String, Metric> existing) {
447         for (Entry<String, Metric.Builder> newEntry : processed.entrySet()) {
448             String newKey = newEntry.getKey();
449             if (existing.containsKey(newKey)) {
450                 CLog.e(
451                         "Key '%s' is already asssociated with a metric and will not be "
452                                 + "replaced.",
453                         newKey);
454                 continue;
455             }
456             // Force the metric to 'processed' since generated in a post-processor.
457             Metric newMetric =
458                     newEntry.getValue()
459                             .setType(getMetricType())
460                             .build();
461             existing.put(newKey, newMetric);
462         }
463     }
464 
465     /**
466      * Override this method to change the metric type if needed. By default metric is set to
467      * processed type.
468      */
getMetricType()469     protected DataType getMetricType() {
470         return DataType.PROCESSED;
471     }
472 
473     /*
474      * TODO: b/191168103 Remove this method after run name dependency is removed from metric file
475      * post processor.
476      */
getRunName()477     protected String getRunName() {
478         return mRunName;
479     }
480 
cleanUp()481     protected void cleanUp() {
482         // Delete all the copies
483         for (LogFile f : mToDelete) {
484             FileUtil.deleteFile(new File(f.getPath()));
485         }
486         mToDelete.clear();
487     }
488 
copyLogFile(LogFile original)489     private LogFile copyLogFile(LogFile original) {
490         if (Strings.isNullOrEmpty(original.getPath())) {
491             CLog.d("%s doesn't exist and can't be copied for post processor.", original);
492             return null;
493         }
494         File originalFile = new File(original.getPath());
495         if (!originalFile.exists()) {
496             CLog.d("%s doesn't exist and can't be copied for post processor.", original.getPath());
497             return null;
498         }
499         try {
500             File copy =
501                     FileUtil.createTempFile(
502                             originalFile.getName(), "", CurrentInvocation.getWorkFolder());
503             copy.delete();
504             FileUtil.hardlinkFile(originalFile, copy);
505             return new LogFile(
506                     copy.getAbsolutePath(),
507                     original.getUrl(),
508                     original.isCompressed(),
509                     original.getType(),
510                     original.getSize());
511         } catch (IOException e) {
512             CLog.e(e);
513         }
514         return null;
515     }
516 }
517