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