psi support for low memory detection inside ActivityManagerService
Add LowMemDetector class and use it in ActivityManagerService to detect
memory conditions based on in-kernel psi monitors.
Test: boots, detects low memory
Bug: 129476847
Change-Id: I7a05ba36196d24ce5147b97c076e38b92dc71e2e
Signed-off-by: Tim Murray <timmurray@google.com>
Signed-off-by: Suren Baghdasaryan <surenb@google.com>
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3b6b404..9bc5c59 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -544,6 +544,7 @@
private static final int NATIVE_DUMP_TIMEOUT_MS = 2000; // 2 seconds;
final OomAdjuster mOomAdjuster;
+ final LowMemDetector mLowMemDetector;
/** All system services */
SystemServiceManager mSystemServiceManager;
@@ -2296,6 +2297,7 @@
? new ActivityManagerConstants(mContext, this, mHandler) : null;
final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
mProcessList.init(this, activeUids);
+ mLowMemDetector = null;
mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
mIntentFirewall = hasHandlerThread
@@ -2344,6 +2346,7 @@
mConstants = new ActivityManagerConstants(mContext, this, mHandler);
final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */);
mProcessList.init(this, activeUids);
+ mLowMemDetector = new LowMemDetector(this);
mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
// Broadcast policy parameters
@@ -16523,25 +16526,29 @@
final boolean updateLowMemStateLocked(int numCached, int numEmpty, int numTrimming) {
final int N = mProcessList.getLruSizeLocked();
final long now = SystemClock.uptimeMillis();
- // Now determine the memory trimming level of background processes.
- // Unfortunately we need to start at the back of the list to do this
- // properly. We only do this if the number of background apps we
- // are managing to keep around is less than half the maximum we desire;
- // if we are keeping a good number around, we'll let them use whatever
- // memory they want.
- final int numCachedAndEmpty = numCached + numEmpty;
int memFactor;
- if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES
- && numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) {
- if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
- memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
- } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
- memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW;
- } else {
- memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE;
- }
+ if (mLowMemDetector != null && mLowMemDetector.isAvailable()) {
+ memFactor = mLowMemDetector.getMemFactor();
} else {
- memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+ // Now determine the memory trimming level of background processes.
+ // Unfortunately we need to start at the back of the list to do this
+ // properly. We only do this if the number of background apps we
+ // are managing to keep around is less than half the maximum we desire;
+ // if we are keeping a good number around, we'll let them use whatever
+ // memory they want.
+ if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES
+ && numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) {
+ final int numCachedAndEmpty = numCached + numEmpty;
+ if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
+ memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
+ } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
+ memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW;
+ } else {
+ memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE;
+ }
+ } else {
+ memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+ }
}
// We always allow the memory level to go up (better). We only allow it to go
// down if we are in a state where that is allowed, *and* the total number of processes
diff --git a/services/core/java/com/android/server/am/LowMemDetector.java b/services/core/java/com/android/server/am/LowMemDetector.java
new file mode 100644
index 0000000..e82a207
--- /dev/null
+++ b/services/core/java/com/android/server/am/LowMemDetector.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 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.am;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Detects low memory using PSI.
+ *
+ * If the kernel doesn't support PSI, then this class is not available.
+ */
+public final class LowMemDetector {
+ private static final String TAG = "LowMemDetector";
+ private final ActivityManagerService mAm;
+ private final LowMemThread mLowMemThread;
+ private boolean mAvailable;
+
+ private final Object mPressureStateLock = new Object();
+
+ @GuardedBy("mPressureStateLock")
+ private int mPressureState = MEM_PRESSURE_NONE;
+
+ /* getPressureState return values */
+ public static final int MEM_PRESSURE_NONE = 0;
+ public static final int MEM_PRESSURE_LOW = 1;
+ public static final int MEM_PRESSURE_MEDIUM = 2;
+ public static final int MEM_PRESSURE_HIGH = 3;
+
+ LowMemDetector(ActivityManagerService am) {
+ mAm = am;
+ mLowMemThread = new LowMemThread();
+ if (init() != 0) {
+ mAvailable = false;
+ } else {
+ mAvailable = true;
+ mLowMemThread.start();
+ }
+ }
+
+ public boolean isAvailable() {
+ return mAvailable;
+ }
+
+ /**
+ * Returns the current mem factor.
+ * Note that getMemFactor returns LowMemDetector.MEM_PRESSURE_XXX
+ * which match ProcessStats.ADJ_MEM_FACTOR_XXX values. If they deviate
+ * there should be conversion performed here to translate pressure state
+ * into memFactor.
+ */
+ public int getMemFactor() {
+ synchronized (mPressureStateLock) {
+ return mPressureState;
+ }
+ }
+
+ private native int init();
+ private native int waitForPressure();
+
+ private final class LowMemThread extends Thread {
+ public void run() {
+
+ while (true) {
+ // sleep waiting for a PSI event
+ int newPressureState = waitForPressure();
+ if (newPressureState == -1) {
+ // epoll broke, tear this down
+ mAvailable = false;
+ break;
+ }
+ // got a PSI event? let's update lowmem info
+ synchronized (mPressureStateLock) {
+ mPressureState = newPressureState;
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index e1318af..6218498 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -51,6 +51,7 @@
"com_android_server_PersistentDataBlockService.cpp",
"com_android_server_GraphicsStatsService.cpp",
"com_android_server_am_AppCompactor.cpp",
+ "com_android_server_am_LowMemDetector.cpp",
"onload.cpp",
":lib_networkStatsFactory_native",
],
@@ -104,6 +105,7 @@
"libbpf_android",
"libnetdbpf",
"libnetdutils",
+ "libpsi",
"android.hardware.audio.common@2.0",
"android.hardware.broadcastradio@1.0",
"android.hardware.broadcastradio@1.1",
diff --git a/services/core/jni/com_android_server_am_LowMemDetector.cpp b/services/core/jni/com_android_server_am_LowMemDetector.cpp
new file mode 100644
index 0000000..e41de4d
--- /dev/null
+++ b/services/core/jni/com_android_server_am_LowMemDetector.cpp
@@ -0,0 +1,156 @@
+/**
+** Copyright 2019, 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.
+*/
+
+#define LOG_TAG "LowMemDetector"
+
+#include <errno.h>
+#include <psi/psi.h>
+#include <string.h>
+#include <sys/epoll.h>
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+
+namespace android {
+
+enum pressure_levels {
+ PRESSURE_NONE,
+ PRESSURE_LOW,
+ PRESSURE_MEDIUM,
+ PRESSURE_HIGH,
+ PRESSURE_LEVEL_COUNT = PRESSURE_HIGH
+};
+
+// amount of stall in us for each level
+static constexpr int PSI_LOW_STALL_US = 15000;
+static constexpr int PSI_MEDIUM_STALL_US = 30000;
+static constexpr int PSI_HIGH_STALL_US = 50000;
+
+// stall tracking window size in us
+static constexpr int PSI_WINDOW_SIZE_US = 1000000;
+
+static int psi_epollfd = -1;
+
+static jint android_server_am_LowMemDetector_init(JNIEnv*, jobject) {
+ int epollfd;
+ int low_psi_fd;
+ int medium_psi_fd;
+ int high_psi_fd;
+
+ epollfd = epoll_create(PRESSURE_LEVEL_COUNT);
+ if (epollfd == -1) {
+ ALOGE("epoll_create failed: %s", strerror(errno));
+ return -1;
+ }
+
+ low_psi_fd = init_psi_monitor(PSI_SOME, PSI_LOW_STALL_US, PSI_WINDOW_SIZE_US);
+ if (low_psi_fd < 0 ||
+ register_psi_monitor(epollfd, low_psi_fd, (void*)PRESSURE_LOW) != 0) {
+ goto low_fail;
+ }
+
+ medium_psi_fd =
+ init_psi_monitor(PSI_FULL, PSI_MEDIUM_STALL_US, PSI_WINDOW_SIZE_US);
+ if (medium_psi_fd < 0 || register_psi_monitor(epollfd, medium_psi_fd,
+ (void*)PRESSURE_MEDIUM) != 0) {
+ goto medium_fail;
+ }
+
+ high_psi_fd =
+ init_psi_monitor(PSI_FULL, PSI_HIGH_STALL_US, PSI_WINDOW_SIZE_US);
+ if (high_psi_fd < 0 ||
+ register_psi_monitor(epollfd, high_psi_fd, (void*)PRESSURE_HIGH) != 0) {
+ goto high_fail;
+ }
+
+ psi_epollfd = epollfd;
+ return 0;
+
+high_fail:
+ unregister_psi_monitor(epollfd, medium_psi_fd);
+medium_fail:
+ unregister_psi_monitor(epollfd, low_psi_fd);
+low_fail:
+ ALOGE("Failed to register psi trigger");
+ close(epollfd);
+ return -1;
+}
+
+static jint android_server_am_LowMemDetector_waitForPressure(JNIEnv*, jobject) {
+ static uint32_t pressure_level = PRESSURE_NONE;
+ struct epoll_event events[PRESSURE_LEVEL_COUNT];
+ int nevents = 0;
+
+ if (psi_epollfd < 0) {
+ ALOGE("Memory pressure detector is not initialized");
+ return -1;
+ }
+
+ do {
+ if (pressure_level == PRESSURE_NONE) {
+ /* Wait for events with no timeout */
+ nevents = epoll_wait(psi_epollfd, events, PRESSURE_LEVEL_COUNT, -1);
+ } else {
+ // This is simpler than lmkd. Assume that the memory pressure
+ // state will stay high for at least 1s. Within that 1s window,
+ // the memory pressure state can go up due to a different FD
+ // becoming available or it can go down when that window expires.
+ // Accordingly, there's no polling: just epoll_wait with a 1s timeout.
+ nevents = epoll_wait(psi_epollfd, events, PRESSURE_LEVEL_COUNT, 1000);
+ if (nevents == 0) {
+ pressure_level = PRESSURE_NONE;
+ return pressure_level;
+ }
+ }
+ // keep waiting if interrupted
+ } while (nevents == -1 && errno == EINTR);
+
+ if (nevents == -1) {
+ ALOGE("epoll_wait failed while waiting for psi events: %s", strerror(errno));
+ return -1;
+ }
+
+ // reset pressure_level and raise it based on received events
+ pressure_level = PRESSURE_NONE;
+ for (int i = 0; i < nevents; i++) {
+ if (events[i].events & (EPOLLERR | EPOLLHUP)) {
+ // should never happen unless psi got disabled in kernel
+ ALOGE("Memory pressure events are not available anymore");
+ return -1;
+ }
+ // record the highest reported level
+ if (events[i].data.u32 > pressure_level) {
+ pressure_level = events[i].data.u32;
+ }
+ }
+
+ return pressure_level;
+}
+
+static const JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ {"init", "()I", (void*)android_server_am_LowMemDetector_init},
+ {"waitForPressure", "()I",
+ (void*)android_server_am_LowMemDetector_waitForPressure},
+};
+
+int register_android_server_am_LowMemDetector(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/am/LowMemDetector",
+ sMethods, NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index cce96ff..efffa6c 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,7 @@
int register_android_server_net_NetworkStatsService(JNIEnv* env);
int register_android_server_security_VerityUtils(JNIEnv* env);
int register_android_server_am_AppCompactor(JNIEnv* env);
+int register_android_server_am_LowMemDetector(JNIEnv* env);
};
using namespace android;
@@ -105,5 +106,6 @@
register_android_server_net_NetworkStatsService(env);
register_android_server_security_VerityUtils(env);
register_android_server_am_AppCompactor(env);
+ register_android_server_am_LowMemDetector(env);
return JNI_VERSION_1_4;
}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 782196d..2baa4d8 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -34,6 +34,7 @@
jni_libs: [
"libdexmakerjvmtiagent",
+ "libpsi",
"libstaticjvmtiagent",
],
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 4ee9551..1522a60 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -69,6 +69,7 @@
"liblog",
"liblzma",
"libnativehelper",
+ "libpsi",
"libui",
"libunwindstack",
"libutils",