| /* |
| * Copyright (C) 2019 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 <getopt.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| #include <meminfo/procmeminfo.h> |
| |
| using ::android::meminfo::Vma; |
| |
| struct VmaInfo { |
| Vma vma; |
| bool is_bss; |
| uint32_t count; |
| |
| VmaInfo() = default; |
| VmaInfo(const Vma& v) : vma(v), is_bss(false), count(1) {} |
| VmaInfo(const Vma& v, bool bss) : vma(v), is_bss(bss), count(1) {} |
| VmaInfo(const Vma& v, const std::string& name, bool bss) : vma(v), is_bss(bss), count(1) { |
| vma.name = name; |
| } |
| }; |
| |
| // Global options |
| static std::string g_filename = ""; |
| static bool g_merge_by_names = false; |
| static bool g_terse = false; |
| static bool g_verbose = false; |
| static bool g_show_addr = false; |
| static bool g_quiet = false; |
| static pid_t g_pid = -1; |
| |
| static VmaInfo g_total; |
| static std::vector<VmaInfo> g_vmas; |
| |
| [[noreturn]] static void usage(int exit_status) { |
| fprintf(stderr, |
| "%s [-aqtv] [-f FILE] PID\n" |
| "-a\taddresses (show virtual memory map)\n" |
| "-q\tquiet (don't show error if map could not be read)\n" |
| "-t\tterse (show only items with private pages)\n" |
| "-v\tverbose (don't coalesce maps with the same name)\n" |
| "-f\tFILE (read from input from FILE instead of PID)\n", |
| getprogname()); |
| |
| exit(exit_status); |
| } |
| |
| static bool is_library(const std::string& name) { |
| return (name.size() > 4) && (name[0] == '/') && ::android::base::EndsWith(name, ".so"); |
| } |
| |
| static bool insert_before(const VmaInfo& a, const VmaInfo& b) { |
| if (g_show_addr) { |
| return (a.vma.start < b.vma.start || (a.vma.start == b.vma.start && a.vma.end < b.vma.end)); |
| } |
| |
| return strcmp(a.vma.name.c_str(), b.vma.name.c_str()) < 0; |
| } |
| |
| static void collect_vma(const Vma& vma) { |
| if (g_vmas.empty()) { |
| g_vmas.emplace_back(vma); |
| return; |
| } |
| |
| VmaInfo current(vma); |
| VmaInfo& last = g_vmas.back(); |
| // determine if this is bss; |
| if (vma.name.empty()) { |
| if (last.vma.end == current.vma.start && is_library(last.vma.name)) { |
| current.vma.name = last.vma.name; |
| current.is_bss = true; |
| } else { |
| current.vma.name = "[anon]"; |
| } |
| } |
| |
| std::vector<VmaInfo>::iterator it; |
| for (it = g_vmas.begin(); it != g_vmas.end(); it++) { |
| if (g_merge_by_names && (it->vma.name == current.vma.name)) { |
| it->vma.usage.vss += current.vma.usage.vss; |
| it->vma.usage.rss += current.vma.usage.rss; |
| it->vma.usage.pss += current.vma.usage.pss; |
| |
| it->vma.usage.shared_clean += current.vma.usage.shared_clean; |
| it->vma.usage.shared_dirty += current.vma.usage.shared_dirty; |
| it->vma.usage.private_clean += current.vma.usage.private_clean; |
| it->vma.usage.private_dirty += current.vma.usage.private_dirty; |
| it->vma.usage.swap += current.vma.usage.swap; |
| it->vma.usage.swap_pss += current.vma.usage.swap_pss; |
| it->is_bss &= current.is_bss; |
| it->count++; |
| break; |
| } |
| |
| if (insert_before(current, *it)) { |
| g_vmas.insert(it, current); |
| break; |
| } |
| } |
| |
| if (it == g_vmas.end()) { |
| g_vmas.emplace_back(current); |
| } |
| } |
| |
| static void print_header() { |
| const char* addr1 = g_show_addr ? " start end " : ""; |
| const char* addr2 = g_show_addr ? " addr addr " : ""; |
| |
| printf("%s virtual shared shared private private\n", addr1); |
| printf("%s size RSS PSS clean dirty clean dirty swap swapPSS", |
| addr2); |
| if (!g_verbose && !g_show_addr) { |
| printf(" # "); |
| } |
| printf(" object\n"); |
| } |
| |
| static void print_divider() { |
| if (g_show_addr) { |
| printf("-------- -------- "); |
| } |
| printf("-------- -------- -------- -------- -------- -------- -------- -------- -------- "); |
| if (!g_verbose && !g_show_addr) { |
| printf("---- "); |
| } |
| printf("------------------------------\n"); |
| } |
| |
| static void print_vmainfo(const VmaInfo& v, bool total) { |
| if (g_show_addr) { |
| if (total) { |
| printf(" "); |
| } else { |
| printf("%16" PRIx64 " %16" PRIx64 " ", v.vma.start, v.vma.end); |
| } |
| } |
| printf("%8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 |
| " %8" PRIu64 " %8" PRIu64 " ", |
| v.vma.usage.vss, v.vma.usage.rss, v.vma.usage.pss, v.vma.usage.shared_clean, |
| v.vma.usage.shared_dirty, v.vma.usage.private_clean, v.vma.usage.private_dirty, |
| v.vma.usage.swap, v.vma.usage.swap_pss); |
| if (!g_verbose && !g_show_addr) { |
| printf("%4" PRIu32 " ", v.count); |
| } |
| } |
| |
| static int showmap(void) { |
| if (!::android::meminfo::ForEachVmaFromFile(g_filename, collect_vma)) { |
| if (!g_quiet) { |
| fprintf(stderr, "Failed to parse file %s\n", g_filename.c_str()); |
| } |
| return 1; |
| } |
| |
| print_header(); |
| print_divider(); |
| |
| for (const auto& v : g_vmas) { |
| g_total.vma.usage.vss += v.vma.usage.vss; |
| g_total.vma.usage.rss += v.vma.usage.rss; |
| g_total.vma.usage.pss += v.vma.usage.pss; |
| |
| g_total.vma.usage.private_clean += v.vma.usage.private_clean; |
| g_total.vma.usage.private_dirty += v.vma.usage.private_dirty; |
| g_total.vma.usage.shared_clean += v.vma.usage.shared_clean; |
| g_total.vma.usage.shared_dirty += v.vma.usage.shared_dirty; |
| |
| g_total.vma.usage.swap += v.vma.usage.swap; |
| g_total.vma.usage.swap_pss += v.vma.usage.swap_pss; |
| g_total.count += v.count; |
| |
| if (g_terse && !(v.vma.usage.private_dirty || v.vma.usage.private_clean)) { |
| continue; |
| } |
| |
| print_vmainfo(v, false); |
| printf("%s%s\n", v.vma.name.c_str(), v.is_bss ? " [bss]" : ""); |
| } |
| |
| print_divider(); |
| print_header(); |
| print_divider(); |
| |
| print_vmainfo(g_total, true); |
| printf("TOTAL\n"); |
| |
| return 0; |
| } |
| |
| int main(int argc, char* argv[]) { |
| signal(SIGPIPE, SIG_IGN); |
| struct option longopts[] = { |
| {"help", no_argument, nullptr, 'h'}, |
| {0, 0, nullptr, 0}, |
| }; |
| |
| int opt; |
| while ((opt = getopt_long(argc, argv, "tvaqf:h", longopts, nullptr)) != -1) { |
| switch (opt) { |
| case 't': |
| g_terse = true; |
| break; |
| case 'a': |
| g_show_addr = true; |
| break; |
| case 'v': |
| g_verbose = true; |
| break; |
| case 'q': |
| g_quiet = true; |
| break; |
| case 'f': |
| g_filename = optarg; |
| break; |
| case 'h': |
| usage(EXIT_SUCCESS); |
| default: |
| usage(EXIT_FAILURE); |
| } |
| } |
| |
| if (g_filename.empty()) { |
| if ((argc - 1) < optind) { |
| fprintf(stderr, "Invalid arguments: Must provide <pid> at the end\n"); |
| usage(EXIT_FAILURE); |
| } |
| |
| g_pid = atoi(argv[optind]); |
| if (g_pid <= 0) { |
| fprintf(stderr, "Invalid process id %s\n", argv[optind]); |
| usage(EXIT_FAILURE); |
| } |
| |
| g_filename = ::android::base::StringPrintf("/proc/%d/smaps", g_pid); |
| } |
| |
| g_merge_by_names = !g_verbose && !g_show_addr; |
| return showmap(); |
| } |