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 <android-base/file.h>
18 #include <android-base/parseint.h>
19 #include <android-base/stringprintf.h>
20 #include <android-base/strings.h>
21 #include <dirent.h>
22 #include <errno.h>
23 #include <inttypes.h>
24 #include <linux/kernel-page-flags.h>
25 #include <linux/oom.h>
26 #include <meminfo/procmeminfo.h>
27 #include <meminfo/sysmeminfo.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <sys/types.h>
31 #include <unistd.h>
32 
33 #include <iostream>
34 #include <memory>
35 #include <sstream>
36 #include <vector>
37 
38 using ::android::meminfo::MemUsage;
39 using ::android::meminfo::ProcMemInfo;
40 
41 struct ProcessRecord {
42   public:
43     ProcessRecord(pid_t pid, bool get_wss = false, uint64_t pgflags = 0, uint64_t pgflags_mask = 0)
44         : pid_(-1),
45           oomadj_(OOM_SCORE_ADJ_MAX + 1),
46           cmdline_(""),
47           proportional_swap_(0),
48           unique_swap_(0),
49           zswap_(0) {
50         std::unique_ptr<ProcMemInfo> procmem =
51                 std::make_unique<ProcMemInfo>(pid, get_wss, pgflags, pgflags_mask);
52         if (procmem == nullptr) {
53             std::cerr << "Failed to create ProcMemInfo for: " << pid << std::endl;
54             return;
55         }
56 
57         std::string fname = ::android::base::StringPrintf("/proc/%d/oom_score_adj", pid);
58         auto oomscore_fp =
59                 std::unique_ptr<FILE, decltype(&fclose)>{fopen(fname.c_str(), "re"), fclose};
60         if (oomscore_fp == nullptr) {
61             std::cerr << "Failed to open oom_score_adj file: " << fname << std::endl;
62             return;
63         }
64 
65         if (fscanf(oomscore_fp.get(), "%d\n", &oomadj_) != 1) {
66             std::cerr << "Failed to read oomadj from: " << fname << std::endl;
67             return;
68         }
69 
70         fname = ::android::base::StringPrintf("/proc/%d/cmdline", pid);
71         if (!::android::base::ReadFileToString(fname, &cmdline_)) {
72             std::cerr << "Failed to read cmdline from: " << fname << std::endl;
73             cmdline_ = "<unknown>";
74         }
75         // We deliberately don't read the proc/<pid>cmdline file directly into 'cmdline_'
76         // because of some processes showing up cmdlines that end with "0x00 0x0A 0x00"
77         // e.g. xtra-daemon, lowi-server
78         // The .c_str() assignment below then takes care of trimming the cmdline at the first
79         // 0x00. This is how original procrank worked (luckily)
80         cmdline_.resize(strlen(cmdline_.c_str()));
81         usage_or_wss_ = get_wss ? procmem->Wss() : procmem->Usage();
82         swap_offsets_ = procmem->SwapOffsets();
83         pid_ = pid;
84     }
85 
86     bool valid() const { return pid_ != -1; }
87 
88     void CalculateSwap(const std::vector<uint16_t>& swap_offset_array,
89                        float zram_compression_ratio) {
90         for (auto& off : swap_offsets_) {
91             proportional_swap_ += getpagesize() / swap_offset_array[off];
92             unique_swap_ += swap_offset_array[off] == 1 ? getpagesize() : 0;
93             zswap_ = proportional_swap_ * zram_compression_ratio;
94         }
95     }
96 
97     // Getters
98     pid_t pid() const { return pid_; }
99     const std::string& cmdline() const { return cmdline_; }
100     int32_t oomadj() const { return oomadj_; }
101     uint64_t proportional_swap() const { return proportional_swap_; }
102     uint64_t unique_swap() const { return unique_swap_; }
103     uint64_t zswap() const { return zswap_; }
104 
105     // Wrappers to ProcMemInfo
106     const std::vector<uint64_t>& SwapOffsets() const { return swap_offsets_; }
107     const MemUsage& Usage() const { return usage_or_wss_; }
108     const MemUsage& Wss() const { return usage_or_wss_; }
109 
110   private:
111     pid_t pid_;
112     int32_t oomadj_;
113     std::string cmdline_;
114     uint64_t proportional_swap_;
115     uint64_t unique_swap_;
116     uint64_t zswap_;
117     MemUsage usage_or_wss_;
118     std::vector<uint64_t> swap_offsets_;
119 };
120 
121 // Show working set instead of memory consumption
122 bool show_wss = false;
123 // Reset working set of each process
124 bool reset_wss = false;
125 // Show per-process oom_score_adj column
126 bool show_oomadj = false;
127 // True if the device has swap enabled
128 bool has_swap = false;
129 // True, if device has zram enabled
130 bool has_zram = false;
131 // If zram is enabled, the compression ratio is zram used / swap used.
132 float zram_compression_ratio = 0.0;
133 // Sort process in reverse, default is descending
134 bool reverse_sort = false;
135 
136 // Calculated total memory usage across all processes in the system
137 uint64_t total_pss = 0;
138 uint64_t total_uss = 0;
139 uint64_t total_swap = 0;
140 uint64_t total_pswap = 0;
141 uint64_t total_uswap = 0;
142 uint64_t total_zswap = 0;
143 
144 [[noreturn]] static void usage(int exit_status) {
145     std::cerr << "Usage: " << getprogname() << " [ -W ] [ -v | -r | -p | -u | -s | -h ]"
146               << std::endl
147               << "    -v  Sort by VSS." << std::endl
148               << "    -r  Sort by RSS." << std::endl
149               << "    -p  Sort by PSS." << std::endl
150               << "    -u  Sort by USS." << std::endl
151               << "    -s  Sort by swap." << std::endl
152               << "        (Default sort order is PSS.)" << std::endl
153               << "    -R  Reverse sort order (default is descending)." << std::endl
154               << "    -c  Only show cached (storage backed) pages" << std::endl
155               << "    -C  Only show non-cached (ram/swap backed) pages" << std::endl
156               << "    -k  Only show pages collapsed by KSM" << std::endl
157               << "    -w  Display statistics for working set only." << std::endl
158               << "    -W  Reset working set of all processes." << std::endl
159               << "    -o  Show and sort by oom score against lowmemorykiller thresholds."
160               << std::endl
161               << "    -h  Display this help screen." << std::endl;
162     exit(exit_status);
163 }
164 
165 static bool read_all_pids(std::vector<pid_t>* pids, std::function<bool(pid_t pid)> for_each_pid) {
166     pids->clear();
167     std::unique_ptr<DIR, int (*)(DIR*)> procdir(opendir("/proc"), closedir);
168     if (!procdir) return false;
169 
170     struct dirent* dir;
171     pid_t pid;
172     while ((dir = readdir(procdir.get()))) {
173         if (!::android::base::ParseInt(dir->d_name, &pid)) continue;
174         if (!for_each_pid(pid)) return false;
175         pids->emplace_back(pid);
176     }
177 
178     return true;
179 }
180 
181 static bool count_swap_offsets(const ProcessRecord& proc,
182                                std::vector<uint16_t>& swap_offset_array) {
183     const std::vector<uint64_t>& swp_offs = proc.SwapOffsets();
184     for (auto& off : swp_offs) {
185         if (off >= swap_offset_array.size()) {
186             std::cerr << "swap offset " << off << " is out of bounds for process: " << proc.pid()
187                       << std::endl;
188             return false;
189         }
190 
191         if (swap_offset_array[off] == USHRT_MAX) {
192             std::cerr << "swap offset " << off << " ref count overflow in process: " << proc.pid()
193                       << std::endl;
194             return false;
195         }
196 
197         swap_offset_array[off]++;
198     }
199 
200     return true;
201 }
202 
203 static void print_header(std::stringstream& ss) {
204     ss.str("");
205     ss << ::android::base::StringPrintf("%5s  ", "PID");
206     if (show_oomadj) {
207         ss << ::android::base::StringPrintf("%5s  ", "oom");
208     }
209 
210     if (show_wss) {
211         ss << ::android::base::StringPrintf("%7s  %7s  %7s  ", "WRss", "WPss", "WUss");
212         // now swap statistics here, working set pages by definition shouldn't end up in swap.
213     } else {
214         ss << ::android::base::StringPrintf("%8s  %7s  %7s  %7s  ", "Vss", "Rss", "Pss", "Uss");
215         if (has_swap) {
216             ss << ::android::base::StringPrintf("%7s  %7s  %7s  ", "Swap", "PSwap", "USwap");
217             if (has_zram) {
218                 ss << ::android::base::StringPrintf("%7s  ", "ZSwap");
219             }
220         }
221     }
222 
223     ss << "cmdline";
224 }
225 
226 static void print_process_record(std::stringstream& ss, ProcessRecord& proc) {
227     ss << ::android::base::StringPrintf("%5d  ", proc.pid());
228     if (show_oomadj) {
229         ss << ::android::base::StringPrintf("%5d  ", proc.oomadj());
230     }
231 
232     if (show_wss) {
233         ss << ::android::base::StringPrintf("%6" PRIu64 "K  %6" PRIu64 "K  %6" PRIu64 "K  ",
234                                             proc.Wss().rss / 1024, proc.Wss().pss / 1024,
235                                             proc.Wss().uss / 1024);
236     } else {
237         ss << ::android::base::StringPrintf("%7" PRIu64 "K  %6" PRIu64 "K  %6" PRIu64 "K  %6" PRIu64
238                                             "K  ",
239                                             proc.Usage().vss / 1024, proc.Usage().rss / 1024,
240                                             proc.Usage().pss / 1024, proc.Usage().uss / 1024);
241         if (has_swap) {
242             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", proc.Usage().swap / 1024);
243             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", proc.proportional_swap() / 1024);
244             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", proc.unique_swap() / 1024);
245             if (has_zram) {
246                 ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", (proc.zswap() / 1024));
247             }
248         }
249     }
250 }
251 
252 static void print_processes(std::stringstream& ss, std::vector<ProcessRecord>& procs,
253                             const std::vector<uint16_t>& swap_offset_array) {
254     for (auto& proc : procs) {
255         total_pss += show_wss ? proc.Wss().pss : proc.Usage().pss;
256         total_uss += show_wss ? proc.Wss().uss : proc.Usage().uss;
257         if (!show_wss && has_swap) {
258             proc.CalculateSwap(swap_offset_array, zram_compression_ratio);
259             total_swap += proc.Usage().swap;
260             total_pswap += proc.proportional_swap();
261             total_uswap += proc.unique_swap();
262             if (has_zram) {
263                 total_zswap += proc.zswap();
264             }
265         }
266 
267         print_process_record(ss, proc);
268         ss << proc.cmdline() << std::endl;
269     }
270 }
271 
272 static void print_separator(std::stringstream& ss) {
273     ss << ::android::base::StringPrintf("%5s  ", "");
274     if (show_oomadj) {
275         ss << ::android::base::StringPrintf("%5s  ", "");
276     }
277 
278     if (show_wss) {
279         ss << ::android::base::StringPrintf("%7s  %7s  %7s  ", "", "------", "------");
280     } else {
281         ss << ::android::base::StringPrintf("%8s  %7s  %7s  %7s  ", "", "", "------", "------");
282         if (has_swap) {
283             ss << ::android::base::StringPrintf("%7s  %7s  %7s  ", "------", "------", "------");
284             if (has_zram) {
285                 ss << ::android::base::StringPrintf("%7s  ", "------");
286             }
287         }
288     }
289 
290     ss << ::android::base::StringPrintf("%s", "------");
291 }
292 
293 static void print_totals(std::stringstream& ss) {
294     ss << ::android::base::StringPrintf("%5s  ", "");
295     if (show_oomadj) {
296         ss << ::android::base::StringPrintf("%5s  ", "");
297     }
298 
299     if (show_wss) {
300         ss << ::android::base::StringPrintf("%7s  %6" PRIu64 "K  %6" PRIu64 "K  ", "",
301                                             total_pss / 1024, total_uss / 1024);
302     } else {
303         ss << ::android::base::StringPrintf("%8s  %7s  %6" PRIu64 "K  %6" PRIu64 "K  ", "", "",
304                                             total_pss / 1024, total_uss / 1024);
305         if (has_swap) {
306             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", total_swap / 1024);
307             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", total_pswap / 1024);
308             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", total_uswap / 1024);
309             if (has_zram) {
310                 ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", total_zswap / 1024);
311             }
312         }
313     }
314     ss << "TOTAL";
315 }
316 
317 static void print_sysmeminfo(std::stringstream& ss, ::android::meminfo::SysMemInfo& smi) {
318     if (has_swap) {
319         ss << ::android::base::StringPrintf("ZRAM: %" PRIu64 "K physical used for %" PRIu64
320                                             "K in swap "
321                                             "(%" PRIu64 "K total swap)",
322                                             smi.mem_zram_kb(),
323                                             (smi.mem_swap_kb() - smi.mem_swap_free_kb()),
324                                             smi.mem_swap_kb())
325            << std::endl;
326     }
327 
328     ss << ::android::base::StringPrintf(" RAM: %" PRIu64 "K total, %" PRIu64 "K free, %" PRIu64
329                                         "K buffers, "
330                                         "%" PRIu64 "K cached, %" PRIu64 "K shmem, %" PRIu64
331                                         "K slab",
332                                         smi.mem_total_kb(), smi.mem_free_kb(), smi.mem_buffers_kb(),
333                                         smi.mem_cached_kb(), smi.mem_shmem_kb(), smi.mem_slab_kb());
334 }
335 
336 int main(int argc, char* argv[]) {
337     auto pss_sort = [](ProcessRecord& a, ProcessRecord& b) {
338         MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
339         MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
340         return reverse_sort ? stats_a.pss < stats_b.pss : stats_a.pss > stats_b.pss;
341     };
342 
343     auto uss_sort = [](ProcessRecord& a, ProcessRecord& b) {
344         MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
345         MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
346         return reverse_sort ? stats_a.uss < stats_b.uss : stats_a.uss > stats_b.uss;
347     };
348 
349     auto rss_sort = [](ProcessRecord& a, ProcessRecord& b) {
350         MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
351         MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
352         return reverse_sort ? stats_a.rss < stats_b.rss : stats_a.rss > stats_b.rss;
353     };
354 
355     auto vss_sort = [](ProcessRecord& a, ProcessRecord& b) {
356         MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
357         MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
358         return reverse_sort ? stats_a.vss < stats_b.vss : stats_a.vss > stats_b.vss;
359     };
360 
361     auto swap_sort = [](ProcessRecord& a, ProcessRecord& b) {
362         MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
363         MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
364         return reverse_sort ? stats_a.swap < stats_b.swap : stats_a.swap > stats_b.swap;
365     };
366 
367     auto oomadj_sort = [](ProcessRecord& a, ProcessRecord& b) {
368         return reverse_sort ? a.oomadj() < b.oomadj() : a.oomadj() > b.oomadj();
369     };
370 
371     // default PSS sort
372     std::function<bool(ProcessRecord & a, ProcessRecord & b)> proc_sort = pss_sort;
373 
374     // count all pages by default
375     uint64_t pgflags = 0;
376     uint64_t pgflags_mask = 0;
377 
378     int opt;
379     while ((opt = getopt(argc, argv, "cChkoprRsuvwW")) != -1) {
380         switch (opt) {
381             case 'c':
382                 pgflags = 0;
383                 pgflags_mask = (1 << KPF_SWAPBACKED);
384                 break;
385             case 'C':
386                 pgflags = (1 << KPF_SWAPBACKED);
387                 pgflags_mask = (1 << KPF_SWAPBACKED);
388                 break;
389             case 'h':
390                 usage(EXIT_SUCCESS);
391             case 'k':
392                 pgflags = (1 << KPF_KSM);
393                 pgflags_mask = (1 << KPF_KSM);
394                 break;
395             case 'o':
396                 proc_sort = oomadj_sort;
397                 show_oomadj = true;
398                 break;
399             case 'p':
400                 proc_sort = pss_sort;
401                 break;
402             case 'r':
403                 proc_sort = rss_sort;
404                 break;
405             case 'R':
406                 reverse_sort = true;
407                 break;
408             case 's':
409                 proc_sort = swap_sort;
410                 break;
411             case 'u':
412                 proc_sort = uss_sort;
413                 break;
414             case 'v':
415                 proc_sort = vss_sort;
416                 break;
417             case 'w':
418                 show_wss = true;
419                 break;
420             case 'W':
421                 reset_wss = true;
422                 break;
423             default:
424                 usage(EXIT_FAILURE);
425         }
426     }
427 
428     std::vector<pid_t> pids;
429     std::vector<ProcessRecord> procs;
430     if (reset_wss) {
431         if (!read_all_pids(&pids,
432                            [&](pid_t pid) -> bool { return ProcMemInfo::ResetWorkingSet(pid); })) {
433             std::cerr << "Failed to reset working set of all processes" << std::endl;
434             exit(EXIT_FAILURE);
435         }
436         // we are done, all other options passed to procrank are ignored in the presence of '-W'
437         return 0;
438     }
439 
440     ::android::meminfo::SysMemInfo smi;
441     if (!smi.ReadMemInfo()) {
442         std::cerr << "Failed to get system memory info" << std::endl;
443         exit(EXIT_FAILURE);
444     }
445 
446     // Figure out swap and zram
447     uint64_t swap_total = smi.mem_swap_kb() * 1024;
448     has_swap = swap_total > 0;
449     // Allocate the swap array
450     std::vector<uint16_t> swap_offset_array(swap_total / getpagesize() + 1, 0);
451     if (has_swap) {
452         has_zram = smi.mem_zram_kb() > 0;
453         if (has_zram) {
454             zram_compression_ratio = static_cast<float>(smi.mem_zram_kb()) /
455                                      (smi.mem_swap_kb() - smi.mem_swap_free_kb());
456         }
457     }
458 
459     auto mark_swap_usage = [&](pid_t pid) -> bool {
460         ProcessRecord proc(pid, show_wss, pgflags, pgflags_mask);
461         if (!proc.valid()) {
462             // Check to see if the process is still around, skip the process if the proc
463             // directory is inaccessible. It was most likely killed while creating the process
464             // record
465             std::string procdir = ::android::base::StringPrintf("/proc/%d", pid);
466             if (access(procdir.c_str(), F_OK | R_OK)) return true;
467 
468             // Warn if we failed to gather process stats even while it is still alive.
469             // Return success here, so we continue to print stats for other processes.
470             std::cerr << "warning: failed to create process record for: " << pid << std::endl;
471             return true;
472         }
473 
474         // Skip processes with no memory mappings
475         uint64_t vss = show_wss ? proc.Wss().vss : proc.Usage().vss;
476         if (vss == 0) return true;
477 
478         // collect swap_offset counts from all processes in 1st pass
479         if (!show_wss && has_swap &&
480             !count_swap_offsets(proc, swap_offset_array)) {
481             std::cerr << "Failed to count swap offsets for process: " << pid << std::endl;
482             return false;
483         }
484 
485         procs.emplace_back(std::move(proc));
486         return true;
487     };
488 
489     // Get a list of all pids currently running in the system in 1st pass through all processes.
490     // Mark each swap offset used by the process as we find them for calculating proportional
491     // swap usage later.
492     if (!read_all_pids(&pids, mark_swap_usage)) {
493         std::cerr << "Failed to read all pids from the system" << std::endl;
494         exit(EXIT_FAILURE);
495     }
496 
497     std::stringstream ss;
498     if (procs.empty()) {
499         // This would happen in corner cases where procrank is being run to find KSM usage on a
500         // system with no KSM and combined with working set determination as follows
501         //   procrank -w -u -k
502         //   procrank -w -s -k
503         //   procrank -w -o -k
504         ss << "<empty>" << std::endl << std::endl;
505         print_sysmeminfo(ss, smi);
506         ss << std::endl;
507         std::cout << ss.str();
508         return 0;
509     }
510 
511     // Sort all process records, default is PSS descending
512     std::sort(procs.begin(), procs.end(), proc_sort);
513 
514     // start dumping output in string stream
515     print_header(ss);
516     ss << std::endl;
517 
518     // 2nd pass to calculate and get per process stats to add them up
519     print_processes(ss, procs, swap_offset_array);
520 
521     // Add separator to output
522     print_separator(ss);
523     ss << std::endl;
524 
525     // Add totals to output
526     print_totals(ss);
527     ss << std::endl << std::endl;
528 
529     // Add system information at the end
530     print_sysmeminfo(ss, smi);
531     ss << std::endl;
532 
533     // dump on the screen
534     std::cout << ss.str();
535 
536     return 0;
537 }
538