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>
This commit is contained in:
Tim Murray
2019-02-13 20:47:46 -08:00
parent 0a069df47f
commit 676d42d983
7 changed files with 278 additions and 17 deletions

View File

@@ -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

View File

@@ -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;
}
}
}
}
}

View File

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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -34,6 +34,7 @@ android_test {
jni_libs: [
"libdexmakerjvmtiagent",
"libpsi",
"libstaticjvmtiagent",
],

View File

@@ -69,6 +69,7 @@ android_test {
"liblog",
"liblzma",
"libnativehelper",
"libpsi",
"libui",
"libunwindstack",
"libutils",