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