diff --git a/libmeminfo/include/meminfo/meminfo.h b/libmeminfo/include/meminfo/meminfo.h index 21e0af4af..5ee32d45f 100644 --- a/libmeminfo/include/meminfo/meminfo.h +++ b/libmeminfo/include/meminfo/meminfo.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include @@ -66,10 +67,16 @@ struct Vma { uint16_t flags; std::string name; + Vma() : start(0), end(0), offset(0), flags(0), name("") {} Vma(uint64_t s, uint64_t e, uint64_t off, uint16_t f, const char* n) : start(s), end(e), offset(off), flags(f), name(n) {} ~Vma() = default; + void clear() { + memset(&usage, 0, sizeof(usage)); + memset(&wss, 0, sizeof(wss)); + } + // Memory usage of this mapping. MemUsage usage; // Working set within this mapping. diff --git a/libmeminfo/include/meminfo/procmeminfo.h b/libmeminfo/include/meminfo/procmeminfo.h index 4aafd7713..0bfd80f2f 100644 --- a/libmeminfo/include/meminfo/procmeminfo.h +++ b/libmeminfo/include/meminfo/procmeminfo.h @@ -26,6 +26,8 @@ namespace android { namespace meminfo { +using VmaCallback = std::function; + class ProcMemInfo final { // Per-process memory accounting public: @@ -38,6 +40,18 @@ class ProcMemInfo final { const MemUsage& Usage(); const MemUsage& Wss(); + // Collect all 'vma' or 'maps' from /proc//smaps and store them in 'maps_'. Returns a + // constant reference to the vma vector after the collection is done. + // + // Each 'struct Vma' is *fully* populated by this method (unlike SmapsOrRollup). + const std::vector& Smaps(const std::string& path = ""); + + // This method reads /proc//smaps and calls the callback() for each + // vma or map that it finds. The map is converted to 'struct Vma' object which is then + // passed to the callback. + // Returns 'false' if the file is malformed. + bool ForEachVma(const VmaCallback& callback); + // Used to parse either of /proc//{smaps, smaps_rollup} and record the process's // Pss and Private memory usage in 'stats'. In particular, the method only populates the fields // of the MemUsage structure that are intended to be used by Android's periodic Pss collection. @@ -49,7 +63,6 @@ class ProcMemInfo final { // private_clean // private_dirty // SwapPss - // // All other fields of MemUsage are zeroed. bool SmapsOrRollup(bool use_rollup, MemUsage* stats) const; @@ -73,6 +86,10 @@ class ProcMemInfo final { std::vector swap_offsets_; }; +// Makes callback for each 'vma' or 'map' found in file provided. The file is expected to be in the +// same format as /proc//smaps. Returns 'false' if the file is malformed. +bool ForEachVmaFromFile(const std::string& path, const VmaCallback& callback); + // Same as ProcMemInfo::SmapsOrRollup but reads the statistics directly // from a file. The file MUST be in the same format as /proc//smaps // or /proc//smaps_rollup diff --git a/libmeminfo/libmeminfo_test.cpp b/libmeminfo/libmeminfo_test.cpp index d5252e9d5..20ed3bf41 100644 --- a/libmeminfo/libmeminfo_test.cpp +++ b/libmeminfo/libmeminfo_test.cpp @@ -365,6 +365,191 @@ VmFlags: rd wr mr mw me ac EXPECT_EQ(stats.swap_pss, 70); } +TEST(TestProcMemInfo, ForEachVmaFromFileTest) { + std::string exec_dir = ::android::base::GetExecutableDirectory(); + std::string path = ::android::base::StringPrintf("%s/testdata1/smaps_short", exec_dir.c_str()); + ProcMemInfo proc_mem(pid); + + std::vector vmas; + auto collect_vmas = [&](const Vma& v) { vmas.push_back(v); }; + ASSERT_TRUE(ForEachVmaFromFile(path, collect_vmas)); + + // Expect values to be equal to what we have in testdata1/smaps_short + // Check for sizes first + ASSERT_EQ(vmas[0].usage.vss, 32768); + EXPECT_EQ(vmas[1].usage.vss, 11204); + EXPECT_EQ(vmas[2].usage.vss, 16896); + EXPECT_EQ(vmas[3].usage.vss, 260); + EXPECT_EQ(vmas[4].usage.vss, 6060); + EXPECT_EQ(vmas[5].usage.vss, 4); + + // Check for names + EXPECT_EQ(vmas[0].name, "[anon:dalvik-zygote-jit-code-cache]"); + EXPECT_EQ(vmas[1].name, "/system/framework/x86_64/boot-framework.art"); + EXPECT_EQ(vmas[2].name, "[anon:libc_malloc]"); + EXPECT_EQ(vmas[3].name, "/system/priv-app/SettingsProvider/oat/x86_64/SettingsProvider.odex"); + EXPECT_EQ(vmas[4].name, "/system/lib64/libhwui.so"); + EXPECT_EQ(vmas[5].name, "[vsyscall]"); + + EXPECT_EQ(vmas[0].usage.rss, 2048); + EXPECT_EQ(vmas[1].usage.rss, 11188); + EXPECT_EQ(vmas[2].usage.rss, 15272); + EXPECT_EQ(vmas[3].usage.rss, 260); + EXPECT_EQ(vmas[4].usage.rss, 4132); + EXPECT_EQ(vmas[5].usage.rss, 0); + + EXPECT_EQ(vmas[0].usage.pss, 113); + EXPECT_EQ(vmas[1].usage.pss, 2200); + EXPECT_EQ(vmas[2].usage.pss, 15272); + EXPECT_EQ(vmas[3].usage.pss, 260); + EXPECT_EQ(vmas[4].usage.pss, 1274); + EXPECT_EQ(vmas[5].usage.pss, 0); + + EXPECT_EQ(vmas[0].usage.uss, 0); + EXPECT_EQ(vmas[1].usage.uss, 1660); + EXPECT_EQ(vmas[2].usage.uss, 15272); + EXPECT_EQ(vmas[3].usage.uss, 260); + EXPECT_EQ(vmas[4].usage.uss, 0); + EXPECT_EQ(vmas[5].usage.uss, 0); + + EXPECT_EQ(vmas[0].usage.private_clean, 0); + EXPECT_EQ(vmas[1].usage.private_clean, 0); + EXPECT_EQ(vmas[2].usage.private_clean, 0); + EXPECT_EQ(vmas[3].usage.private_clean, 260); + EXPECT_EQ(vmas[4].usage.private_clean, 0); + EXPECT_EQ(vmas[5].usage.private_clean, 0); + + EXPECT_EQ(vmas[0].usage.private_dirty, 0); + EXPECT_EQ(vmas[1].usage.private_dirty, 1660); + EXPECT_EQ(vmas[2].usage.private_dirty, 15272); + EXPECT_EQ(vmas[3].usage.private_dirty, 0); + EXPECT_EQ(vmas[4].usage.private_dirty, 0); + EXPECT_EQ(vmas[5].usage.private_dirty, 0); + + EXPECT_EQ(vmas[0].usage.shared_clean, 0); + EXPECT_EQ(vmas[1].usage.shared_clean, 80); + EXPECT_EQ(vmas[2].usage.shared_clean, 0); + EXPECT_EQ(vmas[3].usage.shared_clean, 0); + EXPECT_EQ(vmas[4].usage.shared_clean, 4132); + EXPECT_EQ(vmas[5].usage.shared_clean, 0); + + EXPECT_EQ(vmas[0].usage.shared_dirty, 2048); + EXPECT_EQ(vmas[1].usage.shared_dirty, 9448); + EXPECT_EQ(vmas[2].usage.shared_dirty, 0); + EXPECT_EQ(vmas[3].usage.shared_dirty, 0); + EXPECT_EQ(vmas[4].usage.shared_dirty, 0); + EXPECT_EQ(vmas[5].usage.shared_dirty, 0); + + EXPECT_EQ(vmas[0].usage.swap, 0); + EXPECT_EQ(vmas[1].usage.swap, 0); + EXPECT_EQ(vmas[2].usage.swap, 0); + EXPECT_EQ(vmas[3].usage.swap, 0); + EXPECT_EQ(vmas[4].usage.swap, 0); + EXPECT_EQ(vmas[5].usage.swap, 0); + + EXPECT_EQ(vmas[0].usage.swap_pss, 0); + EXPECT_EQ(vmas[1].usage.swap_pss, 0); + EXPECT_EQ(vmas[2].usage.swap_pss, 0); + EXPECT_EQ(vmas[3].usage.swap_pss, 0); + EXPECT_EQ(vmas[4].usage.swap_pss, 0); + EXPECT_EQ(vmas[5].usage.swap_pss, 0); +} + +TEST(TestProcMemInfo, SmapsReturnTest) { + ProcMemInfo proc_mem(pid); + auto vmas = proc_mem.Smaps(); + EXPECT_FALSE(vmas.empty()); +} + +TEST(TestProcMemInfo, SmapsTest) { + std::string exec_dir = ::android::base::GetExecutableDirectory(); + std::string path = ::android::base::StringPrintf("%s/testdata1/smaps_short", exec_dir.c_str()); + ProcMemInfo proc_mem(pid); + auto vmas = proc_mem.Smaps(path); + + ASSERT_FALSE(vmas.empty()); + + // Expect values to be equal to what we have in testdata1/smaps_short + // Check for sizes first + ASSERT_EQ(vmas[0].usage.vss, 32768); + EXPECT_EQ(vmas[1].usage.vss, 11204); + EXPECT_EQ(vmas[2].usage.vss, 16896); + EXPECT_EQ(vmas[3].usage.vss, 260); + EXPECT_EQ(vmas[4].usage.vss, 6060); + EXPECT_EQ(vmas[5].usage.vss, 4); + + // Check for names + EXPECT_EQ(vmas[0].name, "[anon:dalvik-zygote-jit-code-cache]"); + EXPECT_EQ(vmas[1].name, "/system/framework/x86_64/boot-framework.art"); + EXPECT_EQ(vmas[2].name, "[anon:libc_malloc]"); + EXPECT_EQ(vmas[3].name, "/system/priv-app/SettingsProvider/oat/x86_64/SettingsProvider.odex"); + EXPECT_EQ(vmas[4].name, "/system/lib64/libhwui.so"); + EXPECT_EQ(vmas[5].name, "[vsyscall]"); + + EXPECT_EQ(vmas[0].usage.rss, 2048); + EXPECT_EQ(vmas[1].usage.rss, 11188); + EXPECT_EQ(vmas[2].usage.rss, 15272); + EXPECT_EQ(vmas[3].usage.rss, 260); + EXPECT_EQ(vmas[4].usage.rss, 4132); + EXPECT_EQ(vmas[5].usage.rss, 0); + + EXPECT_EQ(vmas[0].usage.pss, 113); + EXPECT_EQ(vmas[1].usage.pss, 2200); + EXPECT_EQ(vmas[2].usage.pss, 15272); + EXPECT_EQ(vmas[3].usage.pss, 260); + EXPECT_EQ(vmas[4].usage.pss, 1274); + EXPECT_EQ(vmas[5].usage.pss, 0); + + EXPECT_EQ(vmas[0].usage.uss, 0); + EXPECT_EQ(vmas[1].usage.uss, 1660); + EXPECT_EQ(vmas[2].usage.uss, 15272); + EXPECT_EQ(vmas[3].usage.uss, 260); + EXPECT_EQ(vmas[4].usage.uss, 0); + EXPECT_EQ(vmas[5].usage.uss, 0); + + EXPECT_EQ(vmas[0].usage.private_clean, 0); + EXPECT_EQ(vmas[1].usage.private_clean, 0); + EXPECT_EQ(vmas[2].usage.private_clean, 0); + EXPECT_EQ(vmas[3].usage.private_clean, 260); + EXPECT_EQ(vmas[4].usage.private_clean, 0); + EXPECT_EQ(vmas[5].usage.private_clean, 0); + + EXPECT_EQ(vmas[0].usage.private_dirty, 0); + EXPECT_EQ(vmas[1].usage.private_dirty, 1660); + EXPECT_EQ(vmas[2].usage.private_dirty, 15272); + EXPECT_EQ(vmas[3].usage.private_dirty, 0); + EXPECT_EQ(vmas[4].usage.private_dirty, 0); + EXPECT_EQ(vmas[5].usage.private_dirty, 0); + + EXPECT_EQ(vmas[0].usage.shared_clean, 0); + EXPECT_EQ(vmas[1].usage.shared_clean, 80); + EXPECT_EQ(vmas[2].usage.shared_clean, 0); + EXPECT_EQ(vmas[3].usage.shared_clean, 0); + EXPECT_EQ(vmas[4].usage.shared_clean, 4132); + EXPECT_EQ(vmas[5].usage.shared_clean, 0); + + EXPECT_EQ(vmas[0].usage.shared_dirty, 2048); + EXPECT_EQ(vmas[1].usage.shared_dirty, 9448); + EXPECT_EQ(vmas[2].usage.shared_dirty, 0); + EXPECT_EQ(vmas[3].usage.shared_dirty, 0); + EXPECT_EQ(vmas[4].usage.shared_dirty, 0); + EXPECT_EQ(vmas[5].usage.shared_dirty, 0); + + EXPECT_EQ(vmas[0].usage.swap, 0); + EXPECT_EQ(vmas[1].usage.swap, 0); + EXPECT_EQ(vmas[2].usage.swap, 0); + EXPECT_EQ(vmas[3].usage.swap, 0); + EXPECT_EQ(vmas[4].usage.swap, 0); + EXPECT_EQ(vmas[5].usage.swap, 0); + + EXPECT_EQ(vmas[0].usage.swap_pss, 0); + EXPECT_EQ(vmas[1].usage.swap_pss, 0); + EXPECT_EQ(vmas[2].usage.swap_pss, 0); + EXPECT_EQ(vmas[3].usage.swap_pss, 0); + EXPECT_EQ(vmas[4].usage.swap_pss, 0); + EXPECT_EQ(vmas[5].usage.swap_pss, 0); +} + TEST(ValidateProcMemInfoFlags, TestPageFlags1) { // Create proc object using libpagemap pm_kernel_t* ker; diff --git a/libmeminfo/procmeminfo.cpp b/libmeminfo/procmeminfo.cpp index 1daff1ef2..1f8db1a04 100644 --- a/libmeminfo/procmeminfo.cpp +++ b/libmeminfo/procmeminfo.cpp @@ -54,6 +54,51 @@ static void add_mem_usage(MemUsage* to, const MemUsage& from) { to->shared_dirty += from.shared_dirty; } +// Returns true if the line was valid smaps stats line false otherwise. +static bool parse_smaps_field(const char* line, MemUsage* stats) { + char field[64]; + int len; + if (sscanf(line, "%63s %n", field, &len) == 1 && *field && field[strlen(field) - 1] == ':') { + const char* c = line + len; + switch (field[0]) { + case 'P': + if (strncmp(field, "Pss:", 4) == 0) { + stats->pss = strtoull(c, nullptr, 10); + } else if (strncmp(field, "Private_Clean:", 14) == 0) { + uint64_t prcl = strtoull(c, nullptr, 10); + stats->private_clean = prcl; + stats->uss += prcl; + } else if (strncmp(field, "Private_Dirty:", 14) == 0) { + uint64_t prdi = strtoull(c, nullptr, 10); + stats->private_dirty = prdi; + stats->uss += prdi; + } + break; + case 'S': + if (strncmp(field, "Size:", 5) == 0) { + stats->vss = strtoull(c, nullptr, 10); + } else if (strncmp(field, "Shared_Clean:", 13) == 0) { + stats->shared_clean = strtoull(c, nullptr, 10); + } else if (strncmp(field, "Shared_Dirty:", 13) == 0) { + stats->shared_dirty = strtoull(c, nullptr, 10); + } else if (strncmp(field, "Swap:", 5) == 0) { + stats->swap = strtoull(c, nullptr, 10); + } else if (strncmp(field, "SwapPss:", 8) == 0) { + stats->swap_pss = strtoull(c, nullptr, 10); + } + break; + case 'R': + if (strncmp(field, "Rss:", 4) == 0) { + stats->rss = strtoull(c, nullptr, 10); + } + break; + } + return true; + } + + return false; +} + bool ProcMemInfo::ResetWorkingSet(pid_t pid) { std::string clear_refs_path = ::android::base::StringPrintf("/proc/%d/clear_refs", pid); if (!::android::base::WriteStringToFile("1\n", clear_refs_path)) { @@ -75,6 +120,25 @@ const std::vector& ProcMemInfo::Maps() { return maps_; } +const std::vector& ProcMemInfo::Smaps(const std::string& path) { + if (!maps_.empty()) { + return maps_; + } + + auto collect_vmas = [&](const Vma& vma) { maps_.emplace_back(vma); }; + if (path.empty() && !ForEachVma(collect_vmas)) { + LOG(ERROR) << "Failed to read smaps for Process " << pid_; + maps_.clear(); + } + + if (!path.empty() && !ForEachVmaFromFile(path, collect_vmas)) { + LOG(ERROR) << "Failed to read smaps from file " << path; + maps_.clear(); + } + + return maps_; +} + const MemUsage& ProcMemInfo::Usage() { if (get_wss_) { LOG(WARNING) << "Trying to read process memory usage for " << pid_ @@ -103,6 +167,11 @@ const MemUsage& ProcMemInfo::Wss() { return wss_; } +bool ProcMemInfo::ForEachVma(const VmaCallback& callback) { + std::string path = ::android::base::StringPrintf("/proc/%d/smaps", pid_); + return ForEachVmaFromFile(path, callback); +} + bool ProcMemInfo::SmapsOrRollup(bool use_rollup, MemUsage* stats) const { std::string path = ::android::base::StringPrintf("/proc/%d/%s", pid_, use_rollup ? "smaps_rollup" : "smaps"); @@ -260,6 +329,59 @@ bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) { } // Public APIs +bool ForEachVmaFromFile(const std::string& path, const VmaCallback& callback) { + auto fp = std::unique_ptr{fopen(path.c_str(), "re"), fclose}; + if (fp == nullptr) { + return false; + } + + char* line = nullptr; + bool parsing_vma = false; + ssize_t line_len; + Vma vma; + while ((line_len = getline(&line, 0, fp.get())) > 0) { + // Make sure the line buffer terminates like a C string for ReadMapFile + line[line_len] = '\0'; + + if (parsing_vma) { + if (parse_smaps_field(line, &vma.usage)) { + // This was a stats field + continue; + } + + // Done collecting stats, make the call back + callback(vma); + parsing_vma = false; + } + + vma.clear(); + // If it has, we are looking for the vma stats + // 00400000-00409000 r-xp 00000000 fc:00 426998 /usr/lib/gvfs/gvfsd-http + if (!::android::procinfo::ReadMapFileContent( + line, [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, + const char* name) { + vma.start = start; + vma.end = end; + vma.flags = flags; + vma.offset = pgoff; + vma.name = name; + })) { + LOG(ERROR) << "Failed to parse " << path; + return false; + } + parsing_vma = true; + } + + // free getline() managed buffer + free(line); + + if (parsing_vma) { + callback(vma); + } + + return true; +} + bool SmapsOrRollupFromFile(const std::string& path, MemUsage* stats) { auto fp = std::unique_ptr{fopen(path.c_str(), "re"), fclose}; if (fp == nullptr) { diff --git a/libmeminfo/testdata1/showmap_test.sh b/libmeminfo/testdata1/showmap_test.sh new file mode 100755 index 000000000..8ee713ead --- /dev/null +++ b/libmeminfo/testdata1/showmap_test.sh @@ -0,0 +1,86 @@ +#! /system/bin/sh + +TESTDATA_PATH=/data/nativetest64/libmeminfo_test/testdata1 +SMAPS=$TESTDATA_PATH/smaps +OUT1=$TMPDIR/1.txt +OUT2=$TMPDIR/2.txt + +showmap -f $SMAPS > $OUT1 +showmap2 -f $SMAPS > $OUT2 +diff $OUT1 $OUT2 > /dev/null +ret=$? +if [[ $ret != 0 ]]; then + echo "fail: showmap -f "; +else + echo "pass: showmap -f " +fi + +showmap -q -f $SMAPS > $OUT1 +showmap2 -q -f $SMAPS > $OUT2 +diff $OUT1 $OUT2 > /dev/null +ret=$? +if [[ $ret != 0 ]]; then + echo "fail: showmap -q -f "; +else + echo "pass: showmap -q -f " +fi + +showmap -v -f $SMAPS > $OUT1 +showmap2 -v -f $SMAPS > $OUT2 +diff $OUT1 $OUT2 > /dev/null +ret=$? +if [[ $ret != 0 ]]; then + echo "fail: showmap -v -f "; +else + echo "pass: showmap -v -f " +fi + +showmap -a -f $SMAPS > $OUT1 +showmap2 -a -f $SMAPS > $OUT2 +diff $OUT1 $OUT2 > /dev/null +ret=$? +if [[ $ret != 0 ]]; then + echo "fail: showmap -a -f "; +else + echo "pass: showmap -a -f " +fi + +# Note that all tests from here down that have the option +# '-a' added to the command are expected to fail as +# 'showmap2' actually fixes the 64-bit address truncating +# that was already happening with showmap +showmap -a -t -f $SMAPS > $OUT1 +showmap2 -a -t -f $SMAPS > $OUT2 +diff $OUT1 $OUT2 > /dev/null +ret=$? +if [[ $ret != 0 ]]; then + echo "fail: showmap -a -t -f "; +else + echo "pass: showmap -a -t -f " +fi + +showmap -a -t -v -f $SMAPS > $OUT1 +showmap2 -a -t -v -f $SMAPS > $OUT2 +diff $OUT1 $OUT2 > /dev/null +ret=$? +if [[ $ret != 0 ]]; then + echo "fail: showmap -a -t -v -f "; +else + echo "pass: showmap -a -t -v -f " +fi + +# Note: This test again is expected to fail as the new +# showmap fixes an issue with -t where the tool was only +# showing maps with private dirty pages. The '-t' option was however +# supposed to show all maps that have 'private' pages, clean or dirty. +showmap -t -f $SMAPS > $OUT1 +showmap2 -t -f $SMAPS > $OUT2 +diff $OUT1 $OUT2 > /dev/null +ret=$? +if [[ $ret != 0 ]]; then + echo "fail: showmap -t -f "; +else + echo "pass: showmap -t -f " +fi + + diff --git a/libmeminfo/testdata1/smaps_short b/libmeminfo/testdata1/smaps_short new file mode 100644 index 000000000..cee67b384 --- /dev/null +++ b/libmeminfo/testdata1/smaps_short @@ -0,0 +1,122 @@ +54c00000-56c00000 r-xp 00000000 00:00 0 [anon:dalvik-zygote-jit-code-cache] +Name: [anon:dalvik-zygote-jit-code-cache] +Size: 32768 kB +KernelPageSize: 4 kB +MMUPageSize: 4 kB +Rss: 2048 kB +Pss: 113 kB +Shared_Clean: 0 kB +Shared_Dirty: 2048 kB +Private_Clean: 0 kB +Private_Dirty: 0 kB +Referenced: 2048 kB +Anonymous: 2048 kB +AnonHugePages: 2048 kB +ShmemPmdMapped: 0 kB +Shared_Hugetlb: 0 kB +Private_Hugetlb: 0 kB +Swap: 0 kB +SwapPss: 0 kB +Locked: 113 kB +VmFlags: rd ex mr mw me ac +701ea000-70cdb000 rw-p 00000000 fe:00 3165 /system/framework/x86_64/boot-framework.art +Size: 11204 kB +KernelPageSize: 4 kB +MMUPageSize: 4 kB +Rss: 11188 kB +Pss: 2200 kB +Shared_Clean: 80 kB +Shared_Dirty: 9448 kB +Private_Clean: 0 kB +Private_Dirty: 1660 kB +Referenced: 9892 kB +Anonymous: 11108 kB +AnonHugePages: 0 kB +ShmemPmdMapped: 0 kB +Shared_Hugetlb: 0 kB +Private_Hugetlb: 0 kB +Swap: 0 kB +SwapPss: 0 kB +Locked: 2200 kB +VmFlags: rd wr mr mw me ac +70074dd8d000-70074ee0d000 rw-p 00000000 00:00 0 [anon:libc_malloc] +Name: [anon:libc_malloc] +Size: 16896 kB +KernelPageSize: 4 kB +MMUPageSize: 4 kB +Rss: 15272 kB +Pss: 15272 kB +Shared_Clean: 0 kB +Shared_Dirty: 0 kB +Private_Clean: 0 kB +Private_Dirty: 15272 kB +Referenced: 11156 kB +Anonymous: 15272 kB +AnonHugePages: 6144 kB +ShmemPmdMapped: 0 kB +Shared_Hugetlb: 0 kB +Private_Hugetlb: 0 kB +Swap: 0 kB +SwapPss: 0 kB +Locked: 15272 kB +VmFlags: rd wr mr mw me ac +700755a2d000-700755a6e000 r-xp 00016000 fe:00 1947 /system/priv-app/SettingsProvider/oat/x86_64/SettingsProvider.odex +Size: 260 kB +KernelPageSize: 4 kB +MMUPageSize: 4 kB +Rss: 260 kB +Pss: 260 kB +Shared_Clean: 0 kB +Shared_Dirty: 0 kB +Private_Clean: 260 kB +Private_Dirty: 0 kB +Referenced: 260 kB +Anonymous: 0 kB +AnonHugePages: 0 kB +ShmemPmdMapped: 0 kB +Shared_Hugetlb: 0 kB +Private_Hugetlb: 0 kB +Swap: 0 kB +SwapPss: 0 kB +Locked: 260 kB +VmFlags: rd ex mr mw me +7007f85b0000-7007f8b9b000 r-xp 001ee000 fe:00 1537 /system/lib64/libhwui.so +Size: 6060 kB +KernelPageSize: 4 kB +MMUPageSize: 4 kB +Rss: 4132 kB +Pss: 1274 kB +Shared_Clean: 4132 kB +Shared_Dirty: 0 kB +Private_Clean: 0 kB +Private_Dirty: 0 kB +Referenced: 4132 kB +Anonymous: 0 kB +AnonHugePages: 0 kB +ShmemPmdMapped: 0 kB +Shared_Hugetlb: 0 kB +Private_Hugetlb: 0 kB +Swap: 0 kB +SwapPss: 0 kB +Locked: 1274 kB +VmFlags: rd ex mr mw me +ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] +Size: 4 kB +KernelPageSize: 4 kB +MMUPageSize: 4 kB +Rss: 0 kB +Pss: 0 kB +Shared_Clean: 0 kB +Shared_Dirty: 0 kB +Private_Clean: 0 kB +Private_Dirty: 0 kB +Referenced: 0 kB +Anonymous: 0 kB +AnonHugePages: 0 kB +ShmemPmdMapped: 0 kB +Shared_Hugetlb: 0 kB +Private_Hugetlb: 0 kB +Swap: 0 kB +SwapPss: 0 kB +Locked: 0 kB +VmFlags: rd ex diff --git a/libmeminfo/tools/Android.bp b/libmeminfo/tools/Android.bp index 1f934947b..c852bbba8 100644 --- a/libmeminfo/tools/Android.bp +++ b/libmeminfo/tools/Android.bp @@ -53,3 +53,17 @@ cc_binary { "libmeminfo", ], } + +cc_binary { + name: "showmap2", + cflags: [ + "-Wall", + "-Werror", + ], + + srcs: ["showmap.cpp"], + shared_libs: [ + "libbase", + "libmeminfo", + ], +} diff --git a/libmeminfo/tools/showmap.cpp b/libmeminfo/tools/showmap.cpp new file mode 100644 index 000000000..a80fa7643 --- /dev/null +++ b/libmeminfo/tools/showmap.cpp @@ -0,0 +1,265 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +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 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::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 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(); +}