blob: f4887e95fadf2e3841857cdcbe0f7be0ce5e70c8 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.cts.statsd.atom;
import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
import com.android.internal.os.StatsdConfigProto.MessageMatcher;
import com.android.internal.os.StatsdConfigProto.Position;
import com.android.internal.os.StatsdConfigProto.StatsdConfig;
import com.android.os.StatsLog.EventMetricData;
import com.android.tradefed.log.LogUtil;
import java.util.Arrays;
import java.util.List;
/**
* Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app.
*/
public class DeviceAtomTestCase extends AtomTestCase {
public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
public static final String DEVICE_SIDE_TEST_PACKAGE =
"com.android.server.cts.device.statsd";
public static final String DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME =
"com.android.server.cts.device.statsd.StatsdCtsForegroundService";
private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT =
"com.android.server.cts.device.statsd/.StatsdCtsBackgroundService";
public static final long DEVICE_SIDE_TEST_PKG_HASH =
Long.parseUnsignedLong("15694052924544098582");
// Constants from device side tests (not directly accessible here).
public static final String KEY_ACTION = "action";
public static final String ACTION_LMK = "action.lmk";
public static final String CONFIG_NAME = "cts_config";
@Override
protected void setUp() throws Exception {
super.setUp();
getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
installTestApp();
Thread.sleep(1000);
}
@Override
protected void tearDown() throws Exception {
getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
super.tearDown();
}
/**
* Performs a device-side test by calling a method on the app and returns its stats events.
* @param methodName the name of the method in the app's AtomTests to perform
* @param atom atom tag (from atoms.proto)
* @param key atom's field corresponding to state
* @param stateOn 'on' value
* @param stateOff 'off' value
* @param minTimeDiffMs max allowed time between start and stop
* @param maxTimeDiffMs min allowed time between start and stop
* @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop)
* @return list of events with the app's uid matching the configuration defined by the params.
*/
protected List<EventMetricData> doDeviceMethodOnOff(
String methodName, int atom, int key, int stateOn, int stateOff,
int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception {
StatsdConfig.Builder conf = createConfigBuilder();
addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOn));
addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOff));
List<EventMetricData> data = doDeviceMethod(methodName, conf);
if (demandExactlyTwo) {
assertEquals(2, data.size());
} else {
assertTrue("data.size() [" + data.size() + "] should be >= 2", data.size() >= 2);
}
assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
return data;
}
/**
*
* @param methodName the name of the method in the app's AtomTests to perform
* @param cfg statsd configuration
* @return list of events with the app's uid matching the configuration.
*/
protected List<EventMetricData> doDeviceMethod(String methodName, StatsdConfig.Builder cfg)
throws Exception {
removeConfig(CONFIG_ID);
getReportList(); // Clears previous data on disk.
uploadConfig(cfg);
int appUid = getUid();
LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid);
runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName);
return getEventMetricDataList();
}
protected void createAndUploadConfig(int atomTag, boolean useAttribution) throws Exception {
StatsdConfig.Builder conf = createConfigBuilder();
addAtomEvent(conf, atomTag, useAttribution);
uploadConfig(conf);
}
/**
* Adds an event to the config for an atom that matches the given key AND has the app's uid.
* @param conf configuration
* @param atomTag atom tag (from atoms.proto)
* @param fvm FieldValueMatcher.Builder for the relevant key
*/
@Override
protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)
throws Exception {
final int UID_KEY = 1;
FieldValueMatcher.Builder fvmUid = createAttributionFvm(UID_KEY);
addAtomEvent(conf, atomTag, Arrays.asList(fvm, fvmUid));
}
/**
* Adds an event to the config for an atom that matches the app's uid.
* @param conf configuration
* @param atomTag atom tag (from atoms.proto)
* @param useAttribution if the atom has a uid or AttributionNode
*/
protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
boolean useAttribution) throws Exception {
final int UID_KEY = 1;
FieldValueMatcher.Builder fvmUid;
if (useAttribution) {
fvmUid = createAttributionFvm(UID_KEY);
} else {
fvmUid = createFvm(UID_KEY).setEqInt(getUid());
}
addAtomEvent(conf, atomTag, Arrays.asList(fvmUid));
}
/**
* Creates a FieldValueMatcher for atoms that use AttributionNode
*/
protected FieldValueMatcher.Builder createAttributionFvm(int field) {
final int ATTRIBUTION_NODE_UID_KEY = 1;
return createFvm(field).setPosition(Position.ANY)
.setMatchesTuple(MessageMatcher.newBuilder()
.addFieldValueMatcher(createFvm(ATTRIBUTION_NODE_UID_KEY)
.setEqString(DEVICE_SIDE_TEST_PACKAGE)));
}
/**
* Gets the uid of the test app.
*/
protected int getUid() throws Exception {
int currentUser = getDevice().getCurrentUser();
String uidLine = getDevice().executeShellCommand("cmd package list packages -U --user "
+ currentUser + " " + DEVICE_SIDE_TEST_PACKAGE);
String[] uidLineParts = uidLine.split(":");
// 3rd entry is package uid
assertTrue(uidLineParts.length > 2);
int uid = Integer.parseInt(uidLineParts[2].trim());
assertTrue(uid > 10000);
return uid;
}
/**
* Installs the test apk.
*/
protected void installTestApp() throws Exception {
installPackage(DEVICE_SIDE_TEST_APK, true);
LogUtil.CLog.i("Installing device-side test app with uid " + getUid());
allowBackgroundServices();
}
/**
* Uninstalls the test apk.
*/
protected void uninstallPackage() throws Exception{
getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
}
/**
* Required to successfully start a background service from adb in O.
*/
protected void allowBackgroundServices() throws Exception {
getDevice().executeShellCommand(String.format(
"cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
}
/**
* Runs a (background) service to perform the given action.
* @param actionValue the action code constants indicating the desired action to perform.
*/
protected void executeBackgroundService(String actionValue) throws Exception {
allowBackgroundServices();
getDevice().executeShellCommand(String.format(
"am startservice -n '%s' -e %s %s",
DEVICE_SIDE_BG_SERVICE_COMPONENT,
KEY_ACTION, actionValue));
}
/** Make the test app standby-active so it can run syncs and jobs immediately. */
protected void allowImmediateSyncs() throws Exception {
getDevice().executeShellCommand("am set-standby-bucket "
+ DEVICE_SIDE_TEST_PACKAGE + " active");
}
/**
* Runs the specified activity.
*/
protected void runActivity(String activity, String actionKey, String actionValue)
throws Exception {
runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG);
}
/**
* Runs the specified activity.
*/
protected void runActivity(String activity, String actionKey, String actionValue,
long waitTime) throws Exception {
try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) {
Thread.sleep(waitTime);
}
}
/**
* Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
* when closed.
*
* <p>Example usage:
* <pre>
* try (AutoClosable a = withActivity("activity", "action", "action-value")) {
* doStuff();
* }
* </pre>
*/
protected AutoCloseable withActivity(String activity, String actionKey, String actionValue)
throws Exception {
String intentString = null;
if (actionKey != null && actionValue != null) {
intentString = actionKey + " " + actionValue;
}
if (intentString == null) {
getDevice().executeShellCommand(
"am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity);
} else {
getDevice().executeShellCommand(
"am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " +
intentString);
}
return () -> {
getDevice().executeShellCommand(
"am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
Thread.sleep(WAIT_TIME_SHORT);
};
}
protected void resetBatteryStats() throws Exception {
getDevice().executeShellCommand("dumpsys batterystats --reset");
}
protected void clearProcStats() throws Exception {
getDevice().executeShellCommand("dumpsys procstats --clear");
}
protected void startProcStatsTesting() throws Exception {
getDevice().executeShellCommand("dumpsys procstats --start-testing");
}
protected void stopProcStatsTesting() throws Exception {
getDevice().executeShellCommand("dumpsys procstats --stop-testing");
}
protected void commitProcStatsToDisk() throws Exception {
getDevice().executeShellCommand("dumpsys procstats --commit");
}
protected void rebootDeviceAndWaitUntilReady() throws Exception {
rebootDevice();
// Wait for 2 mins.
assertTrue("Device failed to boot", getDevice().waitForBootComplete(120_000));
assertTrue("Stats service failed to start", waitForStatsServiceStart(60_000));
Thread.sleep(2_000);
}
protected boolean waitForStatsServiceStart(final long waitTime) throws Exception {
LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime);
int counter = 1;
long startTime = System.currentTimeMillis();
while ((System.currentTimeMillis() - startTime) < waitTime) {
if ("running".equals(getProperty("init.svc.statsd"))) {
return true;
}
Thread.sleep(Math.min(200 * counter, 2_000));
counter++;
}
LogUtil.CLog.w("Stats service did not start after %d ms", waitTime);
return false;
}
}