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.device.metric;
17 
18 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.build.IDeviceBuildInfo;
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.device.ITestDevice;
23 import com.android.tradefed.device.StubDevice;
24 import com.android.tradefed.invoker.IInvocationContext;
25 import com.android.tradefed.log.LogUtil.CLog;
26 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
27 import com.android.tradefed.result.FailureDescription;
28 import com.android.tradefed.result.ITestInvocationListener;
29 import com.android.tradefed.result.InputStreamSource;
30 import com.android.tradefed.result.LogDataType;
31 import com.android.tradefed.result.TestDescription;
32 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
33 import com.android.tradefed.testtype.suite.ModuleDefinition;
34 import com.android.tradefed.util.FileUtil;
35 
36 import java.io.File;
37 import java.io.IOException;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.stream.Collectors;
46 
47 /**
48  * Base implementation of {@link IMetricCollector} that allows to start and stop collection on
49  * {@link #onTestRunStart(DeviceMetricData)} and {@link #onTestRunEnd(DeviceMetricData, Map)}.
50  */
51 public class BaseDeviceMetricCollector implements IMetricCollector {
52 
53     public static final String TEST_CASE_INCLUDE_GROUP_OPTION = "test-case-include-group";
54     public static final String TEST_CASE_EXCLUDE_GROUP_OPTION = "test-case-exclude-group";
55 
56     @Option(name = "disable", description = "disables the metrics collector")
57     private boolean mDisable = false;
58 
59     @Option(
60         name = TEST_CASE_INCLUDE_GROUP_OPTION,
61         description =
62                 "Specify a group to include as part of the collection,"
63                         + "group can be specified via @MetricOption. Can be repeated."
64                         + "Usage: @MetricOption(group = \"groupname\") to your test methods, then"
65                         + "use --test-case-include-anotation groupename to only run your group."
66     )
67     private List<String> mTestCaseIncludeAnnotationGroup = new ArrayList<>();
68 
69     @Option(
70         name = TEST_CASE_EXCLUDE_GROUP_OPTION,
71         description =
72                 "Specify a group to exclude from the metric collection,"
73                         + "group can be specified via @MetricOption. Can be repeated."
74     )
75     private List<String> mTestCaseExcludeAnnotationGroup = new ArrayList<>();
76 
77     private IInvocationContext mContext;
78     private List<ITestDevice> mRealDeviceList;
79     private ITestInvocationListener mForwarder;
80     private Map<String, File> mTestArtifactFilePathMap = new HashMap<>();
81     private DeviceMetricData mRunData;
82     private DeviceMetricData mTestData;
83     private String mTag;
84     private String mRunName;
85 
86     /**
87      * Variable for whether or not to skip the collection of one test case because it was filtered.
88      */
89     private boolean mSkipTestCase = false;
90     /** Whether or not the collector was initialized already or not. */
91     private boolean mWasInitDone = false;
92 
93     @Override
init( IInvocationContext context, ITestInvocationListener listener)94     public ITestInvocationListener init(
95             IInvocationContext context, ITestInvocationListener listener) {
96         mContext = context;
97         mForwarder = listener;
98         if (mWasInitDone) {
99             throw new IllegalStateException(
100                     String.format("init was called a second time on %s", this));
101         }
102         mWasInitDone = true;
103         return this;
104     }
105 
106     @Override
getDevices()107     public final List<ITestDevice> getDevices() {
108         return mContext.getDevices();
109     }
110 
111     /** Returns all the non-stub devices from the {@link #getDevices()} list. */
getRealDevices()112     public final List<ITestDevice> getRealDevices() {
113         if (mRealDeviceList == null) {
114             mRealDeviceList =
115                     mContext.getDevices()
116                             .stream()
117                             .filter(d -> !(d.getIDevice() instanceof StubDevice))
118                             .collect(Collectors.toList());
119         }
120         return mRealDeviceList;
121     }
122 
123     /**
124      * Retrieve the file from the test artifacts or module artifacts and cache
125      * it in a map for the subsequent calls.
126      *
127      * @param fileName name of the file to look up in the artifacts.
128      * @return File from the test artifact or module artifact. Returns null
129      *         if file is not found.
130      */
getFileFromTestArtifacts(String fileName)131     public File getFileFromTestArtifacts(String fileName) {
132         if (mTestArtifactFilePathMap.containsKey(fileName)) {
133             return mTestArtifactFilePathMap.get(fileName);
134         }
135 
136         File resolvedFile = resolveRelativeFilePath(fileName);
137         if (resolvedFile != null) {
138             CLog.i("Using file %s from %s", fileName, resolvedFile.getAbsolutePath());
139             mTestArtifactFilePathMap.put(fileName, resolvedFile);
140         }
141         return resolvedFile;
142     }
143 
144     @Override
getBuildInfos()145     public final List<IBuildInfo> getBuildInfos() {
146         return mContext.getBuildInfos();
147     }
148 
149     @Override
getInvocationListener()150     public final ITestInvocationListener getInvocationListener() {
151         return mForwarder;
152     }
153 
154     @Override
onTestRunStart(DeviceMetricData runData)155     public void onTestRunStart(DeviceMetricData runData) {
156         // Does nothing
157     }
158 
159     @Override
onTestRunEnd( DeviceMetricData runData, final Map<String, Metric> currentRunMetrics)160     public void onTestRunEnd(
161             DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) {
162         // Does nothing
163     }
164 
165     @Override
onTestStart(DeviceMetricData testData)166     public void onTestStart(DeviceMetricData testData) {
167         // Does nothing
168     }
169 
170     @Override
onTestFail(DeviceMetricData testData, TestDescription test)171     public void onTestFail(DeviceMetricData testData, TestDescription test) {
172         // Does nothing
173     }
174 
175     @Override
onTestAssumptionFailure(DeviceMetricData testData, TestDescription test)176     public void onTestAssumptionFailure(DeviceMetricData testData, TestDescription test) {
177         // Does nothing
178     }
179 
180     @Override
onTestEnd( DeviceMetricData testData, final Map<String, Metric> currentTestCaseMetrics)181     public void onTestEnd(
182             DeviceMetricData testData, final Map<String, Metric> currentTestCaseMetrics) {
183         // Does nothing
184     }
185 
186     @Override
onTestEnd( DeviceMetricData testData, final Map<String, Metric> currentTestCaseMetrics, TestDescription test)187     public void onTestEnd(
188             DeviceMetricData testData,
189             final Map<String, Metric> currentTestCaseMetrics,
190             TestDescription test) {
191         // Call the default implementation of onTestEnd if not overridden
192         onTestEnd(testData, currentTestCaseMetrics);
193     }
194 
195     /** =================================== */
196     /** Invocation Listeners for forwarding */
197     @Override
invocationStarted(IInvocationContext context)198     public final void invocationStarted(IInvocationContext context) {
199         mForwarder.invocationStarted(context);
200     }
201 
202     @Override
invocationFailed(Throwable cause)203     public final void invocationFailed(Throwable cause) {
204         mForwarder.invocationFailed(cause);
205     }
206 
207     @Override
invocationFailed(FailureDescription failure)208     public final void invocationFailed(FailureDescription failure) {
209         mForwarder.invocationFailed(failure);
210     }
211 
212     @Override
invocationEnded(long elapsedTime)213     public final void invocationEnded(long elapsedTime) {
214         mForwarder.invocationEnded(elapsedTime);
215     }
216 
217     @Override
testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)218     public final void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) {
219         mForwarder.testLog(dataName, dataType, dataStream);
220     }
221 
222     @Override
testModuleStarted(IInvocationContext moduleContext)223     public final void testModuleStarted(IInvocationContext moduleContext) {
224         mForwarder.testModuleStarted(moduleContext);
225     }
226 
227     @Override
testModuleEnded()228     public final void testModuleEnded() {
229         mForwarder.testModuleEnded();
230     }
231 
232     /** Test run callbacks */
233     @Override
testRunStarted(String runName, int testCount)234     public final void testRunStarted(String runName, int testCount) {
235         testRunStarted(runName, testCount, 0);
236     }
237 
238     @Override
testRunStarted(String runName, int testCount, int attemptNumber)239     public final void testRunStarted(String runName, int testCount, int attemptNumber) {
240         testRunStarted(runName, testCount, 0, System.currentTimeMillis());
241     }
242 
243     @Override
testRunStarted( String runName, int testCount, int attemptNumber, long startTime)244     public final void testRunStarted(
245             String runName, int testCount, int attemptNumber, long startTime) {
246         mRunData = new DeviceMetricData(mContext);
247         mRunName = runName;
248         try {
249             onTestRunStart(mRunData);
250         } catch (Throwable t) {
251             // Prevent exception from messing up the status reporting.
252             CLog.e(t);
253         }
254         mForwarder.testRunStarted(runName, testCount, attemptNumber, startTime);
255     }
256 
257     @Override
testRunFailed(String errorMessage)258     public final void testRunFailed(String errorMessage) {
259         mForwarder.testRunFailed(errorMessage);
260     }
261 
262     @Override
testRunFailed(FailureDescription failure)263     public final void testRunFailed(FailureDescription failure) {
264         mForwarder.testRunFailed(failure);
265     }
266 
267     @Override
testRunStopped(long elapsedTime)268     public final void testRunStopped(long elapsedTime) {
269         mForwarder.testRunStopped(elapsedTime);
270     }
271 
272     @Override
testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)273     public final void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
274         try {
275             onTestRunEnd(mRunData, runMetrics);
276             mRunData.addToMetrics(runMetrics);
277         } catch (Throwable t) {
278             // Prevent exception from messing up the status reporting.
279             CLog.e(t);
280         }
281         mForwarder.testRunEnded(elapsedTime, runMetrics);
282     }
283 
284     /** Test cases callbacks */
285     @Override
testStarted(TestDescription test)286     public final void testStarted(TestDescription test) {
287         testStarted(test, System.currentTimeMillis());
288     }
289 
290     @Override
testStarted(TestDescription test, long startTime)291     public final void testStarted(TestDescription test, long startTime) {
292         mTestData = new DeviceMetricData(mContext);
293         mSkipTestCase = shouldSkip(test);
294         if (!mSkipTestCase) {
295             try {
296                 onTestStart(mTestData);
297             } catch (Throwable t) {
298                 // Prevent exception from messing up the status reporting.
299                 CLog.e(t);
300             }
301         }
302         mForwarder.testStarted(test, startTime);
303     }
304 
305     @Override
testFailed(TestDescription test, String trace)306     public final void testFailed(TestDescription test, String trace) {
307         if (!mSkipTestCase) {
308             try {
309                 onTestFail(mTestData, test);
310             } catch (Throwable t) {
311                 // Prevent exception from messing up the status reporting.
312                 CLog.e(t);
313             }
314         }
315         mForwarder.testFailed(test, trace);
316     }
317 
318     @Override
testFailed(TestDescription test, FailureDescription failure)319     public final void testFailed(TestDescription test, FailureDescription failure) {
320         if (!mSkipTestCase) {
321             // Don't collect on not_executed test case
322             if (failure.getFailureStatus() == null
323                     || !FailureStatus.NOT_EXECUTED.equals(failure.getFailureStatus())) {
324                 try {
325                     onTestFail(mTestData, test);
326                 } catch (Throwable t) {
327                     // Prevent exception from messing up the status reporting.
328                     CLog.e(t);
329                 }
330             }
331         }
332         mForwarder.testFailed(test, failure);
333     }
334 
335     @Override
testEnded(TestDescription test, HashMap<String, Metric> testMetrics)336     public final void testEnded(TestDescription test, HashMap<String, Metric> testMetrics) {
337         testEnded(test, System.currentTimeMillis(), testMetrics);
338     }
339 
340     @Override
testEnded( TestDescription test, long endTime, HashMap<String, Metric> testMetrics)341     public final void testEnded(
342             TestDescription test, long endTime, HashMap<String, Metric> testMetrics) {
343         if (!mSkipTestCase) {
344             try {
345                 onTestEnd(mTestData, testMetrics, test);
346                 mTestData.addToMetrics(testMetrics);
347             } catch (Throwable t) {
348                 // Prevent exception from messing up the status reporting.
349                 CLog.e(t);
350             }
351         } else {
352             CLog.d("Skipping %s collection for %s.", this.getClass().getName(), test.toString());
353         }
354         mForwarder.testEnded(test, endTime, testMetrics);
355     }
356 
357     @Override
testAssumptionFailure(TestDescription test, String trace)358     public final void testAssumptionFailure(TestDescription test, String trace) {
359         if (!mSkipTestCase) {
360             try {
361                 onTestAssumptionFailure(mTestData, test);
362             } catch (Throwable t) {
363                 // Prevent exception from messing up the status reporting.
364                 CLog.e(t);
365             }
366         }
367         mForwarder.testAssumptionFailure(test, trace);
368     }
369 
370     @Override
testAssumptionFailure(TestDescription test, FailureDescription failure)371     public final void testAssumptionFailure(TestDescription test, FailureDescription failure) {
372         if (!mSkipTestCase) {
373             try {
374                 onTestAssumptionFailure(mTestData, test);
375             } catch (Throwable t) {
376                 // Prevent exception from messing up the status reporting.
377                 CLog.e(t);
378             }
379         }
380         mForwarder.testAssumptionFailure(test, failure);
381     }
382 
383     @Override
testIgnored(TestDescription test)384     public final void testIgnored(TestDescription test) {
385         mForwarder.testIgnored(test);
386     }
387 
388     /** {@inheritDoc} */
389     @Override
isDisabled()390     public final boolean isDisabled() {
391         return mDisable;
392     }
393 
394     /** {@inheritDoc} */
395     @Override
setDisable(boolean isDisabled)396     public final void setDisable(boolean isDisabled) {
397         mDisable = isDisabled;
398     }
399 
400     /**
401      * Sets the {@code mTag} of the collector. It can be used to specify the interval of the
402      * collector.
403      *
404      * @param tag the unique identifier of the collector.
405      */
setTag(String tag)406     public void setTag(String tag) {
407         mTag = tag;
408     }
409 
410     /**
411      * Returns the identifier {@code mTag} of the collector.
412      *
413      * @return mTag, the unique identifier of the collector.
414      */
getTag()415     public String getTag() {
416         return mTag;
417     }
418 
419     /**
420      * Returns the name of test run {@code mRunName} that triggers the collector.
421      *
422      * @return mRunName, the current test run name.
423      */
getRunName()424     public String getRunName() {
425         return mRunName;
426     }
427 
428     /**
429      * Helper to decide if a test case should or not run the collector method associated.
430      *
431      * @param desc the identifier of the test case.
432      * @return True the collector should be skipped. False otherwise.
433      */
shouldSkip(TestDescription desc)434     private boolean shouldSkip(TestDescription desc) {
435         Set<String> testCaseGroups = new HashSet<>();
436         if (desc.getAnnotation(MetricOption.class) != null) {
437             String groupName = desc.getAnnotation(MetricOption.class).group();
438             testCaseGroups.addAll(Arrays.asList(groupName.split(",")));
439         } else {
440             // Add empty group name for default case.
441             testCaseGroups.add("");
442         }
443         // Exclusion has priority: if any of the groups is excluded, exclude the test case.
444         for (String groupName : testCaseGroups) {
445             if (mTestCaseExcludeAnnotationGroup.contains(groupName)) {
446                 return true;
447             }
448         }
449         // Inclusion filter: if any of the group is included, include the test case.
450         for (String includeGroupName : mTestCaseIncludeAnnotationGroup) {
451             if (testCaseGroups.contains(includeGroupName)) {
452                 return false;
453             }
454         }
455 
456         // If we had filters and did not match any groups
457         if (!mTestCaseIncludeAnnotationGroup.isEmpty()) {
458             return true;
459         }
460         return false;
461     }
462 
463     /**
464      * Resolves the relative path of the file from the test artifacts
465      * directory or module directory.
466      *
467      * @param fileName file name that needs to be resolved.
468      * @return File file resolved for the given file name. Returns null
469      *         if file not found.
470      */
resolveRelativeFilePath(String fileName)471     private File resolveRelativeFilePath(String fileName) {
472         IBuildInfo buildInfo = getBuildInfos().get(0);
473         String mModuleName = null;
474         // Retrieve the module name.
475         if (mContext.getAttributes().get(ModuleDefinition.MODULE_NAME) != null) {
476             mModuleName = mContext.getAttributes().get(ModuleDefinition.MODULE_NAME)
477                     .get(0);
478         }
479 
480         File src = null;
481         if (buildInfo != null) {
482             src = buildInfo.getFile(fileName);
483             if (src != null && src.exists()) {
484                 return src;
485             }
486         }
487 
488         if (buildInfo instanceof IDeviceBuildInfo) {
489             IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo) buildInfo;
490             File testDir = deviceBuild.getTestsDir();
491             List<File> scanDirs = new ArrayList<>();
492             // If it exists, always look first in the ANDROID_TARGET_OUT_TESTCASES
493             File targetTestCases = deviceBuild.getFile(BuildInfoFileKey.TARGET_LINKED_DIR);
494             if (targetTestCases != null) {
495                 scanDirs.add(targetTestCases);
496             }
497             // If not, look into the test directory.
498             if (testDir != null) {
499                 scanDirs.add(testDir);
500             }
501 
502             if (mModuleName != null) {
503                 // Use module name as a discriminant to find some files
504                 if (testDir != null) {
505                     try {
506                         File moduleDir = FileUtil.findDirectory(
507                                 mModuleName, scanDirs.toArray(new File[] {}));
508                         if (moduleDir != null) {
509                             // If the spec is pushing the module itself
510                             if (mModuleName.equals(fileName)) {
511                                 // If that's the main binary generated by the target, we push the
512                                 // full directory
513                                 return moduleDir;
514                             }
515                             // Search the module directory if it exists use it in priority
516                             src = FileUtil.findFile(fileName, null, moduleDir);
517                             if (src != null) {
518                                 CLog.i("Retrieving src file from" + src.getAbsolutePath());
519                                 return src;
520                             }
521                         } else {
522                             CLog.d("Did not find any module directory for '%s'", mModuleName);
523                         }
524 
525                     } catch (IOException e) {
526                         CLog.w(
527                                 "Something went wrong while searching for the module '%s' "
528                                         + "directory.",
529                                 mModuleName);
530                     }
531                 }
532             }
533 
534             for (File searchDir : scanDirs) {
535                 try {
536                     Set<File> allMatch = FileUtil.findFilesObject(searchDir, fileName);
537                     if (allMatch.size() > 1) {
538                         CLog.d(
539                                 "Several match for filename '%s', searching for top-level match.",
540                                 fileName);
541                         for (File f : allMatch) {
542                             if (f.getParent().equals(searchDir.getAbsolutePath())) {
543                                 return f;
544                             }
545                         }
546                     } else if (allMatch.size() == 1) {
547                         return allMatch.iterator().next();
548                     }
549                 } catch (IOException e) {
550                     CLog.w("Failed to find test files from directory.");
551                 }
552             }
553         }
554         return src;
555     }
556 
557 }
558