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