Add librank2
Identical with librank, but written using libmeminfo.
Bug: 114325007
Bug: 111694435
Test: librank2
Test: librank2 -m rx
Test: librank2 -m rx -P /system/lib64
Test: librank2 -u -P /system/lib
Test: librank2 -R -m rx
Change-Id: Ib6ecb601a0b6ecb5b4b75285f772586e05ae388a
Signed-off-by: Sandeep Patil <sspatil@google.com>
diff --git a/libmeminfo/tools/Android.bp b/libmeminfo/tools/Android.bp
index 715d63d..1f93494 100644
--- a/libmeminfo/tools/Android.bp
+++ b/libmeminfo/tools/Android.bp
@@ -13,11 +13,24 @@
// limitations under the License.
cc_binary {
+ name: "librank2",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ srcs: ["librank.cpp"],
+ shared_libs: [
+ "libbase",
+ "libmeminfo",
+ ],
+}
+
+cc_binary {
name: "procmem2",
cflags: [
"-Wall",
"-Werror",
- "-Wno-unused-parameter",
],
srcs: ["procmem.cpp"],
@@ -32,7 +45,6 @@
cflags: [
"-Wall",
"-Werror",
- "-Wno-unused-parameter",
],
srcs: ["procrank.cpp"],
diff --git a/libmeminfo/tools/librank.cpp b/libmeminfo/tools/librank.cpp
new file mode 100644
index 0000000..7668640
--- /dev/null
+++ b/libmeminfo/tools/librank.cpp
@@ -0,0 +1,350 @@
+/*
+ * 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 <error.h>
+#include <inttypes.h>
+#include <linux/kernel-page-flags.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#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>
+
+using ::android::meminfo::MemUsage;
+using ::android::meminfo::ProcMemInfo;
+using ::android::meminfo::Vma;
+
+[[noreturn]] static void usage(const char* myname, int exit_status) {
+ fprintf(stderr,
+ "Usage: %s [ -P | -L ] [ -v | -r | -p | -u | -s | -h ]\n"
+ "\n"
+ "Sort options:\n"
+ " -v Sort processes by VSS.\n"
+ " -r Sort processes by RSS.\n"
+ " -p Sort processes by PSS.\n"
+ " -u Sort processes by USS.\n"
+ " -s Sort processes by swap.\n"
+ " (Default sort order is PSS.)\n"
+ " -a Show all mappings, including stack, heap and anon.\n"
+ " -P /path Limit libraries displayed to those in path.\n"
+ " -R Reverse sort order (default is descending).\n"
+ " -m [r][w][x] Only list pages that exactly match permissions\n"
+ " -c Only show cached (storage backed) pages\n"
+ " -C Only show non-cached (ram/swap backed) pages\n"
+ " -k Only show pages collapsed by KSM\n"
+ " -h Display this help screen.\n",
+ myname);
+ exit(exit_status);
+}
+
+static void add_mem_usage(MemUsage* to, const MemUsage& from) {
+ to->vss += from.vss;
+ to->rss += from.rss;
+ to->pss += from.pss;
+ to->uss += from.uss;
+
+ to->swap += from.swap;
+
+ to->private_clean += from.private_clean;
+ to->private_dirty += from.private_dirty;
+
+ to->shared_clean += from.shared_clean;
+ to->shared_dirty += from.shared_dirty;
+}
+
+struct ProcessRecord {
+ public:
+ ProcessRecord(pid_t pid) : pid_(-1), cmdline_("") {
+ std::string fname = ::android::base::StringPrintf("/proc/%d/cmdline", pid);
+ std::string cmdline;
+ if (!::android::base::ReadFileToString(fname, &cmdline)) {
+ fprintf(stderr, "Failed to read cmdline from: %s\n", fname.c_str());
+ return;
+ }
+ // 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_ = cmdline.c_str();
+ pid_ = pid;
+ usage_.clear();
+ }
+
+ ~ProcessRecord() = default;
+
+ bool valid() const { return pid_ != -1; }
+
+ // Getters
+ pid_t pid() const { return pid_; }
+ const std::string& cmdline() const { return cmdline_; }
+ const MemUsage& usage() const { return usage_; }
+
+ // Add to the usage
+ void AddUsage(const MemUsage& mem_usage) { add_mem_usage(&usage_, mem_usage); }
+
+ private:
+ pid_t pid_;
+ std::string cmdline_;
+ MemUsage usage_;
+};
+
+struct LibRecord {
+ public:
+ LibRecord(const std::string& name) : name_(name) {}
+ ~LibRecord() = default;
+
+ const std::string& name() const { return name_; }
+ const MemUsage& usage() const { return usage_; }
+ const std::vector<ProcessRecord>& processes() const { return procs_; }
+ uint64_t pss() const { return usage_.pss; }
+ void AddUsage(const ProcessRecord& proc, const MemUsage& mem_usage) {
+ auto process = std::find_if(procs_.begin(), procs_.end(),
+ [&](auto p) -> bool { return p.pid() == proc.pid(); });
+ if (process == procs_.end()) {
+ process = procs_.emplace(procs_.end(), proc.pid());
+ }
+ process->AddUsage(mem_usage);
+ add_mem_usage(&usage_, mem_usage);
+ }
+
+ void Sort(std::function<bool(const ProcessRecord&, const ProcessRecord&)>& sorter) {
+ std::sort(procs_.begin(), procs_.end(), sorter);
+ }
+
+ private:
+ std::string name_;
+ MemUsage usage_;
+ std::vector<ProcessRecord> procs_;
+};
+
+// List of every library / map
+static std::vector<LibRecord> g_libs;
+
+// List of library/map names that we don't want to show by default
+static const std::vector<std::string> g_blacklisted_libs = {"[heap]", "[stack]"};
+
+// Global flags affected by command line
+static uint64_t g_pgflags = 0;
+static uint64_t g_pgflags_mask = 0;
+static uint16_t g_mapflags_mask = 0;
+static bool g_all_libs = false;
+static bool g_has_swap = false;
+static bool g_reverse_sort = false;
+static std::string g_prefix_filter = "";
+
+static bool read_all_pids(std::function<bool(pid_t pid)> for_each_pid) {
+ 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;
+ }
+
+ return true;
+}
+
+static bool scan_libs_per_process(pid_t pid) {
+ ProcMemInfo pmem(pid, false, g_pgflags, g_pgflags_mask);
+ const std::vector<Vma> maps = pmem.Maps();
+ if (maps.size() == 0) {
+ // nothing to do here, continue
+ return true;
+ }
+
+ ProcessRecord proc(pid);
+ if (!proc.valid()) {
+ fprintf(stderr, "Failed to create process record for process: %d\n", pid);
+ return false;
+ }
+
+ for (auto& map : maps) {
+ // skip library / map if prefix for the path doesn't match
+ if (!g_prefix_filter.empty() && !::android::base::StartsWith(map.name, g_prefix_filter)) {
+ continue;
+ }
+ // Skip maps based on map permissions
+ if (g_mapflags_mask &&
+ ((map.flags & (PROT_READ | PROT_WRITE | PROT_EXEC)) != g_mapflags_mask)) {
+ continue;
+ }
+
+ // skip blacklisted library / map names
+ if (!g_all_libs && (std::find(g_blacklisted_libs.begin(), g_blacklisted_libs.end(),
+ map.name) != g_blacklisted_libs.end())) {
+ continue;
+ }
+
+ auto lib = std::find_if(g_libs.begin(), g_libs.end(),
+ [&](auto l) -> bool { return map.name == l.name(); });
+ if (lib == g_libs.end()) {
+ lib = g_libs.emplace(g_libs.end(), map.name);
+ }
+
+ lib->AddUsage(proc, map.usage);
+ if (!g_has_swap && map.usage.swap) {
+ g_has_swap = true;
+ }
+ }
+
+ return true;
+}
+
+static uint16_t parse_mapflags(const char* mapflags) {
+ uint16_t ret = 0;
+ for (const char* p = mapflags; *p; p++) {
+ switch (*p) {
+ case 'r':
+ ret |= PROT_READ;
+ break;
+ case 'w':
+ ret |= PROT_WRITE;
+ break;
+ case 'x':
+ ret |= PROT_EXEC;
+ break;
+ default:
+ error(EXIT_FAILURE, 0, "Invalid permissions string: %s, %s", mapflags, p);
+ }
+ }
+
+ return ret;
+}
+
+int main(int argc, char* argv[]) {
+ int opt;
+
+ auto pss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
+ return g_reverse_sort ? a.usage().pss < b.usage().pss : a.usage().pss > b.usage().pss;
+ };
+
+ auto uss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
+ return g_reverse_sort ? a.usage().uss < b.usage().uss : a.usage().uss > b.usage().uss;
+ };
+
+ auto vss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
+ return g_reverse_sort ? a.usage().vss < b.usage().vss : a.usage().vss > b.usage().vss;
+ };
+
+ auto rss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
+ return g_reverse_sort ? a.usage().rss < b.usage().rss : a.usage().rss > b.usage().rss;
+ };
+
+ auto swap_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
+ return g_reverse_sort ? a.usage().swap < b.usage().swap : a.usage().swap > b.usage().swap;
+ };
+
+ std::function<bool(const ProcessRecord&, const ProcessRecord&)> sort_func = pss_sort;
+
+ while ((opt = getopt(argc, argv, "acChkm:pP:uvrsR")) != -1) {
+ switch (opt) {
+ case 'a':
+ g_all_libs = true;
+ break;
+ case 'c':
+ g_pgflags = 0;
+ g_pgflags_mask = (1 << KPF_SWAPBACKED);
+ break;
+ case 'C':
+ g_pgflags = g_pgflags_mask = (1 << KPF_SWAPBACKED);
+ break;
+ case 'h':
+ usage(argv[0], EXIT_SUCCESS);
+ case 'k':
+ g_pgflags = g_pgflags_mask = (1 << KPF_KSM);
+ break;
+ case 'm':
+ g_mapflags_mask = parse_mapflags(optarg);
+ break;
+ case 'p':
+ sort_func = pss_sort;
+ break;
+ case 'P':
+ g_prefix_filter = optarg;
+ break;
+ case 'u':
+ sort_func = uss_sort;
+ break;
+ case 'v':
+ sort_func = vss_sort;
+ break;
+ case 'r':
+ sort_func = rss_sort;
+ break;
+ case 's':
+ sort_func = swap_sort;
+ break;
+ case 'R':
+ g_reverse_sort = true;
+ break;
+ default:
+ usage(argv[0], EXIT_FAILURE);
+ }
+ }
+
+ if (!read_all_pids(scan_libs_per_process)) {
+ error(EXIT_FAILURE, 0, "Failed to read all pids from the system");
+ }
+
+ printf(" %6s %7s %6s %6s %6s ", "RSStot", "VSS", "RSS", "PSS", "USS");
+ if (g_has_swap) {
+ printf(" %6s ", "Swap");
+ }
+ printf("Name/PID\n");
+
+ // sort the libraries by their pss
+ std::sort(g_libs.begin(), g_libs.end(),
+ [](const LibRecord& l1, const LibRecord& l2) { return l1.pss() > l2.pss(); });
+
+ for (auto& lib : g_libs) {
+ printf("%6" PRIu64 "K %7s %6s %6s %6s ", lib.pss() / 1024, "", "", "", "");
+ if (g_has_swap) {
+ printf(" %6s ", "");
+ }
+ printf("%s\n", lib.name().c_str());
+
+ // sort all mappings first
+ lib.Sort(sort_func);
+
+ for (auto& p : lib.processes()) {
+ const MemUsage& usage = p.usage();
+ printf(" %6s %7" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ", "",
+ usage.vss / 1024, usage.rss / 1024, usage.pss / 1024, usage.uss / 1024);
+ if (g_has_swap) {
+ printf("%6" PRIu64 "K ", usage.swap / 1024);
+ }
+ printf(" %s [%d]\n", p.cmdline().c_str(), p.pid());
+ }
+ }
+
+ return 0;
+}