CTS to enforce lmkd emitting statsd events for S+ (4.19+) devices
As part of this change, also fix the test flakiness. There are two
sources of flakiness:
- If we allocate too quickly, the OOM reaper might get to the process
first
- If we allocate too slowly from a background service, we might run out
of time and other background restrictions can kick in.
I switched the strategy to spawn a separate victim process that idles,
and have a fg process that allocates mem.
Bug: 177985094
Test: atest UidAtomTests
Change-Id: I8cb123b9488fbc6e88863c2f0e75f1422bcd282e
(cherry picked from commit e91f888636209772644d64ed4d40852c6d7bb466)
diff --git a/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
index 20a9763..1ffa00a 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
+++ b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
@@ -45,6 +45,9 @@
<service android:name=".StatsdCtsBackgroundService"
android:exported="true"/>
+ <service android:name=".LmkVictimBackgroundService"
+ android:process=":lmk_victim"
+ android:exported="true"/>
<activity android:name=".StatsdCtsForegroundActivity"
android:exported="true"/>
<service android:name=".StatsdCtsForegroundService"
diff --git a/hostsidetests/statsdatom/apps/statsdapp/jni/alloc_stress_activity.cpp b/hostsidetests/statsdatom/apps/statsdapp/jni/alloc_stress_activity.cpp
index 5417245..0a005af 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/jni/alloc_stress_activity.cpp
+++ b/hostsidetests/statsdatom/apps/statsdapp/jni/alloc_stress_activity.cpp
@@ -31,12 +31,11 @@
using namespace std;
-size_t s = 4 * (1 << 20); // 4 MB
+size_t s = 8 * (1 << 20); // 8 MB
void *gptr;
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_android_server_cts_device_statsdatom_StatsdCtsBackgroundService_cmain(JNIEnv* , jobject /* this */)
-{
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_server_cts_device_statsdatom_StatsdCtsForegroundActivity_cmain(
+ JNIEnv *, jobject /* this */) {
long long allocCount = 0;
while (1) {
char *ptr = (char *)malloc(s);
@@ -44,10 +43,14 @@
for (int i = 0; i < s; i += 4096) {
*((long long *)&ptr[i]) = allocCount + i;
}
+ allocCount += s;
std::stringstream ss;
ss << "total alloc: " << allocCount / (1 << 20);
LOG(ss.str().c_str());
gptr = ptr;
- allocCount += s;
+
+ // If we are too aggressive allocating, we will end up triggering the
+ // OOM reaper instead of LMKd.
+ usleep(1000);
}
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/LmkVictimBackgroundService.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/LmkVictimBackgroundService.java
new file mode 100644
index 0000000..0e97dbe
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/LmkVictimBackgroundService.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 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 com.android.server.cts.device.statsdatom;
+
+public class LmkVictimBackgroundService extends StatsdCtsBackgroundService {}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsBackgroundService.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsBackgroundService.java
index 8cc2cb0..d7ac653 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsBackgroundService.java
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsBackgroundService.java
@@ -27,14 +27,9 @@
public static final String KEY_ACTION = "action";
public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
- public static final String ACTION_LMK = "action.lmk";
public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
- static {
- System.loadLibrary("lmkhelper_statsdatom");
- }
-
public StatsdCtsBackgroundService() {
super(StatsdCtsBackgroundService.class.getName());
}
@@ -50,16 +45,8 @@
break;
case ACTION_END_IMMEDIATELY:
break;
- case ACTION_LMK:
- new Thread(this::cmain).start();
- break;
default:
Log.e(TAG, "Intent had invalid action");
}
}
-
- /**
- * Keep allocating memory until the process is killed by LMKD.
- **/
- public native void cmain();
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsForegroundActivity.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsForegroundActivity.java
index 26ec79e..58db263 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsForegroundActivity.java
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsForegroundActivity.java
@@ -50,6 +50,7 @@
public static final String ACTION_SHOW_NOTIFICATION = "action.show_notification";
public static final String ACTION_CREATE_CHANNEL_GROUP = "action.create_channel_group";
public static final String ACTION_POLL_NETWORK_STATS = "action.poll_network_stats";
+ public static final String ACTION_LMK = "action.lmk";
public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
public static final int SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY = 2_000;
@@ -57,6 +58,7 @@
static {
System.loadLibrary("crashhelper");
+ System.loadLibrary("lmkhelper_statsdatom");
}
@Override
@@ -100,6 +102,9 @@
case ACTION_POLL_NETWORK_STATS:
doPollNetworkStats();
break;
+ case ACTION_LMK:
+ new Thread(this::cmain).start();
+ break;
default:
Log.e(TAG, "Intent had invalid action " + action);
finish();
@@ -213,4 +218,9 @@
}
private native void segfault();
+
+ /**
+ * Keep allocating memory until the process is killed by LMKD.
+ **/
+ public native void cmain();
}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
index f2f4e1b..e937823 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
@@ -35,6 +35,7 @@
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.util.Pair;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
@@ -53,9 +54,6 @@
public static final String STATSD_ATOM_TEST_APK = "CtsStatsdAtomApp.apk";
public static final String STATSD_ATOM_TEST_PKG = "com.android.server.cts.device.statsdatom";
- private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT =
- "com.android.server.cts.device.statsdatom/.StatsdCtsBackgroundService";
-
private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
private static final String KEY_ACTION = "action";
@@ -379,10 +377,19 @@
*/
public static void executeBackgroundService(ITestDevice device, String actionValue)
throws Exception {
+ executeServiceAction(device, "StatsdCtsBackgroundService", actionValue);
+ }
+
+ /**
+ * Runs the specified statsd package service to perform the given action.
+ * @param actionValue the action code constants indicating the desired action to perform.
+ */
+ public static void executeServiceAction(ITestDevice device, String service, String actionValue)
+ throws Exception {
allowBackgroundServices(device);
device.executeShellCommand(String.format(
- "am startservice -n '%s' -e %s %s",
- DEVICE_SIDE_BG_SERVICE_COMPONENT,
+ "am startservice -n '%s/.%s' -e %s %s",
+ STATSD_ATOM_TEST_PKG, service,
KEY_ACTION, actionValue));
}
@@ -394,5 +401,25 @@
"cmd deviceidle tempwhitelist %s", STATSD_ATOM_TEST_PKG));
}
+ /**
+ * Returns the kernel major version as a pair of ints.
+ */
+ public static Pair<Integer, Integer> getKernelVersion(ITestDevice device)
+ throws Exception {
+ String[] version = device.executeShellCommand("uname -r").split("\\.");
+ if (version.length < 2) {
+ throw new RuntimeException("Could not parse kernel version");
+ }
+ return Pair.create(Integer.parseInt(version[0]), Integer.parseInt(version[1]));
+ }
+
+ /** Returns if the device kernel version >= input kernel version. */
+ public static boolean isKernelGreaterEqual(ITestDevice device, Pair<Integer, Integer> version)
+ throws Exception {
+ Pair<Integer, Integer> kernelVersion = getKernelVersion(device);
+ return kernelVersion.first > version.first
+ || (kernelVersion.first == version.first && kernelVersion.second >= version.second);
+ }
+
private DeviceUtils() {}
}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
index 2df9603..f106b84 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
@@ -26,6 +26,7 @@
import android.os.WakeLockLevelEnum;
import android.server.ErrorSource;
+import com.android.compatibility.common.util.PropertyUtil;
import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
import com.android.internal.os.StatsdConfigProto.StatsdConfig;
import com.android.os.AtomsProto;
@@ -58,6 +59,7 @@
import com.android.tradefed.log.LogUtil;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.Pair;
import java.util.Arrays;
import java.util.Collections;
@@ -132,29 +134,54 @@
assertThat(atom.getState()).isEqualTo(AppBreadcrumbReported.State.START);
}
+ private boolean shouldTestLmkdStats() throws Exception {
+ boolean hasKernel = DeviceUtils.isKernelGreaterEqual(getDevice(), Pair.create(4, 19));
+ boolean hasFirstApiLevel = PropertyUtil.getFirstApiLevel(getDevice()) > 30;
+ return (hasKernel && hasFirstApiLevel)
+ || "true".equals(DeviceUtils.getProperty(getDevice(), "ro.lmk.log_stats"));
+ }
+
public void testLmkKillOccurred() throws Exception {
- if (!"true".equals(DeviceUtils.getProperty(getDevice(), "ro.lmk.log_stats"))) {
+ if (!shouldTestLmkdStats()) {
+ LogUtil.CLog.d("Skipping lmkd stats test.");
return;
}
- final int atomTag = Atom.LMK_KILL_OCCURRED_FIELD_NUMBER;
- final String actionLmk = "action.lmk";
ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
- atomTag, /*uidInAttributionChain=*/false);
-
- DeviceUtils.executeBackgroundService(getDevice(), actionLmk);
- Thread.sleep(15_000);
-
- // Sorted list of events in order in which they occurred.
- List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ Atom.LMK_KILL_OCCURRED_FIELD_NUMBER, /*uidInAttributionChain=*/false);
int appUid = DeviceUtils.getStatsdTestAppUid(getDevice());
- assertThat(data).hasSize(1);
- assertThat(data.get(0).getAtom().hasLmkKillOccurred()).isTrue();
- LmkKillOccurred atom = data.get(0).getAtom().getLmkKillOccurred();
- assertThat(atom.getUid()).isEqualTo(appUid);
- assertThat(atom.getProcessName()).isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG);
- assertThat(atom.getOomAdjScore()).isAtLeast(500);
+ // Start the victim process (service running in process :lmk_victim)
+ // We rely on a victim process (instead of expecting the allocating process to die)
+ // because it can be flaky and dependent on lmkd configuration
+ // (e.g. the OOM reaper can get to it first, depending on the allocation timings)
+ DeviceUtils.executeServiceAction(getDevice(), "LmkVictimBackgroundService",
+ "action.end_immediately");
+ // Start fg activity and allocate
+ try (AutoCloseable a = DeviceUtils.withActivity(
+ getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ "StatsdCtsForegroundActivity", "action", "action.lmk")) {
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = null;
+ for (int i = 0; i < 60; ++i) {
+ Thread.sleep(1_000);
+ data = ReportUtils.getEventMetricDataList(getDevice());
+ if (!data.isEmpty()) {
+ break;
+ }
+ }
+
+ assertThat(data).isNotEmpty();
+ // Even though both processes might have died, the non-fg one (victim)
+ // must have been first.
+ assertThat(data.get(0).getAtom().hasLmkKillOccurred()).isTrue();
+ LmkKillOccurred atom = data.get(0).getAtom().getLmkKillOccurred();
+ assertThat(atom.getUid()).isEqualTo(appUid);
+ assertThat(atom.getProcessName())
+ .isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG + ":lmk_victim");
+ assertThat(atom.getOomAdjScore()).isAtLeast(500);
+ assertThat(atom.getRssInBytes() + atom.getSwapInBytes()).isGreaterThan(0);
+ }
}
public void testAppCrashOccurred() throws Exception {