1 //
2 // Copyright (C) 2008 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 <fcntl.h>
20 #include <inttypes.h>
21 #include <stdbool.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/types.h>
25 #include <unistd.h>
26
27 #include <vector>
28
29 #include <android-base/file.h>
30 #include <android-base/stringprintf.h>
31 #include <android-base/strings.h>
32 #include <pagemap/pagemap.h>
33
34 struct proc_info {
35 pid_t pid;
36 pm_memusage_t usage;
37 uint64_t wss;
38 int oomadj;
39 };
40
41 static void usage(char *myname);
42 static std::string getprocname(pid_t pid);
43 static int getoomadj(pid_t pid);
44 static bool getminfree(std::vector<uint64_t>* minfree, std::vector<int>* adj);
45 static int numcmp(uint64_t a, uint64_t b);
46
47 #define declare_sort(field) \
48 static int sort_by_ ## field (const void *a, const void *b)
49
50 declare_sort(vss);
51 declare_sort(rss);
52 declare_sort(pss);
53 declare_sort(uss);
54 declare_sort(swap);
55 declare_sort(oomadj);
56
57 int (*compfn)(const void *a, const void *b);
58 static int order;
59
60 enum {
61 MEMINFO_TOTAL,
62 MEMINFO_FREE,
63 MEMINFO_BUFFERS,
64 MEMINFO_CACHED,
65 MEMINFO_SHMEM,
66 MEMINFO_SLAB,
67 MEMINFO_SWAP_TOTAL,
68 MEMINFO_SWAP_FREE,
69 MEMINFO_ZRAM_TOTAL,
70 MEMINFO_MAPPED,
71 MEMINFO_VMALLOC_USED,
72 MEMINFO_PAGE_TABLES,
73 MEMINFO_KERNEL_STACK,
74 MEMINFO_COUNT
75 };
76
get_mem_info(uint64_t mem[])77 void get_mem_info(uint64_t mem[]) {
78 char buffer[1024];
79 unsigned int numFound = 0;
80
81 int fd = open("/proc/meminfo", O_RDONLY);
82
83 if (fd < 0) {
84 printf("Unable to open /proc/meminfo: %s\n", strerror(errno));
85 return;
86 }
87
88 const int len = read(fd, buffer, sizeof(buffer)-1);
89 close(fd);
90
91 if (len < 0) {
92 printf("Empty /proc/meminfo");
93 return;
94 }
95 buffer[len] = 0;
96
97 static const char* const tags[] = {
98 "MemTotal:",
99 "MemFree:",
100 "Buffers:",
101 "Cached:",
102 "Shmem:",
103 "Slab:",
104 "SwapTotal:",
105 "SwapFree:",
106 "ZRam:", // not read from meminfo but from /sys/block/zram0
107 "Mapped:",
108 "VmallocUsed:",
109 "PageTables:",
110 "KernelStack:",
111 NULL
112 };
113 static const int tagsLen[] = {
114 9,
115 8,
116 8,
117 7,
118 6,
119 5,
120 10,
121 9,
122 5,
123 7,
124 12,
125 11,
126 12,
127 0
128 };
129
130 char* p = buffer;
131 while (*p && (numFound < (sizeof(tagsLen) / sizeof(tagsLen[0])))) {
132 int i = 0;
133 while (tags[i]) {
134 if (strncmp(p, tags[i], tagsLen[i]) == 0) {
135 p += tagsLen[i];
136 while (*p == ' ') p++;
137 char* num = p;
138 while (*p >= '0' && *p <= '9') p++;
139 if (*p != 0) {
140 *p = 0;
141 p++;
142 }
143 mem[i] = atoll(num);
144 numFound++;
145 break;
146 }
147 i++;
148 }
149 while (*p && *p != '\n') {
150 p++;
151 }
152 if (*p) p++;
153 }
154 }
155
get_zram_mem_used()156 static uint64_t get_zram_mem_used() {
157 #define ZRAM_SYSFS "/sys/block/zram0/"
158 FILE *f = fopen(ZRAM_SYSFS "mm_stat", "r");
159 if (f) {
160 uint64_t mem_used_total = 0;
161
162 int matched = fscanf(f, "%*d %*d %" SCNu64 " %*d %*d %*d %*d", &mem_used_total);
163 if (matched != 1)
164 fprintf(stderr, "warning: failed to parse " ZRAM_SYSFS "mm_stat\n");
165
166 fclose(f);
167 return mem_used_total;
168 }
169
170 f = fopen(ZRAM_SYSFS "mem_used_total", "r");
171 if (f) {
172 uint64_t mem_used_total = 0;
173
174 int matched = fscanf(f, "%" SCNu64, &mem_used_total);
175 if (matched != 1)
176 fprintf(stderr, "warning: failed to parse " ZRAM_SYSFS "mem_used_total\n");
177
178 fclose(f);
179 return mem_used_total;
180 }
181
182 return 0;
183 }
184
main(int argc,char * argv[])185 int main(int argc, char *argv[]) {
186 pm_kernel_t *ker;
187 pm_process_t *proc;
188 pid_t *pids;
189 size_t num_procs;
190 uint64_t total_pss;
191 uint64_t total_uss;
192 uint64_t total_swap;
193 uint64_t total_pswap;
194 uint64_t total_uswap;
195 uint64_t total_zswap;
196 int error;
197 bool has_swap = false, has_zram = false;
198 uint64_t required_flags = 0;
199 uint64_t flags_mask = 0;
200
201 int arg;
202 size_t i;
203
204 enum {
205 WS_OFF,
206 WS_ONLY,
207 WS_RESET,
208 } ws;
209
210 uint64_t mem[MEMINFO_COUNT] = { };
211 pm_proportional_swap_t *p_swap;
212 float zram_cr = 0.0;
213
214 signal(SIGPIPE, SIG_IGN);
215 compfn = &sort_by_pss;
216 order = -1;
217 ws = WS_OFF;
218 bool oomadj = false;
219
220 for (arg = 1; arg < argc; arg++) {
221 if (!strcmp(argv[arg], "-v")) { compfn = &sort_by_vss; continue; }
222 if (!strcmp(argv[arg], "-r")) { compfn = &sort_by_rss; continue; }
223 if (!strcmp(argv[arg], "-p")) { compfn = &sort_by_pss; continue; }
224 if (!strcmp(argv[arg], "-u")) { compfn = &sort_by_uss; continue; }
225 if (!strcmp(argv[arg], "-s")) { compfn = &sort_by_swap; continue; }
226 if (!strcmp(argv[arg], "-o")) { compfn = &sort_by_oomadj; oomadj = true; continue; }
227 if (!strcmp(argv[arg], "-c")) { required_flags = 0; flags_mask = KPF_SWAPBACKED; continue; }
228 if (!strcmp(argv[arg], "-C")) { required_flags = flags_mask = KPF_SWAPBACKED; continue; }
229 if (!strcmp(argv[arg], "-k")) { required_flags = flags_mask = KPF_KSM; continue; }
230 if (!strcmp(argv[arg], "-w")) { ws = WS_ONLY; continue; }
231 if (!strcmp(argv[arg], "-W")) { ws = WS_RESET; continue; }
232 if (!strcmp(argv[arg], "-R")) { order *= -1; continue; }
233 if (!strcmp(argv[arg], "-h")) { usage(argv[0]); exit(0); }
234 fprintf(stderr, "Invalid argument \"%s\".\n", argv[arg]);
235 usage(argv[0]);
236 exit(EXIT_FAILURE);
237 }
238
239 get_mem_info(mem);
240 p_swap = pm_memusage_pswap_create(mem[MEMINFO_SWAP_TOTAL] * 1024);
241
242 error = pm_kernel_create(&ker);
243 if (error) {
244 fprintf(stderr, "Error creating kernel interface -- "
245 "does this kernel have pagemap?\n");
246 exit(EXIT_FAILURE);
247 }
248
249 error = pm_kernel_pids(ker, &pids, &num_procs);
250 if (error) {
251 fprintf(stderr, "Error listing processes.\n");
252 exit(EXIT_FAILURE);
253 }
254
255 std::vector<proc_info> procs(num_procs);
256 for (i = 0; i < num_procs; i++) {
257 procs[i].pid = pids[i];
258 procs[i].oomadj = getoomadj(pids[i]);
259 pm_memusage_zero(&procs[i].usage);
260 pm_memusage_pswap_init_handle(&procs[i].usage, p_swap);
261 error = pm_process_create(ker, pids[i], &proc);
262 if (error) {
263 fprintf(stderr, "warning: could not create process interface for %d\n", pids[i]);
264 continue;
265 }
266
267 switch (ws) {
268 case WS_OFF:
269 error = pm_process_usage_flags(proc, &procs[i].usage, flags_mask,
270 required_flags);
271 break;
272 case WS_ONLY:
273 error = pm_process_workingset(proc, &procs[i].usage, 0);
274 break;
275 case WS_RESET:
276 error = pm_process_workingset(proc, NULL, 1);
277 break;
278 }
279
280 if (error) {
281 fprintf(stderr, "warning: could not read usage for %d\n", pids[i]);
282 }
283
284 if (ws != WS_RESET && procs[i].usage.swap) {
285 has_swap = true;
286 }
287
288 pm_process_destroy(proc);
289 }
290
291 free(pids);
292
293 if (ws == WS_RESET) exit(0);
294
295 procs.erase(std::remove_if(procs.begin(),
296 procs.end(),
297 [](auto proc){
298 return proc.usage.vss == 0;
299 }),
300 procs.end());
301
302 qsort(procs.data(), procs.size(), sizeof(procs[0]), compfn);
303
304 if (has_swap) {
305 uint64_t zram_mem_used = get_zram_mem_used();
306 if (zram_mem_used) {
307 mem[MEMINFO_ZRAM_TOTAL] = zram_mem_used/1024;
308 zram_cr = (float) mem[MEMINFO_ZRAM_TOTAL] /
309 (mem[MEMINFO_SWAP_TOTAL] - mem[MEMINFO_SWAP_FREE]);
310 has_zram = true;
311 }
312 }
313
314 printf("%5s ", "PID");
315 if (oomadj) {
316 printf("%5s ", "oom");
317 }
318 if (ws) {
319 printf("%7s %7s %7s ", "WRss", "WPss", "WUss");
320 if (has_swap) {
321 printf("%7s %7s %7s ", "WSwap", "WPSwap", "WUSwap");
322 if (has_zram) {
323 printf("%7s ", "WZSwap");
324 }
325 }
326 } else {
327 printf("%8s %7s %7s %7s ", "Vss", "Rss", "Pss", "Uss");
328 if (has_swap) {
329 printf("%7s %7s %7s ", "Swap", "PSwap", "USwap");
330 if (has_zram) {
331 printf("%7s ", "ZSwap");
332 }
333 }
334 }
335
336 printf("%s\n", "cmdline");
337
338 total_pss = 0;
339 total_uss = 0;
340 total_swap = 0;
341 total_pswap = 0;
342 total_uswap = 0;
343 total_zswap = 0;
344
345 std::vector<uint64_t> lmk_minfree;
346 std::vector<int> lmk_adj;
347 if (oomadj) {
348 getminfree(&lmk_minfree, &lmk_adj);
349 }
350 auto lmk_minfree_it = lmk_minfree.cbegin();
351 auto lmk_adj_it = lmk_adj.cbegin();
352
353 auto print_oomadj_totals = [&](int adj){
354 for (; lmk_adj_it != lmk_adj.cend() && lmk_minfree_it != lmk_minfree.cend() &&
355 adj > *lmk_adj_it; lmk_adj_it++, lmk_minfree_it++) {
356 // Print the cumulative total line
357 printf("%5s ", ""); // pid
358
359 printf("%5s ", ""); // oomadj
360
361 if (ws) {
362 printf("%7s %6" PRIu64 "K %6" PRIu64 "K ",
363 "", total_pss / 1024, total_uss / 1024);
364 } else {
365 printf("%8s %7s %6" PRIu64 "K %6" PRIu64 "K ",
366 "", "", total_pss / 1024, total_uss / 1024);
367 }
368
369 if (has_swap) {
370 printf("%6" PRIu64 "K ", total_swap / 1024);
371 printf("%6" PRIu64 "K ", total_pswap / 1024);
372 printf("%6" PRIu64 "K ", total_uswap / 1024);
373 if (has_zram) {
374 printf("%6" PRIu64 "K ", total_zswap / 1024);
375 }
376 }
377
378 printf("TOTAL for oomadj < %d (%6" PRIu64 "K)\n", *lmk_adj_it, *lmk_minfree_it / 1024);
379 }
380 };
381
382 for (auto& proc: procs) {
383 if (oomadj) {
384 print_oomadj_totals(proc.oomadj);
385 }
386
387 std::string cmdline = getprocname(proc.pid);
388
389 total_pss += proc.usage.pss;
390 total_uss += proc.usage.uss;
391 total_swap += proc.usage.swap;
392
393 printf("%5d ", proc.pid);
394
395 if (oomadj) {
396 printf("%5d ", proc.oomadj);
397 }
398
399 if (ws) {
400 printf("%6zuK %6zuK %6zuK ",
401 proc.usage.rss / 1024,
402 proc.usage.pss / 1024,
403 proc.usage.uss / 1024
404 );
405 } else {
406 printf("%7zuK %6zuK %6zuK %6zuK ",
407 proc.usage.vss / 1024,
408 proc.usage.rss / 1024,
409 proc.usage.pss / 1024,
410 proc.usage.uss / 1024
411 );
412 }
413
414 if (has_swap) {
415 pm_swapusage_t su;
416
417 pm_memusage_pswap_get_usage(&proc.usage, &su);
418 printf("%6zuK ", proc.usage.swap / 1024);
419 printf("%6zuK ", su.proportional / 1024);
420 printf("%6zuK ", su.unique / 1024);
421 total_pswap += su.proportional;
422 total_uswap += su.unique;
423 pm_memusage_pswap_free(&proc.usage);
424 if (has_zram) {
425 size_t zpswap = su.proportional * zram_cr;
426 printf("%6zuK ", zpswap / 1024);
427 total_zswap += zpswap;
428 }
429 }
430
431 printf("%s\n", cmdline.c_str());
432 }
433
434 pm_memusage_pswap_destroy(p_swap);
435
436 if (oomadj) {
437 print_oomadj_totals(INT_MAX);
438 }
439
440 // Print the separator line
441 printf("%5s ", "");
442
443 if (oomadj) {
444 printf("%5s ", "");
445 }
446
447 if (ws) {
448 printf("%7s %7s %7s ", "", "------", "------");
449 } else {
450 printf("%8s %7s %7s %7s ", "", "", "------", "------");
451 }
452
453 if (has_swap) {
454 printf("%7s %7s %7s ", "------", "------", "------");
455 if (has_zram) {
456 printf("%7s ", "------");
457 }
458 }
459
460 printf("%s\n", "------");
461
462 // Print the total line
463 printf("%5s ", "");
464
465 if (oomadj) {
466 printf("%5s ", "");
467 }
468
469 if (ws) {
470 printf("%7s %6" PRIu64 "K %6" PRIu64 "K ",
471 "", total_pss / 1024, total_uss / 1024);
472 } else {
473 printf("%8s %7s %6" PRIu64 "K %6" PRIu64 "K ",
474 "", "", total_pss / 1024, total_uss / 1024);
475 }
476
477 if (has_swap) {
478 printf("%6" PRIu64 "K ", total_swap / 1024);
479 printf("%6" PRIu64 "K ", total_pswap / 1024);
480 printf("%6" PRIu64 "K ", total_uswap / 1024);
481 if (has_zram) {
482 printf("%6" PRIu64 "K ", total_zswap / 1024);
483 }
484 }
485
486 printf("TOTAL\n");
487
488 printf("\n");
489
490 if (has_swap) {
491 printf("ZRAM: %" PRIu64 "K physical used for %" PRIu64 "K in swap "
492 "(%" PRIu64 "K total swap)\n",
493 mem[MEMINFO_ZRAM_TOTAL], (mem[MEMINFO_SWAP_TOTAL] - mem[MEMINFO_SWAP_FREE]),
494 mem[MEMINFO_SWAP_TOTAL]);
495 }
496 printf(" RAM: %" PRIu64 "K total, %" PRIu64 "K free, %" PRIu64 "K buffers, "
497 "%" PRIu64 "K cached, %" PRIu64 "K shmem, %" PRIu64 "K slab\n",
498 mem[MEMINFO_TOTAL], mem[MEMINFO_FREE], mem[MEMINFO_BUFFERS],
499 mem[MEMINFO_CACHED], mem[MEMINFO_SHMEM], mem[MEMINFO_SLAB]);
500
501 return 0;
502 }
503
usage(char * myname)504 static void usage(char *myname) {
505 fprintf(stderr, "Usage: %s [ -W ] [ -v | -r | -p | -u | -s | -h ]\n"
506 " -v Sort by VSS.\n"
507 " -r Sort by RSS.\n"
508 " -p Sort by PSS.\n"
509 " -u Sort by USS.\n"
510 " -s Sort by swap.\n"
511 " (Default sort order is PSS.)\n"
512 " -R Reverse sort order (default is descending).\n"
513 " -c Only show cached (storage backed) pages\n"
514 " -C Only show non-cached (ram/swap backed) pages\n"
515 " -k Only show pages collapsed by KSM\n"
516 " -w Display statistics for working set only.\n"
517 " -W Reset working set of all processes.\n"
518 " -o Show and sort by oom score against lowmemorykiller thresholds.\n"
519 " -h Display this help screen.\n",
520 myname);
521 }
522
523 // Get the process name for a given PID.
getprocname(pid_t pid)524 static std::string getprocname(pid_t pid) {
525 std::string filename = android::base::StringPrintf("/proc/%d/cmdline", pid);
526
527 std::string procname;
528
529 if (!android::base::ReadFileToString(filename, &procname)) {
530 // The process went away before we could read its process name.
531 procname = "<unknown>";
532 }
533
534 return procname;
535 }
536
getoomadj(pid_t pid)537 static int getoomadj(pid_t pid) {
538 std::string filename = android::base::StringPrintf("/proc/%d/oom_score_adj", pid);
539 std::string oomadj;
540
541 if (!android::base::ReadFileToString(filename, &oomadj)) {
542 return -1001;
543 }
544
545 return strtol(oomadj.c_str(), NULL, 10);
546 }
547
getminfree(std::vector<uint64_t> * minfree,std::vector<int> * adj)548 static bool getminfree(std::vector<uint64_t>* minfree, std::vector<int>* adj) {
549 std::string minfree_str;
550 std::string adj_str;
551
552 if (!android::base::ReadFileToString("/sys/module/lowmemorykiller/parameters/minfree", &minfree_str)) {
553 return false;
554 }
555
556 if (!android::base::ReadFileToString("/sys/module/lowmemorykiller/parameters/adj", &adj_str)) {
557 return false;
558 }
559
560 std::vector<std::string> minfree_vec = android::base::Split(minfree_str, ",");
561 std::vector<std::string> adj_vec = android::base::Split(adj_str, ",");
562
563 minfree->clear();
564 minfree->resize(minfree_vec.size());
565 adj->clear();
566 adj->resize(adj_vec.size());
567
568 std::transform(minfree_vec.begin(), minfree_vec.end(), minfree->begin(),
569 [](const std::string& s) -> uint64_t {
570 return strtoull(s.c_str(), NULL, 10) * PAGE_SIZE;
571 });
572
573 std::transform(adj_vec.begin(), adj_vec.end(), adj->begin(),
574 [](const std::string& s) -> int {
575 return strtol(s.c_str(), NULL, 10);
576 });
577
578 return true;
579 }
580
numcmp(uint64_t a,uint64_t b)581 static int numcmp(uint64_t a, uint64_t b) {
582 if (a < b) return -1;
583 if (a > b) return 1;
584 return 0;
585 }
586
snumcmp(int64_t a,int64_t b)587 static int snumcmp(int64_t a, int64_t b) {
588 if (a < b) return -1;
589 if (a > b) return 1;
590 return 0;
591 }
592
593 #define create_sort(field, compfn) \
594 static int sort_by_ ## field (const void *a, const void *b) { \
595 return order * compfn( \
596 ((struct proc_info*)(a))->usage.field, \
597 ((struct proc_info*)(b))->usage.field \
598 ); \
599 }
600
create_sort(vss,numcmp)601 create_sort(vss, numcmp)
602 create_sort(rss, numcmp)
603 create_sort(pss, numcmp)
604 create_sort(uss, numcmp)
605 create_sort(swap, numcmp)
606
607 static int sort_by_oomadj (const void *a, const void *b) {
608 // Negative oomadj is higher priority, reverse the sort order
609 return -1 * order * snumcmp(
610 ((struct proc_info*)a)->oomadj,
611 ((struct proc_info*)b)->oomadj
612 );
613 }
614