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