• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.cts.statsd.atom;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
22 import com.android.internal.os.StatsdConfigProto.MessageMatcher;
23 import com.android.internal.os.StatsdConfigProto.Position;
24 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
25 import com.android.os.StatsLog.EventMetricData;
26 import com.android.tradefed.log.LogUtil;
27 
28 import java.util.Arrays;
29 import java.util.List;
30 
31 /**
32  * Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app.
33  */
34 public class DeviceAtomTestCase extends AtomTestCase {
35 
36     public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
37     public static final String DEVICE_SIDE_TEST_PACKAGE =
38             "com.android.server.cts.device.statsd";
39     public static final long DEVICE_SIDE_TEST_PACKAGE_VERSION = 10;
40     public static final String DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME =
41             "com.android.server.cts.device.statsd.StatsdCtsForegroundService";
42     private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT =
43             "com.android.server.cts.device.statsd/.StatsdCtsBackgroundService";
44     public static final long DEVICE_SIDE_TEST_PKG_HASH =
45             Long.parseUnsignedLong("15694052924544098582");
46 
47     // Constants from device side tests (not directly accessible here).
48     public static final String KEY_ACTION = "action";
49     public static final String ACTION_LMK = "action.lmk";
50 
51     public static final String CONFIG_NAME = "cts_config";
52 
53     @Override
setUp()54     protected void setUp() throws Exception {
55         super.setUp();
56         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
57         installTestApp();
58         Thread.sleep(1000);
59     }
60 
61     @Override
tearDown()62     protected void tearDown() throws Exception {
63         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
64         super.tearDown();
65     }
66 
67     /**
68      * Performs a device-side test by calling a method on the app and returns its stats events.
69      * @param methodName the name of the method in the app's AtomTests to perform
70      * @param atom atom tag (from atoms.proto)
71      * @param key atom's field corresponding to state
72      * @param stateOn 'on' value
73      * @param stateOff 'off' value
74      * @param minTimeDiffMs max allowed time between start and stop
75      * @param maxTimeDiffMs min allowed time between start and stop
76      * @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop)
77      * @return list of events with the app's uid matching the configuration defined by the params.
78      */
doDeviceMethodOnOff( String methodName, int atom, int key, int stateOn, int stateOff, int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo)79     protected List<EventMetricData> doDeviceMethodOnOff(
80             String methodName, int atom, int key, int stateOn, int stateOff,
81             int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception {
82         StatsdConfig.Builder conf = createConfigBuilder();
83         addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOn));
84         addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOff));
85         List<EventMetricData> data = doDeviceMethod(methodName, conf);
86 
87         if (demandExactlyTwo) {
88             assertThat(data).hasSize(2);
89         } else {
90             assertThat(data.size()).isAtLeast(2);
91         }
92         assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
93         return data;
94     }
95 
96     /**
97      *
98      * @param methodName the name of the method in the app's AtomTests to perform
99      * @param cfg statsd configuration
100      * @return list of events with the app's uid matching the configuration.
101      */
doDeviceMethod(String methodName, StatsdConfig.Builder cfg)102     protected List<EventMetricData> doDeviceMethod(String methodName, StatsdConfig.Builder cfg)
103             throws Exception {
104         removeConfig(CONFIG_ID);
105         getReportList();  // Clears previous data on disk.
106         uploadConfig(cfg);
107         int appUid = getUid();
108         LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid);
109         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName);
110 
111         return getEventMetricDataList();
112     }
113 
createAndUploadConfig(int atomTag, boolean useAttribution)114     protected void createAndUploadConfig(int atomTag, boolean useAttribution) throws Exception {
115         StatsdConfig.Builder conf = createConfigBuilder();
116         addAtomEvent(conf, atomTag, useAttribution);
117         uploadConfig(conf);
118     }
119 
120     /**
121      * Adds an event to the config for an atom that matches the given key AND has the app's uid.
122      * @param conf configuration
123      * @param atomTag atom tag (from atoms.proto)
124      * @param fvm FieldValueMatcher.Builder for the relevant key
125      */
126     @Override
addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)127     protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)
128             throws Exception {
129 
130         final int UID_KEY = 1;
131         FieldValueMatcher.Builder fvmUid = createAttributionFvm(UID_KEY);
132         addAtomEvent(conf, atomTag, Arrays.asList(fvm, fvmUid));
133     }
134 
135     /**
136      * Adds an event to the config for an atom that matches the app's uid.
137      * @param conf configuration
138      * @param atomTag atom tag (from atoms.proto)
139      * @param useAttribution If true, the atom has a uid within an attribution node. Else, the atom
140      * has a uid but not in an attribution node.
141      */
addAtomEvent(StatsdConfig.Builder conf, int atomTag, boolean useAttribution)142     protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
143             boolean useAttribution) throws Exception {
144         final int UID_KEY = 1;
145         FieldValueMatcher.Builder fvmUid;
146         if (useAttribution) {
147             fvmUid = createAttributionFvm(UID_KEY);
148         } else {
149             fvmUid = createFvm(UID_KEY).setEqString(DEVICE_SIDE_TEST_PACKAGE);
150         }
151         addAtomEvent(conf, atomTag, Arrays.asList(fvmUid));
152     }
153 
154     /**
155      * Creates a FieldValueMatcher for atoms that use AttributionNode
156      */
createAttributionFvm(int field)157     protected FieldValueMatcher.Builder createAttributionFvm(int field) {
158         final int ATTRIBUTION_NODE_UID_KEY = 1;
159         return createFvm(field).setPosition(Position.ANY)
160                 .setMatchesTuple(MessageMatcher.newBuilder()
161                         .addFieldValueMatcher(createFvm(ATTRIBUTION_NODE_UID_KEY)
162                                 .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
163     }
164 
165     /**
166      * Gets the uid of the test app.
167      */
getUid()168     protected int getUid() throws Exception {
169         int currentUser = getDevice().getCurrentUser();
170         final String packages = getDevice().executeShellCommand("cmd package list packages -U"
171                 + " --user " + currentUser + " " + DEVICE_SIDE_TEST_PACKAGE);
172 
173         // Split package list by lines
174         // Sample packages response:
175         // package:com.android.server.cts.device.statsd.host uid:1010033
176         // package:com.android.server.cts.device.statsd uid:1010034
177         final String[] lines = packages.split("[\\r\\n]+");
178         for (final String line : lines) {
179             if (line.startsWith("package:" + DEVICE_SIDE_TEST_PACKAGE + " ")) {
180                 final int uidIndex = line.lastIndexOf(":") + 1;
181                 final int uid = Integer.parseInt(line.substring(uidIndex).trim());
182                 assertThat(uid).isGreaterThan(10_000);
183                 return uid;
184             }
185         }
186         throw new Error(
187                 String.format("Could not find installed package: %s", DEVICE_SIDE_TEST_PACKAGE));
188     }
189 
190     /**
191      * Installs the test apk.
192      */
installTestApp()193     protected void installTestApp() throws Exception {
194         installPackage(DEVICE_SIDE_TEST_APK, true);
195         LogUtil.CLog.i("Installing device-side test app with uid " + getUid());
196         allowBackgroundServices();
197     }
198 
199     /**
200      * Uninstalls the test apk.
201      */
uninstallPackage()202     protected void uninstallPackage() throws Exception{
203         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
204     }
205 
206     /**
207      * Required to successfully start a background service from adb in Android O.
208      */
allowBackgroundServices()209     protected void allowBackgroundServices() throws Exception {
210         getDevice().executeShellCommand(String.format(
211                 "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
212     }
213 
214     /**
215      * Runs a (background) service to perform the given action.
216      * @param actionValue the action code constants indicating the desired action to perform.
217      */
executeBackgroundService(String actionValue)218     protected void executeBackgroundService(String actionValue) throws Exception {
219         allowBackgroundServices();
220         getDevice().executeShellCommand(String.format(
221                 "am startservice -n '%s' -e %s %s",
222                 DEVICE_SIDE_BG_SERVICE_COMPONENT,
223                 KEY_ACTION, actionValue));
224     }
225 
226 
227     /** Make the test app standby-active so it can run syncs and jobs immediately. */
allowImmediateSyncs()228     protected void allowImmediateSyncs() throws Exception {
229         getDevice().executeShellCommand("am set-standby-bucket "
230                 + DEVICE_SIDE_TEST_PACKAGE + " active");
231     }
232 
233     /**
234      * Runs the specified activity.
235      */
runActivity(String activity, String actionKey, String actionValue)236     protected void runActivity(String activity, String actionKey, String actionValue)
237             throws Exception {
238         runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG);
239     }
240 
241     /**
242      * Runs the specified activity.
243      */
runActivity(String activity, String actionKey, String actionValue, long waitTime)244     protected void runActivity(String activity, String actionKey, String actionValue,
245             long waitTime) throws Exception {
246         try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) {
247             Thread.sleep(waitTime);
248         }
249     }
250 
251     /**
252      * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
253      * when closed.
254      *
255      * <p>Example usage:
256      * <pre>
257      *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
258      *         doStuff();
259      *     }
260      * </pre>
261      */
withActivity(String activity, String actionKey, String actionValue)262     protected AutoCloseable withActivity(String activity, String actionKey, String actionValue)
263             throws Exception {
264         String intentString = null;
265         if (actionKey != null && actionValue != null) {
266             intentString = actionKey + " " + actionValue;
267         }
268         if (intentString == null) {
269             getDevice().executeShellCommand(
270                     "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity);
271         } else {
272             getDevice().executeShellCommand(
273                     "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " +
274                             intentString);
275         }
276         return () -> {
277             getDevice().executeShellCommand(
278                     "am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
279             Thread.sleep(WAIT_TIME_SHORT);
280         };
281     }
282 
resetBatteryStats()283     protected void resetBatteryStats() throws Exception {
284         getDevice().executeShellCommand("dumpsys batterystats --reset");
285     }
286 
clearProcStats()287     protected void clearProcStats() throws Exception {
288         getDevice().executeShellCommand("dumpsys procstats --clear");
289     }
290 
startProcStatsTesting()291     protected void startProcStatsTesting() throws Exception {
292         getDevice().executeShellCommand("dumpsys procstats --start-testing");
293     }
294 
stopProcStatsTesting()295     protected void stopProcStatsTesting() throws Exception {
296         getDevice().executeShellCommand("dumpsys procstats --stop-testing");
297     }
298 
commitProcStatsToDisk()299     protected void commitProcStatsToDisk() throws Exception {
300         getDevice().executeShellCommand("dumpsys procstats --commit");
301     }
302 
rebootDeviceAndWaitUntilReady()303     protected void rebootDeviceAndWaitUntilReady() throws Exception {
304         rebootDevice();
305         // Wait for 3 mins.
306         assertWithMessage("Device failed to boot")
307             .that(getDevice().waitForBootComplete(180_000)).isTrue();
308         assertWithMessage("Stats service failed to start")
309             .that(waitForStatsServiceStart(60_000)).isTrue();
310         Thread.sleep(2_000);
311     }
312 
waitForStatsServiceStart(final long waitTime)313     protected boolean waitForStatsServiceStart(final long waitTime) throws Exception {
314         LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime);
315         int counter = 1;
316         long startTime = System.currentTimeMillis();
317         while ((System.currentTimeMillis() - startTime) < waitTime) {
318             if ("running".equals(getProperty("init.svc.statsd"))) {
319                 return true;
320             }
321             Thread.sleep(Math.min(200 * counter, 2_000));
322             counter++;
323         }
324         LogUtil.CLog.w("Stats service did not start after %d ms", waitTime);
325         return false;
326     }
327 
getNetworkStatsCombinedSubTypeEnabled()328     boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception {
329         final String output = getDevice().executeShellCommand(
330                 "settings get global netstats_combine_subtype_enabled").trim();
331         return output.equals("1");
332     }
333 
setNetworkStatsCombinedSubTypeEnabled(boolean enable)334     void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
335         getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
336                 + (enable ? "1" : "0"));
337     }
338 }
339