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