summaryrefslogtreecommitdiffstats
path: root/libmeminfo
diff options
context:
space:
mode:
Diffstat (limited to 'libmeminfo')
-rw-r--r--libmeminfo/tools/Android.bp15
-rw-r--r--libmeminfo/tools/procrank.cpp530
2 files changed, 545 insertions, 0 deletions
diff --git a/libmeminfo/tools/Android.bp b/libmeminfo/tools/Android.bp
index 0870130ea..7c4166023 100644
--- a/libmeminfo/tools/Android.bp
+++ b/libmeminfo/tools/Android.bp
@@ -25,3 +25,18 @@ cc_binary {
"libmeminfo",
],
}
+
+cc_binary {
+ name: "procrank2",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ ],
+
+ srcs: ["procrank.cpp"],
+ shared_libs: [
+ "libbase",
+ "libmeminfo",
+ ],
+}
diff --git a/libmeminfo/tools/procrank.cpp b/libmeminfo/tools/procrank.cpp
new file mode 100644
index 000000000..e39e7fa56
--- /dev/null
+++ b/libmeminfo/tools/procrank.cpp
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <linux/kernel-page-flags.h>
+#include <linux/oom.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include <meminfo/procmeminfo.h>
+#include <meminfo/sysmeminfo.h>
+
+using ::android::meminfo::MemUsage;
+using ::android::meminfo::ProcMemInfo;
+
+struct ProcessRecord {
+ public:
+ ProcessRecord(pid_t pid, bool get_wss = false, uint64_t pgflags = 0, uint64_t pgflags_mask = 0)
+ : pid_(-1),
+ procmem_(nullptr),
+ oomadj_(OOM_SCORE_ADJ_MAX + 1),
+ cmdline_(""),
+ proportional_swap_(0),
+ unique_swap_(0),
+ zswap_(0) {
+ std::unique_ptr<ProcMemInfo> procmem =
+ std::make_unique<ProcMemInfo>(pid, get_wss, pgflags, pgflags_mask);
+ if (procmem == nullptr) {
+ std::cerr << "Failed to create ProcMemInfo for: " << pid << std::endl;
+ return;
+ }
+
+ std::string fname = ::android::base::StringPrintf("/proc/%d/oom_score_adj", pid);
+ auto oomscore_fp =
+ std::unique_ptr<FILE, decltype(&fclose)>{fopen(fname.c_str(), "re"), fclose};
+ if (oomscore_fp == nullptr) {
+ std::cerr << "Failed to open oom_score_adj file: " << fname << std::endl;
+ return;
+ }
+
+ if (fscanf(oomscore_fp.get(), "%d\n", &oomadj_) != 1) {
+ std::cerr << "Failed to read oomadj from: " << fname << std::endl;
+ return;
+ }
+
+ fname = ::android::base::StringPrintf("/proc/%d/cmdline", pid);
+ if (!::android::base::ReadFileToString(fname, &cmdline_)) {
+ std::cerr << "Failed to read cmdline from: " << fname << std::endl;
+ cmdline_ = "<unknown>";
+ }
+ // We deliberately don't read the proc/<pid>cmdline file directly into 'cmdline_'
+ // because of some processes showing up cmdlines that end with "0x00 0x0A 0x00"
+ // e.g. xtra-daemon, lowi-server
+ // The .c_str() assignment below then takes care of trimming the cmdline at the first
+ // 0x00. This is how original procrank worked (luckily)
+ cmdline_.resize(strlen(cmdline_.c_str()));
+ procmem_ = std::move(procmem);
+ pid_ = pid;
+ }
+
+ bool valid() const { return pid_ != -1; }
+
+ void CalculateSwap(const uint16_t* swap_offset_array, float zram_compression_ratio) {
+ const std::vector<uint16_t>& swp_offs = procmem_->SwapOffsets();
+ for (auto& off : swp_offs) {
+ proportional_swap_ += getpagesize() / swap_offset_array[off];
+ unique_swap_ += swap_offset_array[off] == 1 ? getpagesize() : 0;
+ zswap_ = proportional_swap_ * zram_compression_ratio;
+ }
+ }
+
+ // Getters
+ pid_t pid() const { return pid_; }
+ const std::string& cmdline() const { return cmdline_; }
+ int32_t oomadj() const { return oomadj_; }
+ uint64_t proportional_swap() const { return proportional_swap_; }
+ uint64_t unique_swap() const { return unique_swap_; }
+ uint64_t zswap() const { return zswap_; }
+
+ // Wrappers to ProcMemInfo
+ const std::vector<uint16_t>& SwapOffsets() const { return procmem_->SwapOffsets(); }
+ const MemUsage& Usage() const { return procmem_->Usage(); }
+ const MemUsage& Wss() const { return procmem_->Wss(); }
+
+ private:
+ pid_t pid_;
+ std::unique_ptr<ProcMemInfo> procmem_;
+ int32_t oomadj_;
+ std::string cmdline_;
+ uint64_t proportional_swap_;
+ uint64_t unique_swap_;
+ uint64_t zswap_;
+};
+
+// Show working set instead of memory consumption
+bool show_wss = false;
+// Reset working set of each process
+bool reset_wss = false;
+// Show per-process oom_score_adj column
+bool show_oomadj = false;
+// True if the device has swap enabled
+bool has_swap = false;
+// True, if device has zram enabled
+bool has_zram = false;
+// If zram is enabled, the compression ratio is zram used / swap used.
+float zram_compression_ratio = 0.0;
+// Sort process in reverse, default is descending
+bool reverse_sort = false;
+
+// Calculated total memory usage across all processes in the system
+uint64_t total_pss = 0;
+uint64_t total_uss = 0;
+uint64_t total_swap = 0;
+uint64_t total_pswap = 0;
+uint64_t total_uswap = 0;
+uint64_t total_zswap = 0;
+
+static void usage(const char* myname) {
+ std::cerr << "Usage: " << myname << " [ -W ] [ -v | -r | -p | -u | -s | -h ]" << std::endl
+ << " -v Sort by VSS." << std::endl
+ << " -r Sort by RSS." << std::endl
+ << " -p Sort by PSS." << std::endl
+ << " -u Sort by USS." << std::endl
+ << " -s Sort by swap." << std::endl
+ << " (Default sort order is PSS.)" << std::endl
+ << " -R Reverse sort order (default is descending)." << std::endl
+ << " -c Only show cached (storage backed) pages" << std::endl
+ << " -C Only show non-cached (ram/swap backed) pages" << std::endl
+ << " -k Only show pages collapsed by KSM" << std::endl
+ << " -w Display statistics for working set only." << std::endl
+ << " -W Reset working set of all processes." << std::endl
+ << " -o Show and sort by oom score against lowmemorykiller thresholds."
+ << std::endl
+ << " -h Display this help screen." << std::endl;
+}
+
+static bool read_all_pids(std::vector<pid_t>* pids, std::function<bool(pid_t pid)> for_each_pid) {
+ pids->clear();
+ std::unique_ptr<DIR, int (*)(DIR*)> procdir(opendir("/proc"), closedir);
+ if (!procdir) return false;
+
+ struct dirent* dir;
+ pid_t pid;
+ while ((dir = readdir(procdir.get()))) {
+ if (!::android::base::ParseInt(dir->d_name, &pid)) continue;
+ if (!for_each_pid(pid)) return false;
+ pids->push_back(pid);
+ }
+
+ return true;
+}
+
+static bool count_swap_offsets(const ProcessRecord& proc, uint16_t* swap_offset_array,
+ uint32_t size) {
+ const std::vector<uint16_t>& swp_offs = proc.SwapOffsets();
+ for (auto& off : swp_offs) {
+ if (off >= size) {
+ std::cerr << "swap offset " << off << " is out of bounds for process: " << proc.pid()
+ << std::endl;
+ return false;
+ }
+
+ if (swap_offset_array[off] == USHRT_MAX) {
+ std::cerr << "swap offset " << off << " ref count overflow in process: " << proc.pid()
+ << std::endl;
+ return false;
+ }
+
+ swap_offset_array[off]++;
+ }
+
+ return true;
+}
+
+static void print_header(std::stringstream& ss) {
+ ss.str("");
+ ss << ::android::base::StringPrintf("%5s ", "PID");
+ if (show_oomadj) {
+ ss << ::android::base::StringPrintf("%5s ", "oom");
+ }
+
+ if (show_wss) {
+ ss << ::android::base::StringPrintf("%7s %7s %7s ", "WRss", "WPss", "WUss");
+ // now swap statistics here, working set pages by definition shouldn't end up in swap.
+ } else {
+ ss << ::android::base::StringPrintf("%8s %7s %7s %7s ", "Vss", "Rss", "Pss", "Uss");
+ if (has_swap) {
+ ss << ::android::base::StringPrintf("%7s %7s %7s ", "Swap", "PSwap", "USwap");
+ if (has_zram) {
+ ss << ::android::base::StringPrintf("%7s ", "ZSwap");
+ }
+ }
+ }
+
+ ss << "cmdline";
+}
+
+static void print_process_record(std::stringstream& ss, ProcessRecord& proc) {
+ ss << ::android::base::StringPrintf("%5d ", proc.pid());
+ if (show_oomadj) {
+ ss << ::android::base::StringPrintf("%5d ", proc.oomadj());
+ }
+
+ if (show_wss) {
+ ss << ::android::base::StringPrintf("%6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ",
+ proc.Wss().rss / 1024, proc.Wss().pss / 1024,
+ proc.Wss().uss / 1024);
+ } else {
+ ss << ::android::base::StringPrintf("%7" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64
+ "K ",
+ proc.Usage().vss / 1024, proc.Usage().rss / 1024,
+ proc.Usage().pss / 1024, proc.Usage().uss / 1024);
+ if (has_swap) {
+ ss << ::android::base::StringPrintf("%6" PRIu64 "K ", proc.Usage().swap / 1024);
+ ss << ::android::base::StringPrintf("%6" PRIu64 "K ", proc.proportional_swap() / 1024);
+ ss << ::android::base::StringPrintf("%6" PRIu64 "K ", proc.unique_swap() / 1024);
+ if (has_zram) {
+ ss << ::android::base::StringPrintf("%6" PRIu64 "K ", (proc.zswap() / 1024));
+ }
+ }
+ }
+}
+
+static void print_processes(std::stringstream& ss, std::vector<ProcessRecord>& procs,
+ uint16_t* swap_offset_array) {
+ for (auto& proc : procs) {
+ total_pss += show_wss ? proc.Wss().pss : proc.Usage().pss;
+ total_uss += show_wss ? proc.Wss().uss : proc.Usage().uss;
+ if (!show_wss && has_swap) {
+ proc.CalculateSwap(swap_offset_array, zram_compression_ratio);
+ total_swap += proc.Usage().swap;
+ total_pswap += proc.proportional_swap();
+ total_uswap += proc.unique_swap();
+ if (has_zram) {
+ total_zswap += proc.zswap();
+ }
+ }
+
+ print_process_record(ss, proc);
+ ss << proc.cmdline() << std::endl;
+ }
+}
+
+static void print_separator(std::stringstream& ss) {
+ ss << ::android::base::StringPrintf("%5s ", "");
+ if (show_oomadj) {
+ ss << ::android::base::StringPrintf("%5s ", "");
+ }
+
+ if (show_wss) {
+ ss << ::android::base::StringPrintf("%7s %7s %7s ", "", "------", "------");
+ } else {
+ ss << ::android::base::StringPrintf("%8s %7s %7s %7s ", "", "", "------", "------");
+ if (has_swap) {
+ ss << ::android::base::StringPrintf("%7s %7s %7s ", "------", "------", "------");
+ if (has_zram) {
+ ss << ::android::base::StringPrintf("%7s ", "------");
+ }
+ }
+ }
+
+ ss << ::android::base::StringPrintf("%s", "------");
+}
+
+static void print_totals(std::stringstream& ss) {
+ ss << ::android::base::StringPrintf("%5s ", "");
+ if (show_oomadj) {
+ ss << ::android::base::StringPrintf("%5s ", "");
+ }
+
+ if (show_wss) {
+ ss << ::android::base::StringPrintf("%7s %6" PRIu64 "K %6" PRIu64 "K ", "",
+ total_pss / 1024, total_uss / 1024);
+ } else {
+ ss << ::android::base::StringPrintf("%8s %7s %6" PRIu64 "K %6" PRIu64 "K ", "", "",
+ total_pss / 1024, total_uss / 1024);
+ if (has_swap) {
+ ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_swap / 1024);
+ ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_pswap / 1024);
+ ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_uswap / 1024);
+ if (has_zram) {
+ ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_zswap / 1024);
+ }
+ }
+ }
+ ss << "TOTAL";
+}
+
+static void print_sysmeminfo(std::stringstream& ss, ::android::meminfo::SysMemInfo& smi) {
+ if (has_swap) {
+ ss << ::android::base::StringPrintf("ZRAM: %" PRIu64 "K physical used for %" PRIu64
+ "K in swap "
+ "(%" PRIu64 "K total swap)",
+ smi.mem_zram_kb(),
+ (smi.mem_swap_kb() - smi.mem_swap_free_kb()),
+ smi.mem_swap_kb())
+ << std::endl;
+ }
+
+ ss << ::android::base::StringPrintf(" RAM: %" PRIu64 "K total, %" PRIu64 "K free, %" PRIu64
+ "K buffers, "
+ "%" PRIu64 "K cached, %" PRIu64 "K shmem, %" PRIu64
+ "K slab",
+ smi.mem_total_kb(), smi.mem_free_kb(), smi.mem_buffers_kb(),
+ smi.mem_cached_kb(), smi.mem_shmem_kb(), smi.mem_slab_kb());
+}
+
+int main(int argc, char* argv[]) {
+ auto pss_sort = [](ProcessRecord& a, ProcessRecord& b) {
+ MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
+ MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
+ return reverse_sort ? stats_a.pss < stats_b.pss : stats_a.pss > stats_b.pss;
+ };
+
+ auto uss_sort = [](ProcessRecord& a, ProcessRecord& b) {
+ MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
+ MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
+ return reverse_sort ? stats_a.uss < stats_b.uss : stats_a.uss > stats_b.uss;
+ };
+
+ auto rss_sort = [](ProcessRecord& a, ProcessRecord& b) {
+ MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
+ MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
+ return reverse_sort ? stats_a.rss < stats_b.pss : stats_a.pss > stats_b.pss;
+ };
+
+ auto vss_sort = [](ProcessRecord& a, ProcessRecord& b) {
+ MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
+ MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
+ return reverse_sort ? stats_a.vss < stats_b.vss : stats_a.vss > stats_b.vss;
+ };
+
+ auto swap_sort = [](ProcessRecord& a, ProcessRecord& b) {
+ MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
+ MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
+ return reverse_sort ? stats_a.swap < stats_b.swap : stats_a.swap > stats_b.swap;
+ };
+
+ auto oomadj_sort = [](ProcessRecord& a, ProcessRecord& b) {
+ return reverse_sort ? a.oomadj() < b.oomadj() : a.oomadj() > b.oomadj();
+ };
+
+ // default PSS sort
+ std::function<bool(ProcessRecord & a, ProcessRecord & b)> proc_sort = pss_sort;
+
+ // count all pages by default
+ uint64_t pgflags = 0;
+ uint64_t pgflags_mask = 0;
+
+ int opt;
+ while ((opt = getopt(argc, argv, "cChkoprRsuvwW")) != -1) {
+ switch (opt) {
+ case 'c':
+ pgflags = 0;
+ pgflags_mask = (1 << KPF_SWAPBACKED);
+ break;
+ case 'C':
+ pgflags = (1 << KPF_SWAPBACKED);
+ pgflags_mask = (1 << KPF_SWAPBACKED);
+ break;
+ case 'h':
+ usage(argv[0]);
+ return 0;
+ break;
+ case 'k':
+ pgflags = (1 << KPF_KSM);
+ pgflags_mask = (1 << KPF_KSM);
+ break;
+ case 'o':
+ proc_sort = oomadj_sort;
+ show_oomadj = true;
+ break;
+ case 'p':
+ proc_sort = pss_sort;
+ break;
+ case 'r':
+ proc_sort = rss_sort;
+ break;
+ case 'R':
+ reverse_sort = true;
+ break;
+ case 's':
+ proc_sort = swap_sort;
+ break;
+ case 'u':
+ proc_sort = uss_sort;
+ break;
+ case 'v':
+ proc_sort = vss_sort;
+ break;
+ case 'w':
+ show_wss = true;
+ break;
+ case 'W':
+ reset_wss = true;
+ break;
+ default:
+ abort();
+ }
+ }
+
+ std::vector<pid_t> pids;
+ std::vector<ProcessRecord> procs;
+ if (reset_wss) {
+ if (!read_all_pids(&pids,
+ [&](pid_t pid) -> bool { return ProcMemInfo::ResetWorkingSet(pid); })) {
+ std::cerr << "Failed to reset working set of all processes" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ // we are done, all other options passed to procrank are ignored in the presence of '-W'
+ return 0;
+ }
+
+ ::android::meminfo::SysMemInfo smi;
+ if (!smi.ReadMemInfo()) {
+ std::cerr << "Failed to get system memory info" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ // Figure out swap and zram
+ uint64_t swap_total = smi.mem_swap_kb() * 1024;
+ has_swap = swap_total > 0;
+ // Allocate the swap array
+ auto swap_offset_array = std::make_unique<uint16_t[]>(swap_total / getpagesize());
+ if (has_swap) {
+ has_zram = smi.mem_zram_kb() > 0;
+ if (has_zram) {
+ zram_compression_ratio = static_cast<float>(smi.mem_zram_kb()) /
+ (smi.mem_swap_kb() - smi.mem_swap_free_kb());
+ }
+ }
+
+ auto mark_swap_usage = [&](pid_t pid) -> bool {
+ ProcessRecord proc(pid, show_wss, pgflags, pgflags_mask);
+ if (!proc.valid()) {
+ std::cerr << "Failed to create process record for: " << pid << std::endl;
+ return false;
+ }
+
+ // Skip processes with no memory mappings
+ uint64_t vss = show_wss ? proc.Wss().vss : proc.Usage().vss;
+ if (vss == 0) return true;
+
+ // collect swap_offset counts from all processes in 1st pass
+ if (!show_wss && has_swap &&
+ !count_swap_offsets(proc, swap_offset_array.get(), swap_total / getpagesize())) {
+ std::cerr << "Failed to count swap offsets for process: " << pid << std::endl;
+ return false;
+ }
+
+ procs.push_back(std::move(proc));
+ return true;
+ };
+
+ // Get a list of all pids currently running in the system in
+ // 1st pass through all processes. Mark each swap offset used by the process as we find them
+ // for calculating proportional swap usage later.
+ if (!read_all_pids(&pids, mark_swap_usage)) {
+ std::cerr << "Failed to read all pids from the system" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ std::stringstream ss;
+ if (procs.empty()) {
+ // This would happen in corner cases where procrank is being run to find KSM usage on a
+ // system with no KSM and combined with working set determination as follows
+ // procrank -w -u -k
+ // procrank -w -s -k
+ // procrank -w -o -k
+ ss << "<empty>" << std::endl << std::endl;
+ print_sysmeminfo(ss, smi);
+ ss << std::endl;
+ std::cout << ss.str();
+ return 0;
+ }
+
+ // Sort all process records, default is PSS descending
+ std::sort(procs.begin(), procs.end(), proc_sort);
+
+ // start dumping output in string stream
+ print_header(ss);
+ ss << std::endl;
+
+ // 2nd pass to calculate and get per process stats to add them up
+ print_processes(ss, procs, swap_offset_array.get());
+
+ // Add separator to output
+ print_separator(ss);
+ ss << std::endl;
+
+ // Add totals to output
+ print_totals(ss);
+ ss << std::endl << std::endl;
+
+ // Add system information at the end
+ print_sysmeminfo(ss, smi);
+ ss << std::endl;
+
+ // dump on the screen
+ std::cout << ss.str();
+
+ return 0;
+}