1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include <android-base/logging.h>
17 #include <android-base/stringprintf.h>
18 #include <dmabufinfo/dmabufinfo.h>
19 #include <jni.h>
20 #include <meminfo/sysmeminfo.h>
21 #include <procinfo/process.h>
22
23 #include "core_jni_helpers.h"
24
25 using DmaBuffer = ::android::dmabufinfo::DmaBuffer;
26 using android::base::ReadFileToString;
27 using android::base::StringPrintf;
28
29 namespace {
30 static jclass gProcessDmabufClazz;
31 static jmethodID gProcessDmabufCtor;
32 static jclass gProcessGpuMemClazz;
33 static jmethodID gProcessGpuMemCtor;
34 } // namespace
35
36 namespace android {
37
38 struct PidDmaInfo {
39 uid_t uid;
40 std::string cmdline;
41 int oomScoreAdj;
42 };
43
KernelAllocationStats_getDmabufAllocations(JNIEnv * env,jobject)44 static jobjectArray KernelAllocationStats_getDmabufAllocations(JNIEnv *env, jobject) {
45 std::vector<DmaBuffer> buffers;
46
47 if (!dmabufinfo::ReadProcfsDmaBufs(&buffers)) {
48 return nullptr;
49 }
50
51 // Create a reverse map from pid to dmabufs
52 // Store dmabuf inodes & sizes for later processing.
53 std::unordered_map<pid_t, std::set<ino_t>> pidToInodes;
54 std::unordered_map<ino_t, long> inodeToSize;
55 for (auto &buf : buffers) {
56 for (auto pid : buf.pids()) {
57 pidToInodes[pid].insert(buf.inode());
58 }
59 inodeToSize[buf.inode()] = buf.size();
60 }
61
62 pid_t surfaceFlingerPid = -1;
63 // The set of all inodes that are being retained by SurfaceFlinger. Buffers
64 // shared between another process and SF will appear in this set.
65 std::set<ino_t> surfaceFlingerBufferInodes;
66 // The set of all inodes that are being retained by any process other
67 // than SurfaceFlinger. Buffers shared between another process and SF will
68 // appear in this set.
69 std::set<ino_t> otherProcessBufferInodes;
70
71 // Find SurfaceFlinger pid & get cmdlines, oomScoreAdj, etc for each pid
72 // holding any DMA buffers.
73 std::unordered_map<pid_t, PidDmaInfo> pidDmaInfos;
74 for (const auto &pidToInodeEntry : pidToInodes) {
75 pid_t pid = pidToInodeEntry.first;
76
77 android::procinfo::ProcessInfo processInfo;
78 if (!android::procinfo::GetProcessInfo(pid, &processInfo)) {
79 continue;
80 }
81
82 std::string cmdline;
83 if (!ReadFileToString(StringPrintf("/proc/%d/cmdline", pid), &cmdline)) {
84 continue;
85 }
86
87 // cmdline strings are null-delimited, so we split on \0 here
88 if (cmdline.substr(0, cmdline.find('\0')) == "/system/bin/surfaceflinger") {
89 if (surfaceFlingerPid == -1) {
90 surfaceFlingerPid = pid;
91 surfaceFlingerBufferInodes = pidToInodes[pid];
92 } else {
93 LOG(ERROR) << "getDmabufAllocations found multiple SF processes; pid1: " << pid
94 << ", pid2:" << surfaceFlingerPid;
95 surfaceFlingerPid = -2; // Used as a sentinel value below
96 }
97 } else {
98 otherProcessBufferInodes.insert(pidToInodes[pid].begin(), pidToInodes[pid].end());
99 }
100
101 std::string oomScoreAdjStr;
102 if (!ReadFileToString(StringPrintf("/proc/%d/oom_score_adj", pid), &oomScoreAdjStr)) {
103 continue;
104 }
105
106 pidDmaInfos[pid] = PidDmaInfo{.uid = processInfo.uid,
107 .cmdline = cmdline,
108 .oomScoreAdj = atoi(oomScoreAdjStr.c_str())};
109 }
110
111 if (surfaceFlingerPid < 0) {
112 LOG(ERROR) << "getDmabufAllocations could not identify SurfaceFlinger "
113 << "process via /proc/pid/cmdline";
114 }
115
116 jobjectArray ret = env->NewObjectArray(pidDmaInfos.size(), gProcessDmabufClazz, NULL);
117 int retArrayIndex = 0;
118 for (const auto &pidDmaInfosEntry : pidDmaInfos) {
119 pid_t pid = pidDmaInfosEntry.first;
120
121 // For all processes apart from SurfaceFlinger, this set will store the
122 // dmabuf inodes that are shared with SF. For SF, it will store the inodes
123 // that are shared with any other process.
124 std::set<ino_t> sharedBuffers;
125 if (pid == surfaceFlingerPid) {
126 set_intersection(surfaceFlingerBufferInodes.begin(), surfaceFlingerBufferInodes.end(),
127 otherProcessBufferInodes.begin(), otherProcessBufferInodes.end(),
128 std::inserter(sharedBuffers, sharedBuffers.end()));
129 } else if (surfaceFlingerPid > 0) {
130 set_intersection(pidToInodes[pid].begin(), pidToInodes[pid].end(),
131 surfaceFlingerBufferInodes.begin(), surfaceFlingerBufferInodes.end(),
132 std::inserter(sharedBuffers, sharedBuffers.begin()));
133 } // If surfaceFlingerPid < 0; it means we failed to identify it, and
134 // the SF-related fields below should be left empty.
135
136 long totalSize = 0;
137 long sharedBuffersSize = 0;
138 for (const auto &inode : pidToInodes[pid]) {
139 totalSize += inodeToSize[inode];
140 if (sharedBuffers.count(inode)) {
141 sharedBuffersSize += inodeToSize[inode];
142 }
143 }
144
145 jobject obj = env->NewObject(gProcessDmabufClazz, gProcessDmabufCtor,
146 /* uid */ pidDmaInfos[pid].uid,
147 /* process name */
148 env->NewStringUTF(pidDmaInfos[pid].cmdline.c_str()),
149 /* oomscore */ pidDmaInfos[pid].oomScoreAdj,
150 /* retainedSize */ totalSize / 1024,
151 /* retainedCount */ pidToInodes[pid].size(),
152 /* sharedWithSurfaceFlinger size */ sharedBuffersSize / 1024,
153 /* sharedWithSurfaceFlinger count */ sharedBuffers.size());
154
155 env->SetObjectArrayElement(ret, retArrayIndex++, obj);
156 }
157
158 return ret;
159 }
160
KernelAllocationStats_getGpuAllocations(JNIEnv * env)161 static jobject KernelAllocationStats_getGpuAllocations(JNIEnv *env) {
162 std::unordered_map<uint32_t, uint64_t> out;
163 meminfo::ReadPerProcessGpuMem(&out);
164 jobjectArray result = env->NewObjectArray(out.size(), gProcessGpuMemClazz, nullptr);
165 if (result == NULL) {
166 jniThrowRuntimeException(env, "Cannot create result array");
167 return nullptr;
168 }
169 int idx = 0;
170 for (const auto &entry : out) {
171 jobject pidStats =
172 env->NewObject(gProcessGpuMemClazz, gProcessGpuMemCtor, entry.first, entry.second);
173 env->SetObjectArrayElement(result, idx, pidStats);
174 env->DeleteLocalRef(pidStats);
175 ++idx;
176 }
177 return result;
178 }
179
180 static const JNINativeMethod methods[] = {
181 {"getDmabufAllocations", "()[Lcom/android/internal/os/KernelAllocationStats$ProcessDmabuf;",
182 (void *)KernelAllocationStats_getDmabufAllocations},
183 {"getGpuAllocations", "()[Lcom/android/internal/os/KernelAllocationStats$ProcessGpuMem;",
184 (void *)KernelAllocationStats_getGpuAllocations},
185 };
186
register_com_android_internal_os_KernelAllocationStats(JNIEnv * env)187 int register_com_android_internal_os_KernelAllocationStats(JNIEnv *env) {
188 int res = RegisterMethodsOrDie(env, "com/android/internal/os/KernelAllocationStats", methods,
189 NELEM(methods));
190 jclass clazz =
191 FindClassOrDie(env, "com/android/internal/os/KernelAllocationStats$ProcessDmabuf");
192 gProcessDmabufClazz = MakeGlobalRefOrDie(env, clazz);
193 gProcessDmabufCtor =
194 GetMethodIDOrDie(env, gProcessDmabufClazz, "<init>", "(ILjava/lang/String;IIIII)V");
195
196 clazz = FindClassOrDie(env, "com/android/internal/os/KernelAllocationStats$ProcessGpuMem");
197 gProcessGpuMemClazz = MakeGlobalRefOrDie(env, clazz);
198 gProcessGpuMemCtor = GetMethodIDOrDie(env, gProcessGpuMemClazz, "<init>", "(II)V");
199 return res;
200 }
201
202 } // namespace android