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