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