Add out-of-memory (OOM) kills to ApplicationExitInfo
Adding the capability to track processes that have been affected by the
OOM killer.
Test: Trigger OOM kill and verify `noteAppKill()` is called with the matching OOM victim data.
Bug: 244232958
Bug: 234500267
Change-Id: I7e948a7826751a5a6f64f9ac9614864b3632fb4e
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 7b3d017..d15c79f 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -468,6 +468,15 @@
*/
public static final int SUBREASON_EXCESSIVE_BINDER_OBJECTS = 29;
+ /**
+ * The process was killed by the [kernel] Out-of-memory (OOM) killer; this
+ * would be set only when the reason is {@link #REASON_LOW_MEMORY}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_OOM_KILL = 30;
+
// If there is any OEM code which involves additional app kill reasons, it should
// be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
@@ -644,6 +653,7 @@
SUBREASON_PACKAGE_UPDATE,
SUBREASON_UNDELIVERED_BROADCAST,
SUBREASON_EXCESSIVE_BINDER_OBJECTS,
+ SUBREASON_OOM_KILL,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SubReason {}
@@ -1371,6 +1381,8 @@
return "UNDELIVERED BROADCAST";
case SUBREASON_EXCESSIVE_BINDER_OBJECTS:
return "EXCESSIVE BINDER OBJECTS";
+ case SUBREASON_OOM_KILL:
+ return "OOM KILL";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/os/OomKillRecord.java b/core/java/android/os/OomKillRecord.java
new file mode 100644
index 0000000..151a65f
--- /dev/null
+++ b/core/java/android/os/OomKillRecord.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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 android.os;
+
+
+/**
+ * Expected data to get back from the OOM event's file.
+ * Note that this should be equivalent to the struct <b>OomKill</b> inside
+ * <pre>
+ * system/memory/libmeminfo/libmemevents/include/memevents.h
+ * </pre>
+ *
+ * @hide
+ */
+public final class OomKillRecord {
+ private long mTimeStampInMillis;
+ private int mPid;
+ private int mUid;
+ private String mProcessName;
+ private short mOomScoreAdj;
+
+ public OomKillRecord(long timeStampInMillis, int pid, int uid,
+ String processName, short oomScoreAdj) {
+ this.mTimeStampInMillis = timeStampInMillis;
+ this.mPid = pid;
+ this.mUid = uid;
+ this.mProcessName = processName;
+ this.mOomScoreAdj = oomScoreAdj;
+ }
+
+ public long getTimestampMilli() {
+ return mTimeStampInMillis;
+ }
+
+ public int getPid() {
+ return mPid;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public String getProcessName() {
+ return mProcessName;
+ }
+
+ public short getOomScoreAdj() {
+ return mOomScoreAdj;
+ }
+}
diff --git a/services/core/java/com/android/server/am/OomConnection.java b/services/core/java/com/android/server/am/OomConnection.java
new file mode 100644
index 0000000..17a4ce5
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomConnection.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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 android.os.OomKillRecord;
+import android.util.Slog;
+
+/** Connection to the out-of-memory (OOM) events' file */
+public final class OomConnection {
+ private static final String TAG = "OomConnection";
+
+ /** Connection listener interface */
+ public interface OomConnectionListener {
+
+ /**
+ * Callback function to handle the newest OOM kills.
+ *
+ * @param oomKills List of oom kills received from `waitOom()`
+ */
+ void handleOomEvent(OomKillRecord[] oomKills);
+ }
+
+ private final OomConnectionListener mOomListener;
+
+ private final OomConnectionThread mOomConnectionThread;
+
+ private static native OomKillRecord[] waitOom();
+
+ public OomConnection(OomConnectionListener listener) {
+ mOomListener = listener;
+ mOomConnectionThread = new OomConnectionThread();
+ mOomConnectionThread.start();
+ }
+
+ private final class OomConnectionThread extends Thread {
+ public void run() {
+ while (true) {
+ OomKillRecord[] oom_kills = null;
+ try {
+ oom_kills = waitOom();
+ mOomListener.handleOomEvent(oom_kills);
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "failed waiting for OOM events: " + e);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 0615ecf..74d84e4 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -97,6 +97,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.OomKillRecord;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -412,6 +413,8 @@
private static LmkdConnection sLmkdConnection = null;
+ private static OomConnection sOomConnection = null;
+
private boolean mOomLevelsSet = false;
private boolean mAppDataIsolationEnabled = false;
@@ -855,6 +858,21 @@
THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
sKillThread.start();
sKillHandler = new KillHandler(sKillThread.getLooper());
+ sOomConnection = new OomConnection(new OomConnection.OomConnectionListener() {
+ @Override
+ public void handleOomEvent(OomKillRecord[] oomKills) {
+ for (OomKillRecord oomKill: oomKills) {
+ synchronized (mProcLock) {
+ noteAppKill(
+ oomKill.getPid(),
+ oomKill.getUid(),
+ ApplicationExitInfo.REASON_LOW_MEMORY,
+ ApplicationExitInfo.SUBREASON_OOM_KILL,
+ "oom");
+ }
+ }
+ }
+ });
sLmkdConnection = new LmkdConnection(sKillThread.getLooper().getQueue(),
new LmkdConnection.LmkdConnectionListener() {
@Override
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index ec5378f..bc70658 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -78,6 +78,7 @@
"onload.cpp",
":lib_cachedAppOptimizer_native",
":lib_gameManagerService_native",
+ ":lib_oomConnection_native",
],
include_dirs: [
@@ -122,6 +123,7 @@
"libhardware_legacy",
"libhidlbase",
"libmeminfo",
+ "libmemevents",
"libmemtrackproxy",
"libmtp",
"libnativehelper",
@@ -239,3 +241,8 @@
"com_android_server_app_GameManagerService.cpp",
],
}
+
+filegroup {
+ name: "lib_oomConnection_native",
+ srcs: ["com_android_server_am_OomConnection.cpp",],
+}
diff --git a/services/core/jni/com_android_server_am_OomConnection.cpp b/services/core/jni/com_android_server_am_OomConnection.cpp
new file mode 100644
index 0000000..e892d23
--- /dev/null
+++ b/services/core/jni/com_android_server_am_OomConnection.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 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 "OomConnection"
+
+#include <core_jni_helpers.h>
+#include <jni.h>
+#include <memevents/memevents.h>
+
+namespace android {
+
+// Used to cache the results of the JNI name lookup
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+} sOomKillRecordInfo;
+
+static memevents::MemEventListener memevent_listener;
+
+/**
+ * Initialize listening and waiting for new out-of-memory (OOM) events to occur.
+ * Once a OOM event is detected, we then fetch the list of OOM kills, and return
+ * a corresponding java array with the information gathered.
+ *
+ * In the case that we encounter an error, we make sure to close the epfd, and
+ * the OOM file descriptor, by calling `deregisterAllEvents()`.
+ *
+ * @return list of `android.os.OomKillRecord`
+ * @throws java.lang.RuntimeException
+ */
+static jobjectArray android_server_am_OomConnection_waitOom(JNIEnv* env, jobject) {
+ const memevents::MemEvent oom_event = memevents::MemEvent::OOM_KILL;
+ if (!memevent_listener.registerEvent(oom_event)) {
+ memevent_listener.deregisterAllEvents();
+ jniThrowRuntimeException(env, "listener failed to register to OOM events");
+ return nullptr;
+ }
+
+ memevents::MemEvent event_received;
+ do {
+ event_received = memevent_listener.listen();
+ if (event_received == memevents::MemEvent::ERROR) {
+ memevent_listener.deregisterAllEvents();
+ jniThrowRuntimeException(env, "listener received error event");
+ return nullptr;
+ }
+ } while (event_received != oom_event);
+
+ std::vector<memevents::OomKill> oom_events;
+ if (!memevent_listener.getOomEvents(oom_events)) {
+ memevent_listener.deregisterAllEvents();
+ jniThrowRuntimeException(env, "Failed to get OOM events");
+ return nullptr;
+ }
+
+ jobjectArray java_oom_array =
+ env->NewObjectArray(oom_events.size(), sOomKillRecordInfo.clazz, nullptr);
+ if (java_oom_array == NULL) {
+ memevent_listener.deregisterAllEvents();
+ jniThrowRuntimeException(env, "Failed to create OomKillRecord array");
+ return nullptr;
+ }
+
+ for (int i = 0; i < oom_events.size(); i++) {
+ const memevents::OomKill oom_event = oom_events[i];
+ jstring process_name = env->NewStringUTF(oom_event.process_name);
+ if (process_name == NULL) {
+ memevent_listener.deregisterAllEvents();
+ jniThrowRuntimeException(env, "Failed creating java string for process name");
+ }
+ jobject java_oom_kill = env->NewObject(sOomKillRecordInfo.clazz, sOomKillRecordInfo.ctor,
+ oom_event.timestamp_ms, oom_event.pid, oom_event.uid,
+ process_name, oom_event.oom_score_adj);
+ if (java_oom_kill == NULL) {
+ memevent_listener.deregisterAllEvents();
+ jniThrowRuntimeException(env, "Failed to create OomKillRecord object");
+ return java_oom_array;
+ }
+ env->SetObjectArrayElement(java_oom_array, i, java_oom_kill);
+ }
+ return java_oom_array;
+}
+
+static const JNINativeMethod sOomConnectionMethods[] = {
+ /* name, signature, funcPtr */
+ {"waitOom", "()[Landroid/os/OomKillRecord;",
+ (void*)android_server_am_OomConnection_waitOom},
+};
+
+int register_android_server_am_OomConnection(JNIEnv* env) {
+ sOomKillRecordInfo.clazz = FindClassOrDie(env, "android/os/OomKillRecord");
+ sOomKillRecordInfo.clazz = MakeGlobalRefOrDie(env, sOomKillRecordInfo.clazz);
+
+ sOomKillRecordInfo.ctor =
+ GetMethodIDOrDie(env, sOomKillRecordInfo.clazz, "<init>", "(JIILjava/lang/String;S)V");
+
+ return RegisterMethodsOrDie(env, "com/android/server/am/OomConnection", sOomConnectionMethods,
+ NELEM(sOomConnectionMethods));
+}
+} // namespace android
\ No newline at end of file
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index f6f6737..df44895 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -52,6 +52,7 @@
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
int register_android_hardware_display_DisplayViewport(JNIEnv* env);
+int register_android_server_am_OomConnection(JNIEnv* env);
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
@@ -112,6 +113,7 @@
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
register_android_hardware_display_DisplayViewport(env);
+ register_android_server_am_OomConnection(env);
register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_LowMemDetector(env);
register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);