Per process DMA-BUF stats

In order to detect memory regressions caused by improper handling of
dma-buf buffers we need to measure how this memory is used.

This change:
- Introduces DmabufInfoReader which wraps libmeminfo and provides
per-process DMA-BUF stats.
- Specifically, measures buffers mapped to the process address space
(from /proc/pid/maps). This is supported on all devices.
- For devices running 5.4+ (where the system processes can query fdinfo)
also measures the total retained memory (either by mmap or an open fd).
- Introduces a statsd atom that collects this for all running managed
processes. Non-managed processes will also hold dmabufs, but the
likelihood of them causing issues (post-launch) is smaller so I am
trading them off for cheaper collection (if indeed we encounter such
problems, they will definitely be caught by the total dmabuf counters).

Test: manual
Bug: 183708249
Change-Id: I5e0ef58ac1a66ebe2d280c5733de46ba84f75a46
This commit is contained in:
Ioannis Ilkos
2021-03-25 15:44:03 +00:00
parent 1d70937fe7
commit 228fc0293b
5 changed files with 157 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2021 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.internal.os;
import android.annotation.Nullable;
/** Wrapper around libdmabufinfo. */
public final class DmabufInfoReader {
private DmabufInfoReader() {}
/** Process dma-buf stats. */
public static final class ProcessDmabuf {
/** Size of buffers retained by the process. */
public final int retainedSizeKb;
/** Number of buffers retained by the process. */
public final int retainedBuffersCount;
/** Size of buffers mapped to the address space. */
public final int mappedSizeKb;
/** Count of buffers mapped to the address space. */
public final int mappedBuffersCount;
ProcessDmabuf(int retainedSizeKb, int retainedBuffersCount,
int mappedSizeKb, int mappedBuffersCount) {
this.retainedSizeKb = retainedSizeKb;
this.retainedBuffersCount = retainedBuffersCount;
this.mappedSizeKb = mappedSizeKb;
this.mappedBuffersCount = mappedBuffersCount;
}
}
/**
* Return stats for DMA-BUFs retained by process pid or null if the DMA-BUF
* stats could not be read.
*/
@Nullable
public static native ProcessDmabuf getProcessStats(int pid);
}

View File

@@ -202,6 +202,7 @@ cc_library_shared {
"com_android_internal_content_om_OverlayConfig.cpp",
"com_android_internal_net_NetworkUtilsInternal.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
"com_android_internal_os_DmabufInfoReader.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
"com_android_internal_os_KernelCpuBpfTracking.cpp",
"com_android_internal_os_KernelCpuTotalBpfMapReader.cpp",

View File

@@ -191,6 +191,7 @@ extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env
extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_DmabufInfoReader(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv* env);
extern int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv* env);
@@ -1616,6 +1617,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_animation_PropertyValuesHolder),
REG_JNI(register_android_security_Scrypt),
REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
REG_JNI(register_com_android_internal_os_DmabufInfoReader),
REG_JNI(register_com_android_internal_os_FuseAppLoop),
REG_JNI(register_com_android_internal_os_KernelCpuBpfTracking),
REG_JNI(register_com_android_internal_os_KernelCpuTotalBpfMapReader),

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2021 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.
*/
#include <dmabufinfo/dmabufinfo.h>
#include "core_jni_helpers.h"
namespace android {
static jobject DmabufInfoReader_getProcessStats(JNIEnv *env, jobject, jint pid) {
std::vector<dmabufinfo::DmaBuffer> buffers;
if (!dmabufinfo::ReadDmaBufMapRefs(pid, &buffers)) {
return nullptr;
}
jint mappedSize = 0;
jint mappedCount = buffers.size();
for (const auto &buffer : buffers) {
mappedSize += buffer.size();
}
mappedSize /= 1024;
jint retainedSize = -1;
jint retainedCount = -1;
if (dmabufinfo::ReadDmaBufFdRefs(pid, &buffers)) {
retainedCount = buffers.size();
retainedSize = 0;
for (const auto &buffer : buffers) {
retainedSize += buffer.size();
}
retainedSize /= 1024;
}
jclass clazz = FindClassOrDie(env, "com/android/internal/os/DmabufInfoReader$ProcessDmabuf");
jmethodID constructID = GetMethodIDOrDie(env, clazz, "<init>", "(IIII)V");
return env->NewObject(clazz, constructID, retainedSize, retainedCount, mappedSize, mappedCount);
}
static const JNINativeMethod methods[] = {
{"getProcessStats", "(I)Lcom/android/internal/os/DmabufInfoReader$ProcessDmabuf;",
(void *)DmabufInfoReader_getProcessStats},
};
int register_com_android_internal_os_DmabufInfoReader(JNIEnv *env) {
return RegisterMethodsOrDie(env, "com/android/internal/os/DmabufInfoReader", methods,
NELEM(methods));
}
} // namespace android

View File

@@ -111,6 +111,7 @@ import android.os.IThermalService;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StatFs;
@@ -148,6 +149,7 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.DmabufInfoReader;
import com.android.internal.os.KernelCpuBpfTracking;
import com.android.internal.os.KernelCpuThreadReader;
import com.android.internal.os.KernelCpuThreadReaderDiff;
@@ -198,6 +200,7 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -528,6 +531,8 @@ public class StatsPullAtomService extends SystemService {
synchronized (mProcessSystemIonHeapSizeLock) {
return pullProcessSystemIonHeapSizeLocked(atomTag, data);
}
case FrameworkStatsLog.PROCESS_DMABUF_MEMORY:
return pullProcessDmabufMemory(atomTag, data);
case FrameworkStatsLog.SYSTEM_MEMORY:
return pullSystemMemory(atomTag, data);
case FrameworkStatsLog.TEMPERATURE:
@@ -818,6 +823,7 @@ public class StatsPullAtomService extends SystemService {
registerIonHeapSize();
registerProcessSystemIonHeapSize();
registerSystemMemory();
registerProcessDmabufMemory();
registerTemperature();
registerCoolingDevice();
registerBinderCallsStats();
@@ -2183,6 +2189,43 @@ public class StatsPullAtomService extends SystemService {
return StatsManager.PULL_SUCCESS;
}
private void registerProcessDmabufMemory() {
int tagId = FrameworkStatsLog.PROCESS_DMABUF_MEMORY;
mStatsManager.setPullAtomCallback(
tagId,
null, // use default PullAtomMetadata values
DIRECT_EXECUTOR,
mStatsCallbackImpl
);
}
int pullProcessDmabufMemory(int atomTag, List<StatsEvent> pulledData) {
List<ProcessMemoryState> managedProcessList =
LocalServices.getService(ActivityManagerInternal.class)
.getMemoryStateForProcesses();
managedProcessList.sort(Comparator.comparingInt(x -> x.oomScore));
for (ProcessMemoryState process : managedProcessList) {
if (process.uid == Process.SYSTEM_UID) {
continue;
}
DmabufInfoReader.ProcessDmabuf proc = DmabufInfoReader.getProcessStats(process.pid);
if (proc == null || (proc.retainedBuffersCount <= 0 && proc.mappedBuffersCount <= 0)) {
continue;
}
pulledData.add(
FrameworkStatsLog.buildStatsEvent(
atomTag,
process.uid,
process.processName,
process.oomScore,
proc.retainedSizeKb,
proc.retainedBuffersCount,
proc.mappedSizeKb,
proc.mappedBuffersCount));
}
return StatsManager.PULL_SUCCESS;
}
private void registerSystemMemory() {
int tagId = FrameworkStatsLog.SYSTEM_MEMORY;
mStatsManager.setPullAtomCallback(