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