1 /*
2 * Copyright (C) 2019 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
17 #include <dirent.h>
18 #include <errno.h>
19 #include <getopt.h>
20 #include <inttypes.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24
25 #include <fstream>
26 #include <iostream>
27 #include <map>
28 #include <set>
29 #include <sstream>
30 #include <string>
31 #include <vector>
32
33 #include <android-base/stringprintf.h>
34 #include <dmabufinfo/dmabufinfo.h>
35
36 using DmaBuffer = ::android::dmabufinfo::DmaBuffer;
37
usage(int exit_status)38 [[noreturn]] static void usage(int exit_status) {
39 fprintf(stderr,
40 "Usage: %s [-ah] [PID] \n"
41 "-a\t show all dma buffers (ion) in big table, [buffer x process] grid \n"
42 "-h\t show this help\n"
43 " \t If PID is supplied, the dmabuf information for that process is shown.\n",
44 getprogname());
45
46 exit(exit_status);
47 }
48
GetProcessComm(const pid_t pid)49 static std::string GetProcessComm(const pid_t pid) {
50 std::string pid_path = android::base::StringPrintf("/proc/%d/comm", pid);
51 std::ifstream in{pid_path};
52 if (!in) return std::string("N/A");
53 std::string line;
54 std::getline(in, line);
55 if (!in) return std::string("N/A");
56 return line;
57 }
58
PrintDmaBufTable(const std::vector<DmaBuffer> & bufs)59 static void PrintDmaBufTable(const std::vector<DmaBuffer>& bufs) {
60 if (bufs.empty()) {
61 printf("dmabuf info not found ¯\\_(ツ)_/¯\n");
62 return;
63 }
64
65 // Find all unique pids in the input vector, create a set
66 std::set<pid_t> pid_set;
67 for (auto& buf : bufs) {
68 pid_set.insert(buf.pids().begin(), buf.pids().end());
69 }
70
71 // Format the header string spaced and separated with '|'
72 printf(" Dmabuf Inode | Size | Fd Ref Counts | Map Ref Counts |");
73 for (auto pid : pid_set) {
74 printf("%16s:%-5d |", GetProcessComm(pid).c_str(), pid);
75 }
76 printf("\n");
77
78 // holds per-process dmabuf size in kB
79 std::map<pid_t, uint64_t> per_pid_size = {};
80 uint64_t dmabuf_total_size = 0;
81
82 // Iterate through all dmabufs and collect per-process sizes, refs
83 for (auto& buf : bufs) {
84 printf("%16ju |%13" PRIu64 " kB |%16zu |%16zu |",
85 static_cast<uintmax_t>(buf.inode()), buf.size() / 1024, buf.fdrefs().size(),
86 buf.maprefs().size());
87 // Iterate through each process to find out per-process references for each buffer,
88 // gather total size used by each process etc.
89 for (pid_t pid : pid_set) {
90 int pid_refs = 0;
91 if (buf.fdrefs().count(pid) == 1) {
92 // Get the total number of ref counts the process is holding
93 // on this buffer. We don't differentiate between mmap or fd.
94 pid_refs += buf.fdrefs().at(pid);
95 if (buf.maprefs().count(pid) == 1) {
96 pid_refs += buf.maprefs().at(pid);
97 }
98 }
99
100 if (pid_refs) {
101 // Add up the per-pid total size. Note that if a buffer is mapped
102 // in 2 different processes, the size will be shown as mapped or opened
103 // in both processes. This is intended for visibility.
104 //
105 // If one wants to get the total *unique* dma buffers, they can simply
106 // sum the size of all dma bufs shown by the tool
107 per_pid_size[pid] += buf.size() / 1024;
108 printf("%17d refs |", pid_refs);
109 } else {
110 printf("%22s |", "--");
111 }
112 }
113 dmabuf_total_size += buf.size() / 1024;
114 printf("\n");
115 }
116
117 printf("------------------------------------\n");
118 printf("%-16s %13" PRIu64 " kB |%16s |", "TOTALS", dmabuf_total_size, "n/a");
119 for (auto pid : pid_set) {
120 printf("%19" PRIu64 " kB |", per_pid_size[pid]);
121 }
122 printf("\n");
123
124 return;
125 }
126
PrintDmaBufPerProcess(const std::vector<DmaBuffer> & bufs)127 static void PrintDmaBufPerProcess(const std::vector<DmaBuffer>& bufs) {
128 if (bufs.empty()) {
129 printf("dmabuf info not found ¯\\_(ツ)_/¯\n");
130 return;
131 }
132
133 // Create a reverse map from pid to dmabufs
134 std::unordered_map<pid_t, std::set<ino_t>> pid_to_inodes = {};
135 uint64_t total_size = 0; // Total size of dmabufs in the system
136 uint64_t kernel_rss = 0; // Total size of dmabufs NOT mapped or opened by a process
137 for (auto& buf : bufs) {
138 for (auto pid : buf.pids()) {
139 pid_to_inodes[pid].insert(buf.inode());
140 }
141 total_size += buf.size();
142 if (buf.fdrefs().empty() && buf.maprefs().empty()) {
143 kernel_rss += buf.size();
144 }
145 }
146 // Create an inode to dmabuf map. We know inodes are unique..
147 std::unordered_map<ino_t, DmaBuffer> inode_to_dmabuf;
148 for (auto buf : bufs) {
149 inode_to_dmabuf[buf.inode()] = buf;
150 }
151
152 uint64_t total_rss = 0, total_pss = 0;
153 for (auto& [pid, inodes] : pid_to_inodes) {
154 uint64_t pss = 0;
155 uint64_t rss = 0;
156
157 printf("%16s:%-5d\n", GetProcessComm(pid).c_str(), pid);
158 printf("%22s %16s %16s %16s %16s\n", "Name", "Rss", "Pss", "nr_procs", "Inode");
159 for (auto& inode : inodes) {
160 DmaBuffer& buf = inode_to_dmabuf[inode];
161 uint64_t proc_pss = buf.Pss(pid);
162 printf("%22s %13" PRIu64 " kB %13" PRIu64 " kB %16zu %16" PRIuMAX "\n",
163 buf.name().empty() ? "<unknown>" : buf.name().c_str(), buf.size() / 1024,
164 proc_pss / 1024, buf.pids().size(), static_cast<uintmax_t>(buf.inode()));
165 rss += buf.size();
166 pss += proc_pss;
167 }
168 printf("%22s %13" PRIu64 " kB %13" PRIu64 " kB %16s\n", "PROCESS TOTAL", rss / 1024,
169 pss / 1024, "");
170 printf("----------------------\n");
171 total_rss += rss;
172 total_pss += pss;
173 }
174 printf("dmabuf total: %" PRIu64 " kB kernel_rss: %" PRIu64 " kB userspace_rss: %" PRIu64
175 " kB userspace_pss: %" PRIu64 " kB\n ",
176 total_size / 1024, kernel_rss / 1024, total_rss / 1024, total_pss / 1024);
177 }
178
ReadDmaBufs(std::vector<DmaBuffer> * bufs)179 static bool ReadDmaBufs(std::vector<DmaBuffer>* bufs) {
180 bufs->clear();
181
182 if (!ReadDmaBufInfo(bufs)) {
183 printf("debugfs entry for dmabuf not available, using /proc/<pid>/fdinfo instead\n");
184 }
185
186 std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir("/proc"), closedir);
187 if (!dir) {
188 fprintf(stderr, "Failed to open /proc directory\n");
189 bufs->clear();
190 return false;
191 }
192
193 struct dirent* dent;
194 while ((dent = readdir(dir.get()))) {
195 if (dent->d_type != DT_DIR) continue;
196
197 int pid = atoi(dent->d_name);
198 if (pid == 0) {
199 continue;
200 }
201
202 if (!AppendDmaBufInfo(pid, bufs)) {
203 fprintf(stderr, "Unable to read dmabuf info for pid %d\n", pid);
204 bufs->clear();
205 return false;
206 }
207 }
208
209 return true;
210 }
211
main(int argc,char * argv[])212 int main(int argc, char* argv[]) {
213 struct option longopts[] = {{"all", no_argument, nullptr, 'a'},
214 {"help", no_argument, nullptr, 'h'},
215 {0, 0, nullptr, 0}};
216
217 int opt;
218 bool show_table = false;
219 while ((opt = getopt_long(argc, argv, "ah", longopts, nullptr)) != -1) {
220 switch (opt) {
221 case 'a':
222 show_table = true;
223 break;
224 case 'h':
225 usage(EXIT_SUCCESS);
226 default:
227 usage(EXIT_FAILURE);
228 }
229 }
230
231 pid_t pid = -1;
232 if (optind < argc) {
233 if (show_table) {
234 fprintf(stderr, "Invalid arguments: -a does not need arguments\n");
235 usage(EXIT_FAILURE);
236 }
237 if (optind != (argc - 1)) {
238 fprintf(stderr, "Invalid arguments - only one [PID] argument is allowed\n");
239 usage(EXIT_FAILURE);
240 }
241 pid = atoi(argv[optind]);
242 if (pid == 0) {
243 fprintf(stderr, "Invalid process id %s\n", argv[optind]);
244 usage(EXIT_FAILURE);
245 }
246 }
247
248 std::vector<DmaBuffer> bufs;
249 if (pid != -1) {
250 if (!ReadDmaBufInfo(pid, &bufs)) {
251 fprintf(stderr, "Unable to read dmabuf info for %d\n", pid);
252 exit(EXIT_FAILURE);
253 }
254 } else {
255 if (!ReadDmaBufs(&bufs)) exit(EXIT_FAILURE);
256 }
257
258 // Show the old dmabuf table, inode x process
259 if (show_table) {
260 PrintDmaBufTable(bufs);
261 return 0;
262 }
263
264 PrintDmaBufPerProcess(bufs);
265
266 return 0;
267 }
268