From 2aeaaebc491939cbb5cf3b2dea6399e491dd4daa Mon Sep 17 00:00:00 2001 From: Sandeep Patil Date: Fri, 23 Nov 2018 00:13:16 -0800 Subject: [PATCH] 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 --- libmeminfo/tools/Android.bp | 16 +- libmeminfo/tools/librank.cpp | 350 +++++++++++++++++++++++++++++++++++ 2 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 libmeminfo/tools/librank.cpp diff --git a/libmeminfo/tools/Android.bp b/libmeminfo/tools/Android.bp index 715d63df5..1f934947b 100644 --- a/libmeminfo/tools/Android.bp +++ b/libmeminfo/tools/Android.bp @@ -12,12 +12,25 @@ // See the License for the specific language governing permissions and // 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 @@ cc_binary { 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 000000000..76686408a --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +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/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& 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& sorter) { + std::sort(procs_.begin(), procs_.end(), sorter); + } + + private: + std::string name_; + MemUsage usage_; + std::vector procs_; +}; + +// List of every library / map +static std::vector g_libs; + +// List of library/map names that we don't want to show by default +static const std::vector 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 for_each_pid) { + std::unique_ptr 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 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 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; +}