• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
usage(int exit_status)44 [[noreturn]] static void usage(int exit_status) {
45     fprintf(stderr,
46             "Usage: %s [ -P | -L ] [ -v | -r | -p | -u | -s | -h ]\n"
47             "\n"
48             "Sort options:\n"
49             "    -v  Sort processes by VSS.\n"
50             "    -r  Sort processes by RSS.\n"
51             "    -p  Sort processes by PSS.\n"
52             "    -u  Sort processes by USS.\n"
53             "    -s  Sort processes by swap.\n"
54             "        (Default sort order is PSS.)\n"
55             "    -a  Show all mappings, including stack, heap and anon.\n"
56             "    -P /path  Limit libraries displayed to those in path.\n"
57             "    -R  Reverse sort order (default is descending).\n"
58             "    -m [r][w][x] Only list pages that exactly match permissions\n"
59             "    -c  Only show cached (storage backed) pages\n"
60             "    -C  Only show non-cached (ram/swap backed) pages\n"
61             "    -k  Only show pages collapsed by KSM\n"
62             "    -h  Display this help screen.\n",
63             getprogname());
64     exit(exit_status);
65 }
66 
add_mem_usage(MemUsage * to,const MemUsage & from)67 static void add_mem_usage(MemUsage* to, const MemUsage& from) {
68     to->vss += from.vss;
69     to->rss += from.rss;
70     to->pss += from.pss;
71     to->uss += from.uss;
72 
73     to->swap += from.swap;
74 
75     to->private_clean += from.private_clean;
76     to->private_dirty += from.private_dirty;
77 
78     to->shared_clean += from.shared_clean;
79     to->shared_dirty += from.shared_dirty;
80 }
81 
82 struct ProcessRecord {
83   public:
ProcessRecordProcessRecord84     ProcessRecord(pid_t pid) : pid_(-1), cmdline_("") {
85         std::string fname = ::android::base::StringPrintf("/proc/%d/cmdline", pid);
86         std::string cmdline;
87         if (!::android::base::ReadFileToString(fname, &cmdline)) {
88             fprintf(stderr, "Failed to read cmdline from: %s\n", fname.c_str());
89             return;
90         }
91         // We deliberately don't read the proc/<pid>cmdline file directly into 'cmdline_'
92         // because of some processes showing up cmdlines that end with "0x00 0x0A 0x00"
93         // e.g. xtra-daemon, lowi-server
94         // The .c_str() assignment below then takes care of trimming the cmdline at the first
95         // 0x00. This is how original procrank worked (luckily)
96         cmdline_ = cmdline.c_str();
97         pid_ = pid;
98         usage_.clear();
99     }
100 
101     ~ProcessRecord() = default;
102 
validProcessRecord103     bool valid() const { return pid_ != -1; }
104 
105     // Getters
pidProcessRecord106     pid_t pid() const { return pid_; }
cmdlineProcessRecord107     const std::string& cmdline() const { return cmdline_; }
usageProcessRecord108     const MemUsage& usage() const { return usage_; }
109 
110     // Add to the usage
AddUsageProcessRecord111     void AddUsage(const MemUsage& mem_usage) { add_mem_usage(&usage_, mem_usage); }
112 
113   private:
114     pid_t pid_;
115     std::string cmdline_;
116     MemUsage usage_;
117 };
118 
119 struct LibRecord {
120   public:
LibRecordLibRecord121     LibRecord(const std::string& name) : name_(name) {}
122     ~LibRecord() = default;
123 
nameLibRecord124     const std::string& name() const { return name_; }
usageLibRecord125     const MemUsage& usage() const { return usage_; }
processesLibRecord126     const std::map<pid_t, ProcessRecord>& processes() const { return procs_; }
pssLibRecord127     uint64_t pss() const { return usage_.pss; }
AddUsageLibRecord128     void AddUsage(const ProcessRecord& proc, const MemUsage& mem_usage) {
129         auto [it, inserted] = procs_.insert(std::pair<pid_t, ProcessRecord>(proc.pid(), proc));
130         it->second.AddUsage(mem_usage);
131         add_mem_usage(&usage_, mem_usage);
132     }
133 
134   private:
135     std::string name_;
136     MemUsage usage_;
137     std::map<pid_t, ProcessRecord> procs_;
138 };
139 
140 // List of every library / map
141 static std::map<std::string, LibRecord> g_libs;
142 
143 // List of library/map names that we don't want to show by default
144 static const std::vector<std::string> g_blacklisted_libs = {"[heap]", "[stack]"};
145 
146 // Global flags affected by command line
147 static uint64_t g_pgflags = 0;
148 static uint64_t g_pgflags_mask = 0;
149 static uint16_t g_mapflags_mask = 0;
150 static bool g_all_libs = false;
151 static bool g_has_swap = false;
152 static bool g_reverse_sort = false;
153 static std::string g_prefix_filter = "";
154 
read_all_pids(std::function<bool (pid_t pid)> for_each_pid)155 static bool read_all_pids(std::function<bool(pid_t pid)> for_each_pid) {
156     std::unique_ptr<DIR, int (*)(DIR*)> procdir(opendir("/proc"), closedir);
157     if (!procdir) return false;
158 
159     struct dirent* dir;
160     pid_t pid;
161     while ((dir = readdir(procdir.get()))) {
162         if (!::android::base::ParseInt(dir->d_name, &pid)) continue;
163         if (!for_each_pid(pid)) return false;
164     }
165 
166     return true;
167 }
168 
scan_libs_per_process(pid_t pid)169 static bool scan_libs_per_process(pid_t pid) {
170     ProcMemInfo pmem(pid, false, g_pgflags, g_pgflags_mask);
171     const std::vector<Vma> maps = pmem.Maps();
172     if (maps.size() == 0) {
173         // nothing to do here, continue
174         return true;
175     }
176 
177     ProcessRecord proc(pid);
178     if (!proc.valid()) {
179         fprintf(stderr, "Failed to create process record for process: %d\n", pid);
180         return false;
181     }
182 
183     for (auto& map : maps) {
184         // skip library / map if prefix for the path doesn't match
185         if (!g_prefix_filter.empty() && !::android::base::StartsWith(map.name, g_prefix_filter)) {
186             continue;
187         }
188         // Skip maps based on map permissions
189         if (g_mapflags_mask &&
190             ((map.flags & (PROT_READ | PROT_WRITE | PROT_EXEC)) != g_mapflags_mask)) {
191             continue;
192         }
193 
194         // skip blacklisted library / map names
195         if (!g_all_libs && (std::find(g_blacklisted_libs.begin(), g_blacklisted_libs.end(),
196                                       map.name) != g_blacklisted_libs.end())) {
197             continue;
198         }
199 
200         auto [it, inserted] =
201             g_libs.insert(std::pair<std::string, LibRecord>(map.name, LibRecord(map.name)));
202         it->second.AddUsage(proc, map.usage);
203 
204         if (!g_has_swap && map.usage.swap) {
205             g_has_swap = true;
206         }
207     }
208 
209     return true;
210 }
211 
parse_mapflags(const char * mapflags)212 static uint16_t parse_mapflags(const char* mapflags) {
213     uint16_t ret = 0;
214     for (const char* p = mapflags; *p; p++) {
215         switch (*p) {
216             case 'r':
217                 ret |= PROT_READ;
218                 break;
219             case 'w':
220                 ret |= PROT_WRITE;
221                 break;
222             case 'x':
223                 ret |= PROT_EXEC;
224                 break;
225             default:
226                 error(EXIT_FAILURE, 0, "Invalid permissions string: %s, %s", mapflags, p);
227         }
228     }
229 
230     return ret;
231 }
232 
main(int argc,char * argv[])233 int main(int argc, char* argv[]) {
234     int opt;
235 
236     auto pss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
237         return g_reverse_sort ? a.usage().pss < b.usage().pss : a.usage().pss > b.usage().pss;
238     };
239 
240     auto uss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
241         return g_reverse_sort ? a.usage().uss < b.usage().uss : a.usage().uss > b.usage().uss;
242     };
243 
244     auto vss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
245         return g_reverse_sort ? a.usage().vss < b.usage().vss : a.usage().vss > b.usage().vss;
246     };
247 
248     auto rss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
249         return g_reverse_sort ? a.usage().rss < b.usage().rss : a.usage().rss > b.usage().rss;
250     };
251 
252     auto swap_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
253         return g_reverse_sort ? a.usage().swap < b.usage().swap : a.usage().swap > b.usage().swap;
254     };
255 
256     std::function<bool(const ProcessRecord&, const ProcessRecord&)> sort_func = pss_sort;
257 
258     while ((opt = getopt(argc, argv, "acChkm:pP:uvrsR")) != -1) {
259         switch (opt) {
260             case 'a':
261                 g_all_libs = true;
262                 break;
263             case 'c':
264                 g_pgflags = 0;
265                 g_pgflags_mask = (1 << KPF_SWAPBACKED);
266                 break;
267             case 'C':
268                 g_pgflags = g_pgflags_mask = (1 << KPF_SWAPBACKED);
269                 break;
270             case 'h':
271                 usage(EXIT_SUCCESS);
272             case 'k':
273                 g_pgflags = g_pgflags_mask = (1 << KPF_KSM);
274                 break;
275             case 'm':
276                 g_mapflags_mask = parse_mapflags(optarg);
277                 break;
278             case 'p':
279                 sort_func = pss_sort;
280                 break;
281             case 'P':
282                 g_prefix_filter = optarg;
283                 break;
284             case 'u':
285                 sort_func = uss_sort;
286                 break;
287             case 'v':
288                 sort_func = vss_sort;
289                 break;
290             case 'r':
291                 sort_func = rss_sort;
292                 break;
293             case 's':
294                 sort_func = swap_sort;
295                 break;
296             case 'R':
297                 g_reverse_sort = true;
298                 break;
299             default:
300                 usage(EXIT_FAILURE);
301         }
302     }
303 
304     if (!read_all_pids(scan_libs_per_process)) {
305         error(EXIT_FAILURE, 0, "Failed to read all pids from the system");
306     }
307 
308     printf(" %6s   %7s   %6s   %6s   %6s  ", "RSStot", "VSS", "RSS", "PSS", "USS");
309     if (g_has_swap) {
310         printf(" %6s  ", "Swap");
311     }
312     printf("Name/PID\n");
313 
314     std::vector<LibRecord> v_libs;
315     v_libs.reserve(g_libs.size());
316     std::transform(g_libs.begin(), g_libs.end(), std::back_inserter(v_libs),
317         [] (std::pair<std::string, LibRecord> const& pair) { return pair.second; });
318 
319     // sort the libraries by their pss
320     std::sort(v_libs.begin(), v_libs.end(),
321               [](const LibRecord& l1, const LibRecord& l2) { return l1.pss() > l2.pss(); });
322 
323     for (auto& lib : v_libs) {
324         printf("%6" PRIu64 "K   %7s   %6s   %6s   %6s  ", lib.pss() / 1024, "", "", "", "");
325         if (g_has_swap) {
326             printf(" %6s  ", "");
327         }
328         printf("%s\n", lib.name().c_str());
329 
330         // sort all mappings first
331 
332         std::vector<ProcessRecord> procs;
333         procs.reserve(lib.processes().size());
334         std::transform(lib.processes().begin(), lib.processes().end(), std::back_inserter(procs),
335             [] (std::pair<pid_t, ProcessRecord> const& pair) { return pair.second; });
336 
337         std::sort(procs.begin(), procs.end(), sort_func);
338 
339         for (auto& p : procs) {
340             const MemUsage& usage = p.usage();
341             printf(" %6s  %7" PRIu64 "K  %6" PRIu64 "K  %6" PRIu64 "K  %6" PRIu64 "K  ", "",
342                    usage.vss / 1024, usage.rss / 1024, usage.pss / 1024, usage.uss / 1024);
343             if (g_has_swap) {
344                 printf("%6" PRIu64 "K  ", usage.swap / 1024);
345             }
346             printf("  %s [%d]\n", p.cmdline().c_str(), p.pid());
347         }
348     }
349 
350     return 0;
351 }
352