1 /*
2 * Copyright (C) 2018 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 <error.h>
20 #include <inttypes.h>
21 #include <linux/kernel-page-flags.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <sys/mman.h>
25 #include <sys/types.h>
26 #include <unistd.h>
27
28 #include <algorithm>
29 #include <map>
30 #include <memory>
31 #include <vector>
32
33 #include <android-base/file.h>
34 #include <android-base/parseint.h>
35 #include <android-base/stringprintf.h>
36 #include <android-base/strings.h>
37
38 #include <meminfo/procmeminfo.h>
39
40 using ::android::meminfo::MemUsage;
41 using ::android::meminfo::ProcMemInfo;
42 using ::android::meminfo::Vma;
43
44 // The output format that can be specifid by user.
45 enum Format {
46 RAW = 0,
47 JSON,
48 CSV
49 };
50
usage(int exit_status)51 [[noreturn]] static void usage(int exit_status) {
52 fprintf(stderr,
53 "Usage: %s [ -P | -L ] [ -v | -r | -p | -u | -s | -h ]\n"
54 "\n"
55 "Sort options:\n"
56 " -v Sort processes by VSS.\n"
57 " -r Sort processes by RSS.\n"
58 " -p Sort processes by PSS.\n"
59 " -u Sort processes by USS.\n"
60 " -s Sort processes by swap.\n"
61 " (Default sort order is PSS.)\n"
62 " -a Show all mappings, including stack, heap and anon.\n"
63 " -P /path Limit libraries displayed to those in path.\n"
64 " -R Reverse sort order (default is descending).\n"
65 " -m [r][w][x] Only list pages that exactly match permissions\n"
66 " -c Only show cached (storage backed) pages\n"
67 " -C Only show non-cached (ram/swap backed) pages\n"
68 " -k Only show pages collapsed by KSM\n"
69 " -f [raw][json][csv] Print output in the specified format.\n"
70 " (Default format is raw text.)\n"
71 " -h Display this help screen.\n",
72 getprogname());
73 exit(exit_status);
74 }
75
add_mem_usage(MemUsage * to,const MemUsage & from)76 static void add_mem_usage(MemUsage* to, const MemUsage& from) {
77 to->vss += from.vss;
78 to->rss += from.rss;
79 to->pss += from.pss;
80 to->uss += from.uss;
81
82 to->swap += from.swap;
83
84 to->private_clean += from.private_clean;
85 to->private_dirty += from.private_dirty;
86
87 to->shared_clean += from.shared_clean;
88 to->shared_dirty += from.shared_dirty;
89 }
90
91 struct ProcessRecord {
92 public:
ProcessRecordProcessRecord93 ProcessRecord(pid_t pid) : pid_(-1), cmdline_("") {
94 std::string fname = ::android::base::StringPrintf("/proc/%d/cmdline", pid);
95 std::string cmdline;
96 if (!::android::base::ReadFileToString(fname, &cmdline)) {
97 fprintf(stderr, "Failed to read cmdline from: %s\n", fname.c_str());
98 return;
99 }
100 // We deliberately don't read the proc/<pid>cmdline file directly into 'cmdline_'
101 // because of some processes showing up cmdlines that end with "0x00 0x0A 0x00"
102 // e.g. xtra-daemon, lowi-server
103 // The .c_str() assignment below then takes care of trimming the cmdline at the first
104 // 0x00. This is how original procrank worked (luckily)
105 cmdline_ = cmdline.c_str();
106 pid_ = pid;
107 usage_.clear();
108 }
109
110 ~ProcessRecord() = default;
111
validProcessRecord112 bool valid() const { return pid_ != -1; }
113
114 // Getters
pidProcessRecord115 pid_t pid() const { return pid_; }
cmdlineProcessRecord116 const std::string& cmdline() const { return cmdline_; }
usageProcessRecord117 const MemUsage& usage() const { return usage_; }
118
119 // Add to the usage
AddUsageProcessRecord120 void AddUsage(const MemUsage& mem_usage) { add_mem_usage(&usage_, mem_usage); }
121
122 private:
123 pid_t pid_;
124 std::string cmdline_;
125 MemUsage usage_;
126 };
127
128 struct LibRecord {
129 public:
LibRecordLibRecord130 LibRecord(const std::string& name) : name_(name) {}
131 ~LibRecord() = default;
132
nameLibRecord133 const std::string& name() const { return name_; }
usageLibRecord134 const MemUsage& usage() const { return usage_; }
processesLibRecord135 const std::map<pid_t, ProcessRecord>& processes() const { return procs_; }
pssLibRecord136 uint64_t pss() const { return usage_.pss; }
AddUsageLibRecord137 void AddUsage(const ProcessRecord& proc, const MemUsage& mem_usage) {
138 auto [it, inserted] = procs_.insert(std::pair<pid_t, ProcessRecord>(proc.pid(), proc));
139 it->second.AddUsage(mem_usage);
140 add_mem_usage(&usage_, mem_usage);
141 }
142
143 private:
144 std::string name_;
145 MemUsage usage_;
146 std::map<pid_t, ProcessRecord> procs_;
147 };
148
149 // List of every library / map
150 static std::map<std::string, LibRecord> g_libs;
151
152 // List of library/map names that we don't want to show by default
153 static const std::vector<std::string> g_blacklisted_libs = {"[heap]", "[stack]"};
154
155 // Global flags affected by command line
156 static uint64_t g_pgflags = 0;
157 static uint64_t g_pgflags_mask = 0;
158 static uint16_t g_mapflags_mask = 0;
159 static bool g_all_libs = false;
160 static bool g_has_swap = false;
161 static bool g_reverse_sort = false;
162 static std::string g_prefix_filter = "";
163
read_all_pids(std::function<bool (pid_t pid)> for_each_pid)164 static bool read_all_pids(std::function<bool(pid_t pid)> for_each_pid) {
165 std::unique_ptr<DIR, int (*)(DIR*)> procdir(opendir("/proc"), closedir);
166 if (!procdir) return false;
167
168 struct dirent* dir;
169 pid_t pid;
170 while ((dir = readdir(procdir.get()))) {
171 if (!::android::base::ParseInt(dir->d_name, &pid)) continue;
172 if (!for_each_pid(pid)) return false;
173 }
174
175 return true;
176 }
177
scan_libs_per_process(pid_t pid)178 static bool scan_libs_per_process(pid_t pid) {
179 ProcMemInfo pmem(pid, false, g_pgflags, g_pgflags_mask);
180 const std::vector<Vma> maps = pmem.Maps();
181 if (maps.size() == 0) {
182 // nothing to do here, continue
183 return true;
184 }
185
186 ProcessRecord proc(pid);
187 if (!proc.valid()) {
188 fprintf(stderr, "Failed to create process record for process: %d\n", pid);
189 return false;
190 }
191
192 for (auto& map : maps) {
193 // skip library / map if prefix for the path doesn't match
194 if (!g_prefix_filter.empty() && !::android::base::StartsWith(map.name, g_prefix_filter)) {
195 continue;
196 }
197 // Skip maps based on map permissions
198 if (g_mapflags_mask &&
199 ((map.flags & (PROT_READ | PROT_WRITE | PROT_EXEC)) != g_mapflags_mask)) {
200 continue;
201 }
202
203 // skip blacklisted library / map names
204 if (!g_all_libs && (std::find(g_blacklisted_libs.begin(), g_blacklisted_libs.end(),
205 map.name) != g_blacklisted_libs.end())) {
206 continue;
207 }
208
209 auto [it, inserted] =
210 g_libs.insert(std::pair<std::string, LibRecord>(map.name, LibRecord(map.name)));
211 it->second.AddUsage(proc, map.usage);
212
213 if (!g_has_swap && map.usage.swap) {
214 g_has_swap = true;
215 }
216 }
217
218 return true;
219 }
220
parse_mapflags(const char * mapflags)221 static uint16_t parse_mapflags(const char* mapflags) {
222 uint16_t ret = 0;
223 for (const char* p = mapflags; *p; p++) {
224 switch (*p) {
225 case 'r':
226 ret |= PROT_READ;
227 break;
228 case 'w':
229 ret |= PROT_WRITE;
230 break;
231 case 'x':
232 ret |= PROT_EXEC;
233 break;
234 default:
235 error(EXIT_FAILURE, 0, "Invalid permissions string: %s, %s", mapflags, p);
236 }
237 }
238
239 return ret;
240 }
241
escape_csv_string(const std::string & raw)242 static std::string escape_csv_string(const std::string& raw) {
243 std::string ret;
244 for (auto it = raw.cbegin(); it != raw.cend(); it++) {
245 switch (*it) {
246 case '"':
247 ret += "\"\"";
248 break;
249 default:
250 ret += *it;
251 break;
252 }
253 }
254 return '"' + ret + '"';
255 }
256
to_csv(LibRecord & l,ProcessRecord & p)257 std::string to_csv(LibRecord& l, ProcessRecord& p) {
258 const MemUsage& usage = p.usage();
259 return escape_csv_string(l.name())
260 + "," + std::to_string(l.pss() / 1024)
261 + "," + escape_csv_string(p.cmdline())
262 + ",\"[" + std::to_string(p.pid()) + "]\""
263 + "," + std::to_string(usage.vss/1024)
264 + "," + std::to_string(usage.rss/1024)
265 + "," + std::to_string(usage.pss/1024)
266 + "," + std::to_string(usage.uss/1024)
267 + (g_has_swap ? "," + std::to_string(usage.swap/1024) : "");
268 }
269
escape_json_string(const std::string & raw)270 static std::string escape_json_string(const std::string& raw) {
271 std::string ret;
272 for (auto it = raw.cbegin(); it != raw.cend(); it++) {
273 switch (*it) {
274 case '\\':
275 ret += "\\\\";
276 break;
277 case '"':
278 ret += "\\\"";
279 break;
280 case '/':
281 ret += "\\/";
282 break;
283 case '\b':
284 ret += "\\b";
285 break;
286 case '\f':
287 ret += "\\f";
288 break;
289 case '\n':
290 ret += "\\n";
291 break;
292 case '\r':
293 ret += "\\r";
294 break;
295 case '\t':
296 ret += "\\t";
297 break;
298 default:
299 ret += *it;
300 break;
301 }
302 }
303 return '"' + ret + '"';
304 }
305
to_json(LibRecord & l,ProcessRecord & p)306 std::string to_json(LibRecord& l, ProcessRecord& p) {
307 const MemUsage& usage = p.usage();
308 return "{\"Library\":" + escape_json_string(l.name())
309 + ",\"Total_RSS\":" + std::to_string(l.pss() / 1024)
310 + ",\"Process\":" + escape_json_string(p.cmdline())
311 + ",\"PID\":\"" + std::to_string(p.pid()) + "\""
312 + ",\"VSS\":" + std::to_string(usage.vss/1024)
313 + ",\"RSS\":" + std::to_string(usage.rss/1024)
314 + ",\"PSS\":" + std::to_string(usage.pss/1024)
315 + ",\"USS\":" + std::to_string(usage.uss/1024)
316 + (g_has_swap ? ",\"Swap\":" + std::to_string(usage.swap/1024) : "")
317 + "}";
318 }
319
get_format(std::string arg)320 static Format get_format(std::string arg) {
321 if (arg.compare("json") == 0) {
322 return JSON;
323 }
324 if (arg.compare("csv") == 0) {
325 return CSV;
326 }
327 if (arg.compare("raw") == 0) {
328 return RAW;
329 }
330 error(EXIT_FAILURE, 0, "Invalid format.");
331 return RAW;
332 }
333
main(int argc,char * argv[])334 int main(int argc, char* argv[]) {
335 int opt;
336
337 auto pss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
338 return g_reverse_sort ? a.usage().pss < b.usage().pss : a.usage().pss > b.usage().pss;
339 };
340
341 auto uss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
342 return g_reverse_sort ? a.usage().uss < b.usage().uss : a.usage().uss > b.usage().uss;
343 };
344
345 auto vss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
346 return g_reverse_sort ? a.usage().vss < b.usage().vss : a.usage().vss > b.usage().vss;
347 };
348
349 auto rss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
350 return g_reverse_sort ? a.usage().rss < b.usage().rss : a.usage().rss > b.usage().rss;
351 };
352
353 auto swap_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
354 return g_reverse_sort ? a.usage().swap < b.usage().swap : a.usage().swap > b.usage().swap;
355 };
356
357 std::function<bool(const ProcessRecord&, const ProcessRecord&)> sort_func = pss_sort;
358
359 Format format = RAW;
360 while ((opt = getopt(argc, argv, "acCf:hkm:pP:uvrsR")) != -1) {
361 switch (opt) {
362 case 'a':
363 g_all_libs = true;
364 break;
365 case 'c':
366 g_pgflags = 0;
367 g_pgflags_mask = (1 << KPF_SWAPBACKED);
368 break;
369 case 'C':
370 g_pgflags = g_pgflags_mask = (1 << KPF_SWAPBACKED);
371 break;
372 case 'f':
373 format = get_format(optarg);
374 break;
375 case 'h':
376 usage(EXIT_SUCCESS);
377 case 'k':
378 g_pgflags = g_pgflags_mask = (1 << KPF_KSM);
379 break;
380 case 'm':
381 g_mapflags_mask = parse_mapflags(optarg);
382 break;
383 case 'p':
384 sort_func = pss_sort;
385 break;
386 case 'P':
387 g_prefix_filter = optarg;
388 break;
389 case 'u':
390 sort_func = uss_sort;
391 break;
392 case 'v':
393 sort_func = vss_sort;
394 break;
395 case 'r':
396 sort_func = rss_sort;
397 break;
398 case 's':
399 sort_func = swap_sort;
400 break;
401 case 'R':
402 g_reverse_sort = true;
403 break;
404 default:
405 usage(EXIT_FAILURE);
406 }
407 }
408
409 if (!read_all_pids(scan_libs_per_process)) {
410 error(EXIT_FAILURE, 0, "Failed to read all pids from the system");
411 }
412
413 switch (format) {
414 case RAW:
415 printf(" %6s %7s %6s %6s %6s ", "RSStot", "VSS", "RSS", "PSS", "USS");
416 if (g_has_swap) {
417 printf(" %6s ", "Swap");
418 }
419 printf("Name/PID\n");
420 break;
421 case CSV:
422 printf("\"Library\",\"Total_RSS\",\"Process\",\"PID\",\"VSS\",\"RSS\",\"PSS\",\"USS\"");
423 if (g_has_swap) {
424 printf(", \"Swap\"");
425 }
426 printf("\n");
427 break;
428 case JSON:
429 break;
430 }
431
432 std::vector<LibRecord> v_libs;
433 v_libs.reserve(g_libs.size());
434 std::transform(g_libs.begin(), g_libs.end(), std::back_inserter(v_libs),
435 [] (std::pair<std::string, LibRecord> const& pair) { return pair.second; });
436
437 // sort the libraries by their pss
438 std::sort(v_libs.begin(), v_libs.end(),
439 [](const LibRecord& l1, const LibRecord& l2) { return l1.pss() > l2.pss(); });
440
441 for (auto& lib : v_libs) {
442 if (format == RAW) {
443 printf("%6" PRIu64 "K %7s %6s %6s %6s ", lib.pss() / 1024, "", "", "", "");
444 if (g_has_swap) {
445 printf(" %6s ", "");
446 }
447 printf("%s\n", lib.name().c_str());
448 }
449
450 // sort all mappings first
451
452 std::vector<ProcessRecord> procs;
453 procs.reserve(lib.processes().size());
454 std::transform(lib.processes().begin(), lib.processes().end(), std::back_inserter(procs),
455 [] (std::pair<pid_t, ProcessRecord> const& pair) { return pair.second; });
456
457 std::sort(procs.begin(), procs.end(), sort_func);
458
459 for (auto& p : procs) {
460 const MemUsage& usage = p.usage();
461 switch (format) {
462 case RAW:
463 printf(" %6s %7" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ", "",
464 usage.vss / 1024, usage.rss / 1024, usage.pss / 1024, usage.uss / 1024);
465 if (g_has_swap) {
466 printf("%6" PRIu64 "K ", usage.swap / 1024);
467 }
468 printf(" %s [%d]\n", p.cmdline().c_str(), p.pid());
469 break;
470 case JSON:
471 printf("%s\n", to_json(lib, p).c_str());
472 break;
473 case CSV:
474 printf("%s\n", to_csv(lib, p).c_str());
475 break;
476 }
477 }
478 }
479
480 return 0;
481 }
482