diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3b6b404170c50..9bc5c59dd35d7 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub ? 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 0000000000000..e82a207cdd59d --- /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 e1318af32301c..621849869590a 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -51,6 +51,7 @@ cc_library_static { "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 @@ cc_defaults { "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 0000000000000..e41de4d5d3c33 --- /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 +#include +#include +#include + +#include +#include +#include + +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 cce96ff5f4382..efffa6ca735b8 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -56,6 +56,7 @@ int register_android_server_net_NetworkStatsFactory(JNIEnv* env); 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 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) 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 782196dc00485..2baa4d81515a8 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -34,6 +34,7 @@ android_test { jni_libs: [ "libdexmakerjvmtiagent", + "libpsi", "libstaticjvmtiagent", ], diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 4ee9551542b55..1522a60850a1c 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -69,6 +69,7 @@ android_test { "liblog", "liblzma", "libnativehelper", + "libpsi", "libui", "libunwindstack", "libutils",