| /* |
| * 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 <map> |
| #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(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", |
| getprogname()); |
| 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::map<pid_t, ProcessRecord>& processes() const { return procs_; } |
| uint64_t pss() const { return usage_.pss; } |
| void AddUsage(const ProcessRecord& proc, const MemUsage& mem_usage) { |
| auto [it, inserted] = procs_.insert(std::pair<pid_t, ProcessRecord>(proc.pid(), proc)); |
| it->second.AddUsage(mem_usage); |
| add_mem_usage(&usage_, mem_usage); |
| } |
| |
| private: |
| std::string name_; |
| MemUsage usage_; |
| std::map<pid_t, ProcessRecord> procs_; |
| }; |
| |
| // List of every library / map |
| static std::map<std::string, 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 [it, inserted] = |
| g_libs.insert(std::pair<std::string, LibRecord>(map.name, LibRecord(map.name))); |
| it->second.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(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(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"); |
| |
| std::vector<LibRecord> v_libs; |
| v_libs.reserve(g_libs.size()); |
| std::transform(g_libs.begin(), g_libs.end(), std::back_inserter(v_libs), |
| [] (std::pair<std::string, LibRecord> const& pair) { return pair.second; }); |
| |
| // sort the libraries by their pss |
| std::sort(v_libs.begin(), v_libs.end(), |
| [](const LibRecord& l1, const LibRecord& l2) { return l1.pss() > l2.pss(); }); |
| |
| for (auto& lib : v_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 |
| |
| std::vector<ProcessRecord> procs; |
| procs.reserve(lib.processes().size()); |
| std::transform(lib.processes().begin(), lib.processes().end(), std::back_inserter(procs), |
| [] (std::pair<pid_t, ProcessRecord> const& pair) { return pair.second; }); |
| |
| std::sort(procs.begin(), procs.end(), sort_func); |
| |
| for (auto& p : procs) { |
| 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; |
| } |