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 String uidLine = getDevice().executeShellCommand("cmd package list packages -U --user " 171 + currentUser + " " + DEVICE_SIDE_TEST_PACKAGE); 172 String[] uidLineParts = uidLine.split(":"); 173 // 3rd entry is package uid 174 assertThat(uidLineParts.length).isGreaterThan(2); 175 int uid = Integer.parseInt(uidLineParts[2].trim()); 176 assertThat(uid).isGreaterThan(10000); 177 return uid; 178 } 179 180 /** 181 * Installs the test apk. 182 */ installTestApp()183 protected void installTestApp() throws Exception { 184 installPackage(DEVICE_SIDE_TEST_APK, true); 185 LogUtil.CLog.i("Installing device-side test app with uid " + getUid()); 186 allowBackgroundServices(); 187 } 188 189 /** 190 * Uninstalls the test apk. 191 */ uninstallPackage()192 protected void uninstallPackage() throws Exception{ 193 getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE); 194 } 195 196 /** 197 * Required to successfully start a background service from adb in Android O. 198 */ allowBackgroundServices()199 protected void allowBackgroundServices() throws Exception { 200 getDevice().executeShellCommand(String.format( 201 "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE)); 202 } 203 204 /** 205 * Runs a (background) service to perform the given action. 206 * @param actionValue the action code constants indicating the desired action to perform. 207 */ executeBackgroundService(String actionValue)208 protected void executeBackgroundService(String actionValue) throws Exception { 209 allowBackgroundServices(); 210 getDevice().executeShellCommand(String.format( 211 "am startservice -n '%s' -e %s %s", 212 DEVICE_SIDE_BG_SERVICE_COMPONENT, 213 KEY_ACTION, actionValue)); 214 } 215 216 217 /** Make the test app standby-active so it can run syncs and jobs immediately. */ allowImmediateSyncs()218 protected void allowImmediateSyncs() throws Exception { 219 getDevice().executeShellCommand("am set-standby-bucket " 220 + DEVICE_SIDE_TEST_PACKAGE + " active"); 221 } 222 223 /** 224 * Runs the specified activity. 225 */ runActivity(String activity, String actionKey, String actionValue)226 protected void runActivity(String activity, String actionKey, String actionValue) 227 throws Exception { 228 runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG); 229 } 230 231 /** 232 * Runs the specified activity. 233 */ runActivity(String activity, String actionKey, String actionValue, long waitTime)234 protected void runActivity(String activity, String actionKey, String actionValue, 235 long waitTime) throws Exception { 236 try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) { 237 Thread.sleep(waitTime); 238 } 239 } 240 241 /** 242 * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity 243 * when closed. 244 * 245 * <p>Example usage: 246 * <pre> 247 * try (AutoClosable a = withActivity("activity", "action", "action-value")) { 248 * doStuff(); 249 * } 250 * </pre> 251 */ withActivity(String activity, String actionKey, String actionValue)252 protected AutoCloseable withActivity(String activity, String actionKey, String actionValue) 253 throws Exception { 254 String intentString = null; 255 if (actionKey != null && actionValue != null) { 256 intentString = actionKey + " " + actionValue; 257 } 258 if (intentString == null) { 259 getDevice().executeShellCommand( 260 "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity); 261 } else { 262 getDevice().executeShellCommand( 263 "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " + 264 intentString); 265 } 266 return () -> { 267 getDevice().executeShellCommand( 268 "am force-stop " + DEVICE_SIDE_TEST_PACKAGE); 269 Thread.sleep(WAIT_TIME_SHORT); 270 }; 271 } 272 resetBatteryStats()273 protected void resetBatteryStats() throws Exception { 274 getDevice().executeShellCommand("dumpsys batterystats --reset"); 275 } 276 clearProcStats()277 protected void clearProcStats() throws Exception { 278 getDevice().executeShellCommand("dumpsys procstats --clear"); 279 } 280 startProcStatsTesting()281 protected void startProcStatsTesting() throws Exception { 282 getDevice().executeShellCommand("dumpsys procstats --start-testing"); 283 } 284 stopProcStatsTesting()285 protected void stopProcStatsTesting() throws Exception { 286 getDevice().executeShellCommand("dumpsys procstats --stop-testing"); 287 } 288 commitProcStatsToDisk()289 protected void commitProcStatsToDisk() throws Exception { 290 getDevice().executeShellCommand("dumpsys procstats --commit"); 291 } 292 rebootDeviceAndWaitUntilReady()293 protected void rebootDeviceAndWaitUntilReady() throws Exception { 294 rebootDevice(); 295 // Wait for 2 mins. 296 assertWithMessage("Device failed to boot") 297 .that(getDevice().waitForBootComplete(120_000)).isTrue(); 298 assertWithMessage("Stats service failed to start") 299 .that(waitForStatsServiceStart(60_000)).isTrue(); 300 Thread.sleep(2_000); 301 } 302 waitForStatsServiceStart(final long waitTime)303 protected boolean waitForStatsServiceStart(final long waitTime) throws Exception { 304 LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime); 305 int counter = 1; 306 long startTime = System.currentTimeMillis(); 307 while ((System.currentTimeMillis() - startTime) < waitTime) { 308 if ("running".equals(getProperty("init.svc.statsd"))) { 309 return true; 310 } 311 Thread.sleep(Math.min(200 * counter, 2_000)); 312 counter++; 313 } 314 LogUtil.CLog.w("Stats service did not start after %d ms", waitTime); 315 return false; 316 } 317 getNetworkStatsCombinedSubTypeEnabled()318 boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception { 319 final String output = getDevice().executeShellCommand( 320 "settings get global netstats_combine_subtype_enabled").trim(); 321 return output.equals("1"); 322 } 323 setNetworkStatsCombinedSubTypeEnabled(boolean enable)324 void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception { 325 getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled " 326 + (enable ? "1" : "0")); 327 } 328 } 329