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