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 <getopt.h>
18 #include <inttypes.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/mman.h>
22 #include <sys/signal.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25 
26 #include <memory>
27 #include <string>
28 #include <vector>
29 
30 #include <android-base/stringprintf.h>
31 #include <android-base/strings.h>
32 #include <meminfo/procmeminfo.h>
33 
34 using ::android::meminfo::Vma;
35 
36 struct VmaInfo {
37     Vma vma;
38     bool is_bss;
39     uint32_t count;
40 
41     VmaInfo() = default;
VmaInfoVmaInfo42     VmaInfo(const Vma& v) : vma(v), is_bss(false), count(1) {}
VmaInfoVmaInfo43     VmaInfo(const Vma& v, bool bss) : vma(v), is_bss(bss), count(1) {}
VmaInfoVmaInfo44     VmaInfo(const Vma& v, const std::string& name, bool bss) : vma(v), is_bss(bss), count(1) {
45         vma.name = name;
46     }
47 };
48 
49 // Global options
50 static std::string g_filename = "";
51 static bool g_merge_by_names = false;
52 static bool g_terse = false;
53 static bool g_verbose = false;
54 static bool g_show_addr = false;
55 static bool g_quiet = false;
56 static pid_t g_pid = -1;
57 
58 static VmaInfo g_total;
59 static std::vector<VmaInfo> g_vmas;
60 
usage(const char * progname,int exit_status)61 [[noreturn]] static void usage(const char* progname, int exit_status) {
62     fprintf(stderr,
63             "%s [-aqtv] [-f FILE] PID\n"
64             "-a\taddresses (show virtual memory map)\n"
65             "-q\tquiet (don't show error if map could not be read)\n"
66             "-t\tterse (show only items with private pages)\n"
67             "-v\tverbose (don't coalesce maps with the same name)\n"
68             "-f\tFILE (read from input from FILE instead of PID)\n",
69             progname);
70 
71     exit(exit_status);
72 }
73 
is_library(const std::string & name)74 static bool is_library(const std::string& name) {
75     return (name.size() > 4) && (name[0] == '/') && ::android::base::EndsWith(name, ".so");
76 }
77 
insert_before(const VmaInfo & a,const VmaInfo & b)78 static bool insert_before(const VmaInfo& a, const VmaInfo& b) {
79     if (g_show_addr) {
80         return (a.vma.start < b.vma.start || (a.vma.start == b.vma.start && a.vma.end < b.vma.end));
81     }
82 
83     return strcmp(a.vma.name.c_str(), b.vma.name.c_str()) < 0;
84 }
85 
collect_vma(const Vma & vma)86 static void collect_vma(const Vma& vma) {
87     if (g_vmas.empty()) {
88         g_vmas.emplace_back(vma);
89         return;
90     }
91 
92     VmaInfo current(vma);
93     VmaInfo& last = g_vmas.back();
94     // determine if this is bss;
95     if (vma.name.empty()) {
96         if (last.vma.end == current.vma.start && is_library(last.vma.name)) {
97             current.vma.name = last.vma.name;
98             current.is_bss = true;
99         } else {
100             current.vma.name = "[anon]";
101         }
102     }
103 
104     std::vector<VmaInfo>::iterator it;
105     for (it = g_vmas.begin(); it != g_vmas.end(); it++) {
106         if (g_merge_by_names && (it->vma.name == current.vma.name)) {
107             it->vma.usage.vss += current.vma.usage.vss;
108             it->vma.usage.rss += current.vma.usage.rss;
109             it->vma.usage.pss += current.vma.usage.pss;
110 
111             it->vma.usage.shared_clean += current.vma.usage.shared_clean;
112             it->vma.usage.shared_dirty += current.vma.usage.shared_dirty;
113             it->vma.usage.private_clean += current.vma.usage.private_clean;
114             it->vma.usage.private_dirty += current.vma.usage.private_dirty;
115             it->vma.usage.swap += current.vma.usage.swap;
116             it->vma.usage.swap_pss += current.vma.usage.swap_pss;
117             it->is_bss &= current.is_bss;
118             it->count++;
119             break;
120         }
121 
122         if (insert_before(current, *it)) {
123             g_vmas.insert(it, current);
124             break;
125         }
126     }
127 
128     if (it == g_vmas.end()) {
129         g_vmas.emplace_back(current);
130     }
131 }
132 
print_header()133 static void print_header() {
134     const char* addr1 = g_show_addr ? "           start              end " : "";
135     const char* addr2 = g_show_addr ? "            addr             addr " : "";
136 
137     printf("%s virtual                     shared   shared  private  private\n", addr1);
138     printf("%s    size      RSS      PSS    clean    dirty    clean    dirty     swap  swapPSS",
139            addr2);
140     if (!g_verbose && !g_show_addr) {
141         printf("   # ");
142     }
143     if (g_verbose) {
144         printf(" flags ");
145     }
146     printf(" object\n");
147 }
148 
print_divider()149 static void print_divider() {
150     if (g_show_addr) {
151         printf("-------- -------- ");
152     }
153     printf("-------- -------- -------- -------- -------- -------- -------- -------- -------- ");
154     if (!g_verbose && !g_show_addr) {
155         printf("---- ");
156     }
157     if (g_verbose) {
158         printf("------ ");
159     }
160     printf("------------------------------\n");
161 }
162 
print_vmainfo(const VmaInfo & v,bool total)163 static void print_vmainfo(const VmaInfo& v, bool total) {
164     if (g_show_addr) {
165         if (total) {
166             printf("                                  ");
167         } else {
168             printf("%16" PRIx64 " %16" PRIx64 " ", v.vma.start, v.vma.end);
169         }
170     }
171     printf("%8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64
172            " %8" PRIu64 " %8" PRIu64 " ",
173            v.vma.usage.vss, v.vma.usage.rss, v.vma.usage.pss, v.vma.usage.shared_clean,
174            v.vma.usage.shared_dirty, v.vma.usage.private_clean, v.vma.usage.private_dirty,
175            v.vma.usage.swap, v.vma.usage.swap_pss);
176     if (!g_verbose && !g_show_addr) {
177         printf("%4" PRIu32 " ", v.count);
178     }
179     if (g_verbose) {
180         if (total) {
181             printf("       ");
182         } else {
183             std::string flags_str("---");
184             if (v.vma.flags & PROT_READ) flags_str[0] = 'r';
185             if (v.vma.flags & PROT_WRITE) flags_str[1] = 'w';
186             if (v.vma.flags & PROT_EXEC) flags_str[2] = 'x';
187 
188             printf("%6s ", flags_str.c_str());
189         }
190     }
191 }
192 
showmap(void)193 static int showmap(void) {
194     if (!::android::meminfo::ForEachVmaFromFile(g_filename, collect_vma)) {
195         if (!g_quiet) {
196             fprintf(stderr, "Failed to parse file %s\n", g_filename.c_str());
197         }
198         return 1;
199     }
200 
201     print_header();
202     print_divider();
203 
204     for (const auto& v : g_vmas) {
205         g_total.vma.usage.vss += v.vma.usage.vss;
206         g_total.vma.usage.rss += v.vma.usage.rss;
207         g_total.vma.usage.pss += v.vma.usage.pss;
208 
209         g_total.vma.usage.private_clean += v.vma.usage.private_clean;
210         g_total.vma.usage.private_dirty += v.vma.usage.private_dirty;
211         g_total.vma.usage.shared_clean += v.vma.usage.shared_clean;
212         g_total.vma.usage.shared_dirty += v.vma.usage.shared_dirty;
213 
214         g_total.vma.usage.swap += v.vma.usage.swap;
215         g_total.vma.usage.swap_pss += v.vma.usage.swap_pss;
216         g_total.count += v.count;
217 
218         if (g_terse && !(v.vma.usage.private_dirty || v.vma.usage.private_clean)) {
219             continue;
220         }
221 
222         print_vmainfo(v, false);
223         printf("%s%s\n", v.vma.name.c_str(), v.is_bss ? " [bss]" : "");
224     }
225 
226     print_divider();
227     print_header();
228     print_divider();
229 
230     print_vmainfo(g_total, true);
231     printf("TOTAL\n");
232 
233     return 0;
234 }
235 
main(int argc,char * argv[])236 int main(int argc, char* argv[]) {
237     signal(SIGPIPE, SIG_IGN);
238     struct option longopts[] = {
239             {"help", no_argument, nullptr, 'h'},
240             {0, 0, nullptr, 0},
241     };
242 
243     int opt;
244     while ((opt = getopt_long(argc, argv, "tvaqf:h", longopts, nullptr)) != -1) {
245         switch (opt) {
246             case 't':
247                 g_terse = true;
248                 break;
249             case 'a':
250                 g_show_addr = true;
251                 break;
252             case 'v':
253                 g_verbose = true;
254                 break;
255             case 'q':
256                 g_quiet = true;
257                 break;
258             case 'f':
259                 g_filename = optarg;
260                 break;
261             case 'h':
262                 usage(argv[0], EXIT_SUCCESS);
263             default:
264                 usage(argv[0], EXIT_FAILURE);
265         }
266     }
267 
268     if (g_filename.empty()) {
269         if ((argc - 1) < optind) {
270             fprintf(stderr, "Invalid arguments: Must provide <pid> at the end\n");
271             usage(argv[0], EXIT_FAILURE);
272         }
273 
274         g_pid = atoi(argv[optind]);
275         if (g_pid <= 0) {
276             fprintf(stderr, "Invalid process id %s\n", argv[optind]);
277             usage(argv[0], EXIT_FAILURE);
278         }
279 
280         g_filename = ::android::base::StringPrintf("/proc/%d/smaps", g_pid);
281     }
282 
283     g_merge_by_names = !g_verbose && !g_show_addr;
284     return showmap();
285 }
286