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 
118             it->vma.usage.anon_huge_pages += current.vma.usage.anon_huge_pages;
119             it->vma.usage.shmem_pmd_mapped += current.vma.usage.shmem_pmd_mapped;
120             it->vma.usage.file_pmd_mapped += current.vma.usage.file_pmd_mapped;
121             it->vma.usage.shared_hugetlb += current.vma.usage.shared_hugetlb;
122             it->vma.usage.private_hugetlb += current.vma.usage.private_hugetlb;
123 
124             it->is_bss &= current.is_bss;
125             it->count++;
126 
127             break;
128         }
129 
130         if (insert_before(current, *it)) {
131             g_vmas.insert(it, current);
132             break;
133         }
134     }
135 
136     if (it == g_vmas.end()) {
137         g_vmas.emplace_back(current);
138     }
139 }
140 
print_header()141 static void print_header() {
142     const char* addr1 = g_show_addr ? "           start              end " : "";
143     const char* addr2 = g_show_addr ? "            addr             addr " : "";
144 
145     printf("%s virtual                     shared   shared  private  private                   "
146            "Anon      Shmem     File       Shared   Private\n",
147            addr1);
148     printf("%s    size      RSS      PSS    clean    dirty    clean    dirty     swap  swapPSS "
149            "HugePages PmdMapped PmdMapped  Hugetlb  Hugetlb",
150            addr2);
151     if (!g_verbose && !g_show_addr) {
152         printf("   # ");
153     }
154     if (g_verbose) {
155         printf(" flags ");
156     }
157     printf(" object\n");
158 }
159 
print_divider()160 static void print_divider() {
161     if (g_show_addr) {
162         printf("-------- -------- ");
163     }
164     printf("-------- -------- -------- -------- -------- -------- -------- -------- -------- "
165            "--------- --------- --------- -------- -------- ");
166     if (!g_verbose && !g_show_addr) {
167         printf("---- ");
168     }
169     if (g_verbose) {
170         printf("------ ");
171     }
172     printf("------------------------------\n");
173 }
174 
print_vmainfo(const VmaInfo & v,bool total)175 static void print_vmainfo(const VmaInfo& v, bool total) {
176     if (g_show_addr) {
177         if (total) {
178             printf("                                  ");
179         } else {
180             printf("%16" PRIx64 " %16" PRIx64 " ", v.vma.start, v.vma.end);
181         }
182     }
183     printf("%8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64
184            " %8" PRIu64 " %8" PRIu64 " %9" PRIu64 " %9" PRIu64 " %9" PRIu64 " %8" PRIu64
185            " %8" PRIu64 " ",
186            v.vma.usage.vss, v.vma.usage.rss, v.vma.usage.pss, v.vma.usage.shared_clean,
187            v.vma.usage.shared_dirty, v.vma.usage.private_clean, v.vma.usage.private_dirty,
188            v.vma.usage.swap, v.vma.usage.swap_pss, v.vma.usage.anon_huge_pages,
189            v.vma.usage.shmem_pmd_mapped, v.vma.usage.file_pmd_mapped, v.vma.usage.shared_hugetlb,
190            v.vma.usage.private_hugetlb);
191     if (!g_verbose && !g_show_addr) {
192         printf("%4" PRIu32 " ", v.count);
193     }
194     if (g_verbose) {
195         if (total) {
196             printf("       ");
197         } else {
198             std::string flags_str("---");
199             if (v.vma.flags & PROT_READ) flags_str[0] = 'r';
200             if (v.vma.flags & PROT_WRITE) flags_str[1] = 'w';
201             if (v.vma.flags & PROT_EXEC) flags_str[2] = 'x';
202 
203             printf("%6s ", flags_str.c_str());
204         }
205     }
206 }
207 
showmap(void)208 static int showmap(void) {
209     if (!::android::meminfo::ForEachVmaFromFile(g_filename, collect_vma)) {
210         if (!g_quiet) {
211             fprintf(stderr, "Failed to parse file %s\n", g_filename.c_str());
212         }
213         return 1;
214     }
215 
216     print_header();
217     print_divider();
218 
219     for (const auto& v : g_vmas) {
220         g_total.vma.usage.vss += v.vma.usage.vss;
221         g_total.vma.usage.rss += v.vma.usage.rss;
222         g_total.vma.usage.pss += v.vma.usage.pss;
223 
224         g_total.vma.usage.private_clean += v.vma.usage.private_clean;
225         g_total.vma.usage.private_dirty += v.vma.usage.private_dirty;
226         g_total.vma.usage.shared_clean += v.vma.usage.shared_clean;
227         g_total.vma.usage.shared_dirty += v.vma.usage.shared_dirty;
228 
229         g_total.vma.usage.swap += v.vma.usage.swap;
230         g_total.vma.usage.swap_pss += v.vma.usage.swap_pss;
231         g_total.count += v.count;
232 
233         if (g_terse && !(v.vma.usage.private_dirty || v.vma.usage.private_clean)) {
234             continue;
235         }
236 
237         print_vmainfo(v, false);
238         printf("%s%s\n", v.vma.name.c_str(), v.is_bss ? " [bss]" : "");
239     }
240 
241     print_divider();
242     print_header();
243     print_divider();
244 
245     print_vmainfo(g_total, true);
246     printf("TOTAL\n");
247 
248     return 0;
249 }
250 
main(int argc,char * argv[])251 int main(int argc, char* argv[]) {
252     signal(SIGPIPE, SIG_IGN);
253     struct option longopts[] = {
254             {"help", no_argument, nullptr, 'h'},
255             {0, 0, nullptr, 0},
256     };
257 
258     int opt;
259     while ((opt = getopt_long(argc, argv, "tvaqf:h", longopts, nullptr)) != -1) {
260         switch (opt) {
261             case 't':
262                 g_terse = true;
263                 break;
264             case 'a':
265                 g_show_addr = true;
266                 break;
267             case 'v':
268                 g_verbose = true;
269                 break;
270             case 'q':
271                 g_quiet = true;
272                 break;
273             case 'f':
274                 g_filename = optarg;
275                 break;
276             case 'h':
277                 usage(argv[0], EXIT_SUCCESS);
278             default:
279                 usage(argv[0], EXIT_FAILURE);
280         }
281     }
282 
283     if (g_filename.empty()) {
284         if ((argc - 1) < optind) {
285             fprintf(stderr, "Invalid arguments: Must provide <pid> at the end\n");
286             usage(argv[0], EXIT_FAILURE);
287         }
288 
289         g_pid = atoi(argv[optind]);
290         if (g_pid <= 0) {
291             fprintf(stderr, "Invalid process id %s\n", argv[optind]);
292             usage(argv[0], EXIT_FAILURE);
293         }
294 
295         g_filename = ::android::base::StringPrintf("/proc/%d/smaps", g_pid);
296     }
297 
298     g_merge_by_names = !g_verbose && !g_show_addr;
299     return showmap();
300 }
301