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