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.IBuildInfo;
19 import com.android.tradefed.config.Option;
20 import com.android.tradefed.device.ITestDevice;
21 import com.android.tradefed.invoker.IInvocationContext;
22 import com.android.tradefed.log.LogUtil.CLog;
23 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
24 import com.android.tradefed.result.ITestInvocationListener;
25 import com.android.tradefed.result.InputStreamSource;
26 import com.android.tradefed.result.LogDataType;
27 import com.android.tradefed.result.TestDescription;
28 
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 
37 /**
38  * Base implementation of {@link IMetricCollector} that allows to start and stop collection on
39  * {@link #onTestRunStart(DeviceMetricData)} and {@link #onTestRunEnd(DeviceMetricData, Map)}.
40  */
41 public class BaseDeviceMetricCollector implements IMetricCollector {
42 
43     public static final String TEST_CASE_INCLUDE_GROUP_OPTION = "test-case-include-group";
44     public static final String TEST_CASE_EXCLUDE_GROUP_OPTION = "test-case-exclude-group";
45 
46     @Option(
47         name = TEST_CASE_INCLUDE_GROUP_OPTION,
48         description =
49                 "Specify a group to include as part of the collection,"
50                         + "group can be specified via @MetricOption. Can be repeated."
51                         + "Usage: @MetricOption(group = \"groupname\") to your test methods, then"
52                         + "use --test-case-include-anotation groupename to only run your group."
53     )
54     private List<String> mTestCaseIncludeAnnotationGroup = new ArrayList<>();
55 
56     @Option(
57         name = TEST_CASE_EXCLUDE_GROUP_OPTION,
58         description =
59                 "Specify a group to exclude from the metric collection,"
60                         + "group can be specified via @MetricOption. Can be repeated."
61     )
62     private List<String> mTestCaseExcludeAnnotationGroup = new ArrayList<>();
63 
64     private IInvocationContext mContext;
65     private ITestInvocationListener mForwarder;
66     private DeviceMetricData mRunData;
67     private DeviceMetricData mTestData;
68     private String mTag;
69     private String mRunName;
70     /**
71      * Variable for whether or not to skip the collection of one test case because it was filtered.
72      */
73     private boolean mSkipTestCase = false;
74 
75     @Override
init( IInvocationContext context, ITestInvocationListener listener)76     public ITestInvocationListener init(
77             IInvocationContext context, ITestInvocationListener listener) {
78         mContext = context;
79         mForwarder = listener;
80         return this;
81     }
82 
83     @Override
getDevices()84     public final List<ITestDevice> getDevices() {
85         return mContext.getDevices();
86     }
87 
88     @Override
getBuildInfos()89     public final List<IBuildInfo> getBuildInfos() {
90         return mContext.getBuildInfos();
91     }
92 
93     @Override
getInvocationListener()94     public final ITestInvocationListener getInvocationListener() {
95         return mForwarder;
96     }
97 
98     @Override
onTestRunStart(DeviceMetricData runData)99     public void onTestRunStart(DeviceMetricData runData) {
100         // Does nothing
101     }
102 
103     @Override
onTestRunEnd( DeviceMetricData runData, final Map<String, Metric> currentRunMetrics)104     public void onTestRunEnd(
105             DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) {
106         // Does nothing
107     }
108 
109     @Override
onTestStart(DeviceMetricData testData)110     public void onTestStart(DeviceMetricData testData) {
111         // Does nothing
112     }
113 
114     @Override
onTestEnd( DeviceMetricData testData, final Map<String, Metric> currentTestCaseMetrics)115     public void onTestEnd(
116             DeviceMetricData testData, final Map<String, Metric> currentTestCaseMetrics) {
117         // Does nothing
118     }
119 
120     /** =================================== */
121     /** Invocation Listeners for forwarding */
122     @Override
invocationStarted(IInvocationContext context)123     public final void invocationStarted(IInvocationContext context) {
124         mForwarder.invocationStarted(context);
125     }
126 
127     @Override
invocationFailed(Throwable cause)128     public final void invocationFailed(Throwable cause) {
129         mForwarder.invocationFailed(cause);
130     }
131 
132     @Override
invocationEnded(long elapsedTime)133     public final void invocationEnded(long elapsedTime) {
134         mForwarder.invocationEnded(elapsedTime);
135     }
136 
137     @Override
testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)138     public final void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) {
139         mForwarder.testLog(dataName, dataType, dataStream);
140     }
141 
142     /** Test run callbacks */
143     @Override
testRunStarted(String runName, int testCount)144     public final void testRunStarted(String runName, int testCount) {
145         mRunData = new DeviceMetricData(mContext);
146         mRunName = runName;
147         try {
148             onTestRunStart(mRunData);
149         } catch (Throwable t) {
150             // Prevent exception from messing up the status reporting.
151             CLog.e(t);
152         }
153         mForwarder.testRunStarted(runName, testCount);
154     }
155 
156     @Override
testRunFailed(String errorMessage)157     public final void testRunFailed(String errorMessage) {
158         mForwarder.testRunFailed(errorMessage);
159     }
160 
161     @Override
testRunStopped(long elapsedTime)162     public final void testRunStopped(long elapsedTime) {
163         mForwarder.testRunStopped(elapsedTime);
164     }
165 
166     @Override
testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)167     public final void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
168         try {
169             onTestRunEnd(mRunData, runMetrics);
170             mRunData.addToMetrics(runMetrics);
171         } catch (Throwable t) {
172             // Prevent exception from messing up the status reporting.
173             CLog.e(t);
174         }
175         mForwarder.testRunEnded(elapsedTime, runMetrics);
176     }
177 
178     /** Test cases callbacks */
179     @Override
testStarted(TestDescription test)180     public final void testStarted(TestDescription test) {
181         testStarted(test, System.currentTimeMillis());
182     }
183 
184     @Override
testStarted(TestDescription test, long startTime)185     public final void testStarted(TestDescription test, long startTime) {
186         mTestData = new DeviceMetricData(mContext);
187         mSkipTestCase = shouldSkip(test);
188         if (!mSkipTestCase) {
189             try {
190                 onTestStart(mTestData);
191             } catch (Throwable t) {
192                 // Prevent exception from messing up the status reporting.
193                 CLog.e(t);
194             }
195         }
196         mForwarder.testStarted(test, startTime);
197     }
198 
199     @Override
testFailed(TestDescription test, String trace)200     public final void testFailed(TestDescription test, String trace) {
201         mForwarder.testFailed(test, trace);
202     }
203 
204     @Override
testEnded(TestDescription test, HashMap<String, Metric> testMetrics)205     public final void testEnded(TestDescription test, HashMap<String, Metric> testMetrics) {
206         testEnded(test, System.currentTimeMillis(), testMetrics);
207     }
208 
209     @Override
testEnded( TestDescription test, long endTime, HashMap<String, Metric> testMetrics)210     public final void testEnded(
211             TestDescription test, long endTime, HashMap<String, Metric> testMetrics) {
212         if (!mSkipTestCase) {
213             try {
214                 onTestEnd(mTestData, testMetrics);
215                 mTestData.addToMetrics(testMetrics);
216             } catch (Throwable t) {
217                 // Prevent exception from messing up the status reporting.
218                 CLog.e(t);
219             }
220         } else {
221             CLog.d("Skipping %s collection for %s.", this.getClass().getName(), test.toString());
222         }
223         mForwarder.testEnded(test, endTime, testMetrics);
224     }
225 
226     @Override
testAssumptionFailure(TestDescription test, String trace)227     public final void testAssumptionFailure(TestDescription test, String trace) {
228         mForwarder.testAssumptionFailure(test, trace);
229     }
230 
231     @Override
testIgnored(TestDescription test)232     public final void testIgnored(TestDescription test) {
233         mForwarder.testIgnored(test);
234     }
235 
236     /**
237      * Sets the {@code mTag} of the collector. It can be used to specify the interval of the
238      * collector.
239      *
240      * @param tag the unique identifier of the collector.
241      */
setTag(String tag)242     public void setTag(String tag) {
243         mTag = tag;
244     }
245 
246     /**
247      * Returns the identifier {@code mTag} of the collector.
248      *
249      * @return mTag, the unique identifier of the collector.
250      */
getTag()251     public String getTag() {
252         return mTag;
253     }
254 
255     /**
256      * Returns the name of test run {@code mRunName} that triggers the collector.
257      *
258      * @return mRunName, the current test run name.
259      */
getRunName()260     public String getRunName() {
261         return mRunName;
262     }
263 
264     /**
265      * Helper to decide if a test case should or not run the collector method associated.
266      *
267      * @param desc the identifier of the test case.
268      * @return True the collector should be skipped. False otherwise.
269      */
shouldSkip(TestDescription desc)270     private boolean shouldSkip(TestDescription desc) {
271         Set<String> testCaseGroups = new HashSet<>();
272         if (desc.getAnnotation(MetricOption.class) != null) {
273             String groupName = desc.getAnnotation(MetricOption.class).group();
274             testCaseGroups.addAll(Arrays.asList(groupName.split(",")));
275         } else {
276             // Add empty group name for default case.
277             testCaseGroups.add("");
278         }
279         // Exclusion has priority: if any of the groups is excluded, exclude the test case.
280         for (String groupName : testCaseGroups) {
281             if (mTestCaseExcludeAnnotationGroup.contains(groupName)) {
282                 return true;
283             }
284         }
285         // Inclusion filter: if any of the group is included, include the test case.
286         for (String includeGroupName : mTestCaseIncludeAnnotationGroup) {
287             if (testCaseGroups.contains(includeGroupName)) {
288                 return false;
289             }
290         }
291 
292         // If we had filters and did not match any groups
293         if (!mTestCaseIncludeAnnotationGroup.isEmpty()) {
294             return true;
295         }
296         return false;
297     }
298 }
299