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",