From 2259fdf7df44bc94cf03b955d9a97b30c5e7c490 Mon Sep 17 00:00:00 2001 From: Sandeep Patil Date: Fri, 9 Nov 2018 16:42:45 -0800 Subject: [PATCH 1/6] libmeminfo: Add support for counting swap pages Adds procmeminfo API to get the vector of swap_offsets if SWAP is enabled on the device. Bug: 114325007 Bug: 111694435 Test: libmeminfo_test 1 Change-Id: If0b52d042749a5bcb2c87aa2cb1595190d4769b1 Signed-off-by: Sandeep Patil --- libmeminfo/include/meminfo/meminfo.h | 5 ++- libmeminfo/include/meminfo/procmeminfo.h | 2 ++ libmeminfo/include/meminfo/sysmeminfo.h | 46 ++++++++++++++++-------- libmeminfo/libmeminfo_test.cpp | 26 +++++++++++++- libmeminfo/procmeminfo.cpp | 13 +++++-- libmeminfo/sysmeminfo.cpp | 12 ++++--- 6 files changed, 80 insertions(+), 24 deletions(-) diff --git a/libmeminfo/include/meminfo/meminfo.h b/libmeminfo/include/meminfo/meminfo.h index c328648f3..809054bd8 100644 --- a/libmeminfo/include/meminfo/meminfo.h +++ b/libmeminfo/include/meminfo/meminfo.h @@ -31,6 +31,8 @@ struct MemUsage { uint64_t pss; uint64_t uss; + uint64_t swap; + uint64_t private_clean; uint64_t private_dirty; uint64_t shared_clean; @@ -41,6 +43,7 @@ struct MemUsage { rss(0), pss(0), uss(0), + swap(0), private_clean(0), private_dirty(0), shared_clean(0), @@ -49,7 +52,7 @@ struct MemUsage { ~MemUsage() = default; void clear() { - vss = rss = pss = uss = 0; + vss = rss = pss = uss = swap = 0; private_clean = private_dirty = shared_clean = shared_dirty = 0; } }; diff --git a/libmeminfo/include/meminfo/procmeminfo.h b/libmeminfo/include/meminfo/procmeminfo.h index b37c56bcc..dd599128d 100644 --- a/libmeminfo/include/meminfo/procmeminfo.h +++ b/libmeminfo/include/meminfo/procmeminfo.h @@ -34,6 +34,7 @@ class ProcMemInfo final { const std::vector& Maps(); const MemUsage& Usage(); const MemUsage& Wss(); + const std::vector& SwapOffsets() const; bool WssReset(); ~ProcMemInfo() = default; @@ -49,6 +50,7 @@ class ProcMemInfo final { MemUsage usage_; MemUsage wss_; + std::vector swap_offsets_; }; } // namespace meminfo diff --git a/libmeminfo/include/meminfo/sysmeminfo.h b/libmeminfo/include/meminfo/sysmeminfo.h index f5e05bd0e..87e14642b 100644 --- a/libmeminfo/include/meminfo/sysmeminfo.h +++ b/libmeminfo/include/meminfo/sysmeminfo.h @@ -28,6 +28,22 @@ namespace meminfo { class SysMemInfo final { // System or Global memory accounting public: + static constexpr const char* kMemTotal = "MemTotal:"; + static constexpr const char* kMemFree = "MemFree:"; + static constexpr const char* kMemBuffers = "Buffers:"; + static constexpr const char* kMemCached = "Cached:"; + static constexpr const char* kMemShmem = "Shmem:"; + static constexpr const char* kMemSlab = "Slab:"; + static constexpr const char* kMemSReclaim = "SReclaimable:"; + static constexpr const char* kMemSUnreclaim = "SUnreclaim:"; + static constexpr const char* kMemSwapTotal = "SwapTotal:"; + static constexpr const char* kMemSwapFree = "SwapFree:"; + static constexpr const char* kMemZram = "Zram:"; + static constexpr const char* kMemMapped = "Mapped:"; + static constexpr const char* kMemVmallocUsed = "VmallocUsed:"; + static constexpr const char* kMemPageTables = "PageTables:"; + static constexpr const char* kMemKernelStack = "KernelStack:"; + static const std::vector kDefaultSysMemInfoTags; SysMemInfo() = default; @@ -38,21 +54,21 @@ class SysMemInfo final { const std::string& path = "/proc/meminfo"); // getters - uint64_t mem_total_kb() { return mem_in_kb_["MemTotal:"]; } - uint64_t mem_free_kb() { return mem_in_kb_["MemFree:"]; } - uint64_t mem_buffers_kb() { return mem_in_kb_["Buffers:"]; } - uint64_t mem_cached_kb() { return mem_in_kb_["Cached:"]; } - uint64_t mem_shmem_kb() { return mem_in_kb_["Shmem:"]; } - uint64_t mem_slab_kb() { return mem_in_kb_["Slab:"]; } - uint64_t mem_slab_reclailmable_kb() { return mem_in_kb_["SReclaimable:"]; } - uint64_t mem_slab_unreclaimable_kb() { return mem_in_kb_["SUnreclaim:"]; } - uint64_t mem_swap_kb() { return mem_in_kb_["SwapTotal:"]; } - uint64_t mem_free_swap_kb() { return mem_in_kb_["SwapFree:"]; } - uint64_t mem_zram_kb() { return mem_in_kb_["Zram:"]; } - uint64_t mem_mapped_kb() { return mem_in_kb_["Mapped:"]; } - uint64_t mem_vmalloc_used_kb() { return mem_in_kb_["VmallocUsed:"]; } - uint64_t mem_page_tables_kb() { return mem_in_kb_["PageTables:"]; } - uint64_t mem_kernel_stack_kb() { return mem_in_kb_["KernelStack:"]; } + uint64_t mem_total_kb() { return mem_in_kb_[kMemTotal]; } + uint64_t mem_free_kb() { return mem_in_kb_[kMemFree]; } + uint64_t mem_buffers_kb() { return mem_in_kb_[kMemBuffers]; } + uint64_t mem_cached_kb() { return mem_in_kb_[kMemCached]; } + uint64_t mem_shmem_kb() { return mem_in_kb_[kMemShmem]; } + uint64_t mem_slab_kb() { return mem_in_kb_[kMemSlab]; } + uint64_t mem_slab_reclailmable_kb() { return mem_in_kb_[kMemSReclaim]; } + uint64_t mem_slab_unreclaimable_kb() { return mem_in_kb_[kMemSUnreclaim]; } + uint64_t mem_swap_kb() { return mem_in_kb_[kMemSwapTotal]; } + uint64_t mem_swap_free_kb() { return mem_in_kb_[kMemSwapFree]; } + uint64_t mem_zram_kb() { return mem_in_kb_[kMemZram]; } + uint64_t mem_mapped_kb() { return mem_in_kb_[kMemMapped]; } + uint64_t mem_vmalloc_used_kb() { return mem_in_kb_[kMemVmallocUsed]; } + uint64_t mem_page_tables_kb() { return mem_in_kb_[kMemPageTables]; } + uint64_t mem_kernel_stack_kb() { return mem_in_kb_[kMemPageTables]; } private: std::map mem_in_kb_; diff --git a/libmeminfo/libmeminfo_test.cpp b/libmeminfo/libmeminfo_test.cpp index 7a2be4172..f973694e2 100644 --- a/libmeminfo/libmeminfo_test.cpp +++ b/libmeminfo/libmeminfo_test.cpp @@ -73,7 +73,7 @@ TEST_F(ValidateProcMemInfo, TestMapsEquality) { } } -TEST_F(ValidateProcMemInfo, TestMapsUsage) { +TEST_F(ValidateProcMemInfo, TestMaps) { const std::vector& maps = proc_mem->Maps(); ASSERT_FALSE(maps.empty()); ASSERT_EQ(proc->num_maps, maps.size()); @@ -96,6 +96,30 @@ TEST_F(ValidateProcMemInfo, TestMapsUsage) { EXPECT_EQ(proc_usage.uss, proc_mem->Usage().uss); } +TEST_F(ValidateProcMemInfo, TestSwapUsage) { + const std::vector& maps = proc_mem->Maps(); + ASSERT_FALSE(maps.empty()); + ASSERT_EQ(proc->num_maps, maps.size()); + + pm_memusage_t map_usage, proc_usage; + pm_memusage_zero(&map_usage); + pm_memusage_zero(&proc_usage); + for (size_t i = 0; i < maps.size(); i++) { + ASSERT_EQ(0, pm_map_usage(proc->maps[i], &map_usage)); + EXPECT_EQ(map_usage.swap, maps[i].usage.swap) << "SWAP mismatch for map: " << maps[i].name; + pm_memusage_add(&proc_usage, &map_usage); + } + + EXPECT_EQ(proc_usage.swap, proc_mem->Usage().swap); +} + +TEST_F(ValidateProcMemInfo, TestSwapOffsets) { + const MemUsage& proc_usage = proc_mem->Usage(); + const std::vector& swap_offsets = proc_mem->SwapOffsets(); + + EXPECT_EQ(proc_usage.swap / getpagesize(), swap_offsets.size()); +} + class ValidateProcMemInfoWss : public ::testing::Test { protected: void SetUp() override { diff --git a/libmeminfo/procmeminfo.cpp b/libmeminfo/procmeminfo.cpp index fe91d250b..e3839346a 100644 --- a/libmeminfo/procmeminfo.cpp +++ b/libmeminfo/procmeminfo.cpp @@ -44,6 +44,8 @@ static void add_mem_usage(MemUsage* to, const MemUsage& from) { to->pss += from.pss; to->uss += from.uss; + to->swap += from.swap; + to->private_clean += from.private_clean; to->private_dirty += from.private_dirty; @@ -76,6 +78,10 @@ const MemUsage& ProcMemInfo::Wss() { return wss_; } +const std::vector& ProcMemInfo::SwapOffsets() const { + return swap_offsets_; +} + bool ProcMemInfo::WssReset() { if (!get_wss_) { LOG(ERROR) << "Trying to reset working set from a memory usage counting object"; @@ -115,8 +121,8 @@ bool ProcMemInfo::ReadMaps(bool get_wss) { for (auto& vma : maps_) { if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss)) { - LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start - << "-" << vma.end << "]"; + LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start << "-" + << vma.end << "]"; maps_.clear(); return false; } @@ -153,7 +159,8 @@ bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) { if (!PAGE_PRESENT(p) && !PAGE_SWAPPED(p)) continue; if (PAGE_SWAPPED(p)) { - // TODO: do what's needed for swapped pages + vma.usage.swap += pagesz; + swap_offsets_.emplace_back(PAGE_SWAP_OFFSET(p)); continue; } diff --git a/libmeminfo/sysmeminfo.cpp b/libmeminfo/sysmeminfo.cpp index 50fa2136d..82605b681 100644 --- a/libmeminfo/sysmeminfo.cpp +++ b/libmeminfo/sysmeminfo.cpp @@ -37,9 +37,11 @@ namespace android { namespace meminfo { const std::vector SysMemInfo::kDefaultSysMemInfoTags = { - "MemTotal:", "MemFree:", "Buffers:", "Cached:", "Shmem:", - "Slab:", "SReclaimable:", "SUnreclaim:", "SwapTotal:", "SwapFree:", - "ZRam:", "Mapped:", "VmallocUsed:", "PageTables:", "KernelStack:", + SysMemInfo::kMemTotal, SysMemInfo::kMemFree, SysMemInfo::kMemBuffers, + SysMemInfo::kMemCached, SysMemInfo::kMemShmem, SysMemInfo::kMemSlab, + SysMemInfo::kMemSReclaim, SysMemInfo::kMemSUnreclaim, SysMemInfo::kMemSwapTotal, + SysMemInfo::kMemSwapFree, SysMemInfo::kMemZram, SysMemInfo::kMemMapped, + SysMemInfo::kMemVmallocUsed, SysMemInfo::kMemPageTables, SysMemInfo::kMemKernelStack, }; bool SysMemInfo::ReadMemInfo(const std::string& path) { @@ -99,6 +101,7 @@ bool SysMemInfo::ReadMemInfo(const std::vector& tags, const std::st buffer[len] = '\0'; char* p = buffer; uint32_t found = 0; + uint32_t lineno = 0; while (*p && found < tags.size()) { for (auto& tag : tags) { if (strncmp(p, tag.c_str(), tag.size()) == 0) { @@ -107,7 +110,7 @@ bool SysMemInfo::ReadMemInfo(const std::vector& tags, const std::st char* endptr = nullptr; mem_in_kb_[tag] = strtoull(p, &endptr, 10); if (p == endptr) { - PLOG(ERROR) << "Failed to parse line in file: " << path; + PLOG(ERROR) << "Failed to parse line:" << lineno + 1 << " in file: " << path; return false; } p = endptr; @@ -119,6 +122,7 @@ bool SysMemInfo::ReadMemInfo(const std::vector& tags, const std::st p++; } if (*p) p++; + lineno++; } return true; From 70fa72dd01ba9cac7ed4243676c9f7135926fce7 Mon Sep 17 00:00:00 2001 From: Sandeep Patil Date: Fri, 9 Nov 2018 19:18:29 -0800 Subject: [PATCH 2/6] libmeminfo: Add support to read zram memory consumption Bug: 114325007 Bug: 111694435 Test: libmeminfo_test 1 --gtest_filter=SysMemInfoParse.TestZramTotal Benchmark: libmeminfo_benchmark --benchmark_filter=BM_.*ZramTotal Benchmark Result on Blueline: ----------------------------------------------------------- Benchmark Time CPU Iterations ----------------------------------------------------------- BM_OldReadZramTotal 3857 ns 3839 ns 134096 BM_NewReadZramTotal 4461 ns 4440 ns 157341 Change-Id: I5220fa17b101981ef859179960fe78fe68e84852 Signed-off-by: Sandeep Patil --- libmeminfo/Android.bp | 9 +++ libmeminfo/include/meminfo/sysmeminfo.h | 4 +- libmeminfo/libmeminfo_benchmark.cpp | 61 ++++++++++++++++++-- libmeminfo/libmeminfo_test.cpp | 11 ++++ libmeminfo/sysmeminfo.cpp | 77 +++++++++++++++++++++++-- libmeminfo/testdata1/mm_stat | 1 + libmeminfo/testdata2/mem_used_total | 1 + 7 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 libmeminfo/testdata1/mm_stat create mode 100644 libmeminfo/testdata2/mem_used_total diff --git a/libmeminfo/Android.bp b/libmeminfo/Android.bp index aab3743c7..3e191ad11 100644 --- a/libmeminfo/Android.bp +++ b/libmeminfo/Android.bp @@ -54,6 +54,11 @@ cc_test { srcs: [ "libmeminfo_test.cpp" ], + + data: [ + "testdata1/*", + "testdata2/*" + ], } cc_benchmark { @@ -67,4 +72,8 @@ cc_benchmark { "libmeminfo", "libprocinfo", ], + + data: [ + "testdata1/*", + ], } diff --git a/libmeminfo/include/meminfo/sysmeminfo.h b/libmeminfo/include/meminfo/sysmeminfo.h index 87e14642b..885be1d67 100644 --- a/libmeminfo/include/meminfo/sysmeminfo.h +++ b/libmeminfo/include/meminfo/sysmeminfo.h @@ -38,7 +38,6 @@ class SysMemInfo final { static constexpr const char* kMemSUnreclaim = "SUnreclaim:"; static constexpr const char* kMemSwapTotal = "SwapTotal:"; static constexpr const char* kMemSwapFree = "SwapFree:"; - static constexpr const char* kMemZram = "Zram:"; static constexpr const char* kMemMapped = "Mapped:"; static constexpr const char* kMemVmallocUsed = "VmallocUsed:"; static constexpr const char* kMemPageTables = "PageTables:"; @@ -64,14 +63,15 @@ class SysMemInfo final { uint64_t mem_slab_unreclaimable_kb() { return mem_in_kb_[kMemSUnreclaim]; } uint64_t mem_swap_kb() { return mem_in_kb_[kMemSwapTotal]; } uint64_t mem_swap_free_kb() { return mem_in_kb_[kMemSwapFree]; } - uint64_t mem_zram_kb() { return mem_in_kb_[kMemZram]; } uint64_t mem_mapped_kb() { return mem_in_kb_[kMemMapped]; } uint64_t mem_vmalloc_used_kb() { return mem_in_kb_[kMemVmallocUsed]; } uint64_t mem_page_tables_kb() { return mem_in_kb_[kMemPageTables]; } uint64_t mem_kernel_stack_kb() { return mem_in_kb_[kMemPageTables]; } + uint64_t mem_zram_kb(const std::string& zram_dev = ""); private: std::map mem_in_kb_; + bool MemZramDevice(const std::string& zram_dev, uint64_t* mem_zram_dev); }; } // namespace meminfo diff --git a/libmeminfo/libmeminfo_benchmark.cpp b/libmeminfo/libmeminfo_benchmark.cpp index e2239f049..1db08242a 100644 --- a/libmeminfo/libmeminfo_benchmark.cpp +++ b/libmeminfo/libmeminfo_benchmark.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include #include #include @@ -46,7 +48,7 @@ enum { MEMINFO_COUNT }; -void get_mem_info(uint64_t mem[], const char* file) { +static void get_mem_info(uint64_t mem[], const char* file) { char buffer[4096]; unsigned int numFound = 0; @@ -67,9 +69,10 @@ void get_mem_info(uint64_t mem[], const char* file) { buffer[len] = 0; static const char* const tags[] = { - "MemTotal:", "MemFree:", "Buffers:", "Cached:", "Shmem:", "Slab:", - "SReclaimable:", "SUnreclaim:", "SwapTotal:", "SwapFree:", "ZRam:", "Mapped:", - "VmallocUsed:", "PageTables:", "KernelStack:", NULL}; + "MemTotal:", "MemFree:", "Buffers:", "Cached:", "Shmem:", "Slab:", + "SReclaimable:", "SUnreclaim:", "SwapTotal:", "SwapFree:", "ZRam:", "Mapped:", + "VmallocUsed:", "PageTables:", "KernelStack:", NULL + }; static const int tagsLen[] = {9, 8, 8, 7, 6, 5, 13, 11, 10, 9, 5, 7, 12, 11, 12, 0}; @@ -78,7 +81,8 @@ void get_mem_info(uint64_t mem[], const char* file) { while (*p && (numFound < (sizeof(tagsLen) / sizeof(tagsLen[0])))) { int i = 0; while (tags[i]) { - //std::cout << "tag =" << tags[i] << " p = " << std::string(p, tagsLen[i]) << std::endl; + // std::cout << "tag =" << tags[i] << " p = " << std::string(p, tagsLen[i]) << + // std::endl; if (strncmp(p, tags[i], tagsLen[i]) == 0) { p += tagsLen[i]; while (*p == ' ') p++; @@ -214,4 +218,51 @@ Hugepagesize: 2048 kB)meminfo"; } BENCHMARK(BM_ReadMemInfo); +static uint64_t get_zram_mem_used(const std::string& zram_dir) { + FILE* f = fopen((zram_dir + "mm_stat").c_str(), "r"); + if (f) { + uint64_t mem_used_total = 0; + + int matched = fscanf(f, "%*d %*d %" SCNu64 " %*d %*d %*d %*d", &mem_used_total); + if (matched != 1) + fprintf(stderr, "warning: failed to parse %s\n", (zram_dir + "mm_stat").c_str()); + + fclose(f); + return mem_used_total; + } + + f = fopen((zram_dir + "mem_used_total").c_str(), "r"); + if (f) { + uint64_t mem_used_total = 0; + + int matched = fscanf(f, "%" SCNu64, &mem_used_total); + if (matched != 1) + fprintf(stderr, "warning: failed to parse %s\n", (zram_dir + "mem_used_total").c_str()); + + fclose(f); + return mem_used_total; + } + + return 0; +} + +static void BM_OldReadZramTotal(benchmark::State& state) { + std::string exec_dir = ::android::base::GetExecutableDirectory(); + std::string zram_mmstat_dir = exec_dir + "/testdata1/"; + for (auto _ : state) { + uint64_t zram_total __attribute__((unused)) = get_zram_mem_used(zram_mmstat_dir) / 1024; + } +} +BENCHMARK(BM_OldReadZramTotal); + +static void BM_NewReadZramTotal(benchmark::State& state) { + std::string exec_dir = ::android::base::GetExecutableDirectory(); + std::string zram_mmstat_dir = exec_dir + "/testdata1/"; + ::android::meminfo::SysMemInfo mi; + for (auto _ : state) { + uint64_t zram_total __attribute__((unused)) = mi.mem_zram_kb(zram_mmstat_dir); + } +} +BENCHMARK(BM_NewReadZramTotal); + BENCHMARK_MAIN(); diff --git a/libmeminfo/libmeminfo_test.cpp b/libmeminfo/libmeminfo_test.cpp index f973694e2..923e156d5 100644 --- a/libmeminfo/libmeminfo_test.cpp +++ b/libmeminfo/libmeminfo_test.cpp @@ -312,6 +312,17 @@ TEST(SysMemInfoParser, TestEmptyFile) { EXPECT_EQ(mi.mem_total_kb(), 0); } +TEST(SysMemInfoParse, TestZramTotal) { + std::string exec_dir = ::android::base::GetExecutableDirectory(); + + SysMemInfo mi; + std::string zram_mmstat_dir = exec_dir + "/testdata1/"; + EXPECT_EQ(mi.mem_zram_kb(zram_mmstat_dir), 30504); + + std::string zram_memused_dir = exec_dir + "/testdata2/"; + EXPECT_EQ(mi.mem_zram_kb(zram_memused_dir), 30504); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); if (argc <= 1) { diff --git a/libmeminfo/sysmeminfo.cpp b/libmeminfo/sysmeminfo.cpp index 82605b681..7e56238cb 100644 --- a/libmeminfo/sysmeminfo.cpp +++ b/libmeminfo/sysmeminfo.cpp @@ -17,10 +17,12 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -29,7 +31,9 @@ #include #include #include +#include #include +#include #include "meminfo_private.h" @@ -37,11 +41,11 @@ namespace android { namespace meminfo { const std::vector SysMemInfo::kDefaultSysMemInfoTags = { - SysMemInfo::kMemTotal, SysMemInfo::kMemFree, SysMemInfo::kMemBuffers, - SysMemInfo::kMemCached, SysMemInfo::kMemShmem, SysMemInfo::kMemSlab, - SysMemInfo::kMemSReclaim, SysMemInfo::kMemSUnreclaim, SysMemInfo::kMemSwapTotal, - SysMemInfo::kMemSwapFree, SysMemInfo::kMemZram, SysMemInfo::kMemMapped, - SysMemInfo::kMemVmallocUsed, SysMemInfo::kMemPageTables, SysMemInfo::kMemKernelStack, + SysMemInfo::kMemTotal, SysMemInfo::kMemFree, SysMemInfo::kMemBuffers, + SysMemInfo::kMemCached, SysMemInfo::kMemShmem, SysMemInfo::kMemSlab, + SysMemInfo::kMemSReclaim, SysMemInfo::kMemSUnreclaim, SysMemInfo::kMemSwapTotal, + SysMemInfo::kMemSwapFree, SysMemInfo::kMemMapped, SysMemInfo::kMemVmallocUsed, + SysMemInfo::kMemPageTables, SysMemInfo::kMemKernelStack, }; bool SysMemInfo::ReadMemInfo(const std::string& path) { @@ -129,5 +133,68 @@ bool SysMemInfo::ReadMemInfo(const std::vector& tags, const std::st } #endif +uint64_t SysMemInfo::mem_zram_kb(const std::string& zram_dev) { + uint64_t mem_zram_total = 0; + if (!zram_dev.empty()) { + if (!MemZramDevice(zram_dev, &mem_zram_total)) { + return 0; + } + return mem_zram_total / 1024; + } + + constexpr uint32_t kMaxZramDevices = 256; + for (uint32_t i = 0; i < kMaxZramDevices; i++) { + std::string zram_dev = ::android::base::StringPrintf("/sys/block/zram%u/", i); + if (access(zram_dev.c_str(), F_OK)) { + // We assume zram devices appear in range 0-255 and appear always in sequence + // under /sys/block. So, stop looking for them once we find one is missing. + break; + } + + uint64_t mem_zram_dev; + if (!MemZramDevice(zram_dev, &mem_zram_dev)) { + return 0; + } + + mem_zram_total += mem_zram_dev; + } + + return mem_zram_total / 1024; +} + +bool SysMemInfo::MemZramDevice(const std::string& zram_dev, uint64_t* mem_zram_dev) { + std::string content; + if (android::base::ReadFileToString(zram_dev + "mm_stat", &content)) { + std::vector values = ::android::base::Split(content, " "); + if (values.size() < 3) { + LOG(ERROR) << "Malformed mm_stat file for zram dev: " << zram_dev + << " content: " << content; + return false; + } + + if (!::android::base::ParseUint(values[2], mem_zram_dev)) { + LOG(ERROR) << "Malformed mm_stat file for zram dev: " << zram_dev + << " value: " << values[2]; + return false; + } + + return true; + } + + if (::android::base::ReadFileToString(zram_dev + "mem_used_total", &content)) { + *mem_zram_dev = strtoull(content.c_str(), NULL, 10); + if (*mem_zram_dev == ULLONG_MAX) { + PLOG(ERROR) << "Malformed mem_used_total file for zram dev: " << zram_dev + << " content: " << content; + return false; + } + + return true; + } + + LOG(ERROR) << "Can't find memory status under: " << zram_dev; + return false; +} + } // namespace meminfo } // namespace android diff --git a/libmeminfo/testdata1/mm_stat b/libmeminfo/testdata1/mm_stat new file mode 100644 index 000000000..684f567a6 --- /dev/null +++ b/libmeminfo/testdata1/mm_stat @@ -0,0 +1 @@ +145674240 26801454 31236096 0 45772800 3042 1887 517 diff --git a/libmeminfo/testdata2/mem_used_total b/libmeminfo/testdata2/mem_used_total new file mode 100644 index 000000000..97fcf41bd --- /dev/null +++ b/libmeminfo/testdata2/mem_used_total @@ -0,0 +1 @@ +31236096 From 549feab6314690460e58f50e6084dfb95a1022e3 Mon Sep 17 00:00:00 2001 From: Sandeep Patil Date: Mon, 19 Nov 2018 11:38:40 -0800 Subject: [PATCH 3/6] libmeminfo: Add support to fiter accounting based on page flags Bug: 114325007 Bug: 111694435 Test: libmeminfo_test 1 --gtest_filter=ValidateProcMemInfoFlags.* Change-Id: Ifa7006de78dd909b5b4db282a8c9931ebf97a68a Signed-off-by: Sandeep Patil --- libmeminfo/include/meminfo/procmeminfo.h | 4 +- libmeminfo/libmeminfo_test.cpp | 49 ++++++++++++++++++++++++ libmeminfo/procmeminfo.cpp | 6 ++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/libmeminfo/include/meminfo/procmeminfo.h b/libmeminfo/include/meminfo/procmeminfo.h index dd599128d..3b620a00b 100644 --- a/libmeminfo/include/meminfo/procmeminfo.h +++ b/libmeminfo/include/meminfo/procmeminfo.h @@ -29,7 +29,7 @@ namespace meminfo { class ProcMemInfo final { // Per-process memory accounting public: - ProcMemInfo(pid_t pid, bool get_wss = false); + ProcMemInfo(pid_t pid, bool get_wss = false, uint64_t pgflags = 0, uint64_t pgflags_mask = 0); const std::vector& Maps(); const MemUsage& Usage(); @@ -45,6 +45,8 @@ class ProcMemInfo final { pid_t pid_; bool get_wss_; + uint64_t pgflags_; + uint64_t pgflags_mask_; std::vector maps_; diff --git a/libmeminfo/libmeminfo_test.cpp b/libmeminfo/libmeminfo_test.cpp index 923e156d5..6d731737b 100644 --- a/libmeminfo/libmeminfo_test.cpp +++ b/libmeminfo/libmeminfo_test.cpp @@ -245,6 +245,55 @@ TEST_F(ValidatePageAcct, TestPageIdle) { } } +TEST(ValidateProcMemInfoFlags, TestPageFlags1) { + // Create proc object using libpagemap + pm_kernel_t* ker; + ASSERT_EQ(0, pm_kernel_create(&ker)); + pm_process_t* proc; + ASSERT_EQ(0, pm_process_create(ker, pid, &proc)); + + // count swapbacked pages using libpagemap + pm_memusage_t proc_usage; + pm_memusage_zero(&proc_usage); + ASSERT_EQ(0, pm_process_usage_flags(proc, &proc_usage, (1 << KPF_SWAPBACKED), + (1 << KPF_SWAPBACKED))); + + // Create ProcMemInfo that counts swapbacked pages + ProcMemInfo proc_mem(pid, false, (1 << KPF_SWAPBACKED), (1 << KPF_SWAPBACKED)); + + EXPECT_EQ(proc_usage.vss, proc_mem.Usage().vss); + EXPECT_EQ(proc_usage.rss, proc_mem.Usage().rss); + EXPECT_EQ(proc_usage.pss, proc_mem.Usage().pss); + EXPECT_EQ(proc_usage.uss, proc_mem.Usage().uss); + + pm_process_destroy(proc); + pm_kernel_destroy(ker); +} + +TEST(ValidateProcMemInfoFlags, TestPageFlags2) { + // Create proc object using libpagemap + pm_kernel_t* ker; + ASSERT_EQ(0, pm_kernel_create(&ker)); + pm_process_t* proc; + ASSERT_EQ(0, pm_process_create(ker, pid, &proc)); + + // count non-swapbacked pages using libpagemap + pm_memusage_t proc_usage; + pm_memusage_zero(&proc_usage); + ASSERT_EQ(0, pm_process_usage_flags(proc, &proc_usage, (1 << KPF_SWAPBACKED), 0)); + + // Create ProcMemInfo that counts non-swapbacked pages + ProcMemInfo proc_mem(pid, false, 0, (1 << KPF_SWAPBACKED)); + + EXPECT_EQ(proc_usage.vss, proc_mem.Usage().vss); + EXPECT_EQ(proc_usage.rss, proc_mem.Usage().rss); + EXPECT_EQ(proc_usage.pss, proc_mem.Usage().pss); + EXPECT_EQ(proc_usage.uss, proc_mem.Usage().uss); + + pm_process_destroy(proc); + pm_kernel_destroy(ker); +} + TEST(SysMemInfoParser, TestSysMemInfoFile) { std::string meminfo = R"meminfo(MemTotal: 3019740 kB MemFree: 1809728 kB diff --git a/libmeminfo/procmeminfo.cpp b/libmeminfo/procmeminfo.cpp index e3839346a..56b62e826 100644 --- a/libmeminfo/procmeminfo.cpp +++ b/libmeminfo/procmeminfo.cpp @@ -53,7 +53,8 @@ static void add_mem_usage(MemUsage* to, const MemUsage& from) { to->shared_dirty += from.shared_dirty; } -ProcMemInfo::ProcMemInfo(pid_t pid, bool get_wss) : pid_(pid), get_wss_(get_wss) { +ProcMemInfo::ProcMemInfo(pid_t pid, bool get_wss, uint64_t pgflags, uint64_t pgflags_mask) + : pid_(pid), get_wss_(get_wss), pgflags_(pgflags), pgflags_mask_(pgflags_mask) { if (!ReadMaps(get_wss_)) { LOG(ERROR) << "Failed to read maps for Process " << pid_; } @@ -170,6 +171,9 @@ bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) { return false; } + // skip unwanted pages from the count + if ((pg_flags[i] & pgflags_mask_) != pgflags_) continue; + if (!pinfo.PageMapCount(page_frame, &pg_counts[i])) { LOG(ERROR) << "Failed to get page count for " << page_frame << " in process " << pid_; return false; From f12919935099acefa0b3e34e2ee36db85e2165b0 Mon Sep 17 00:00:00 2001 From: Sandeep Patil Date: Mon, 19 Nov 2018 15:25:18 -0800 Subject: [PATCH 4/6] libmeminfo: Add support to reset workingset without procmeminfo objects Bug: 114325007 Bug: 111694435 Test: libmeminfo_test 1 Change-Id: I816809cda13983e47294a956347a27085397cbcf Signed-off-by: Sandeep Patil --- libmeminfo/include/meminfo/procmeminfo.h | 4 ++- libmeminfo/libmeminfo_test.cpp | 2 +- libmeminfo/procmeminfo.cpp | 32 +++++++++++------------- libmeminfo/tools/procmem.cpp | 18 ++++++------- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/libmeminfo/include/meminfo/procmeminfo.h b/libmeminfo/include/meminfo/procmeminfo.h index 3b620a00b..0d6f652ab 100644 --- a/libmeminfo/include/meminfo/procmeminfo.h +++ b/libmeminfo/include/meminfo/procmeminfo.h @@ -29,6 +29,9 @@ namespace meminfo { class ProcMemInfo final { // Per-process memory accounting public: + // Reset the working set accounting of the process via /proc//clear_refs + static bool ResetWorkingSet(pid_t pid); + ProcMemInfo(pid_t pid, bool get_wss = false, uint64_t pgflags = 0, uint64_t pgflags_mask = 0); const std::vector& Maps(); @@ -36,7 +39,6 @@ class ProcMemInfo final { const MemUsage& Wss(); const std::vector& SwapOffsets() const; - bool WssReset(); ~ProcMemInfo() = default; private: diff --git a/libmeminfo/libmeminfo_test.cpp b/libmeminfo/libmeminfo_test.cpp index 6d731737b..e28c35249 100644 --- a/libmeminfo/libmeminfo_test.cpp +++ b/libmeminfo/libmeminfo_test.cpp @@ -142,7 +142,7 @@ class ValidateProcMemInfoWss : public ::testing::Test { TEST_F(ValidateProcMemInfoWss, TestWorkingTestReset) { // Expect reset to succeed - EXPECT_TRUE(proc_mem->WssReset()); + EXPECT_TRUE(ProcMemInfo::ResetWorkingSet(pid)); } TEST_F(ValidateProcMemInfoWss, TestWssEquality) { diff --git a/libmeminfo/procmeminfo.cpp b/libmeminfo/procmeminfo.cpp index 56b62e826..378f923b3 100644 --- a/libmeminfo/procmeminfo.cpp +++ b/libmeminfo/procmeminfo.cpp @@ -53,6 +53,16 @@ static void add_mem_usage(MemUsage* to, const MemUsage& from) { to->shared_dirty += from.shared_dirty; } +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)) { + PLOG(ERROR) << "Failed to write to " << clear_refs_path; + return false; + } + + return true; +} + ProcMemInfo::ProcMemInfo(pid_t pid, bool get_wss, uint64_t pgflags, uint64_t pgflags_mask) : pid_(pid), get_wss_(get_wss), pgflags_(pgflags), pgflags_mask_(pgflags_mask) { if (!ReadMaps(get_wss_)) { @@ -66,14 +76,16 @@ const std::vector& ProcMemInfo::Maps() { const MemUsage& ProcMemInfo::Usage() { if (get_wss_) { - LOG(WARNING) << "Trying to read memory usage from working set object"; + LOG(WARNING) << "Trying to read process memory usage for " << pid_ + << " using invalid object"; } return usage_; } const MemUsage& ProcMemInfo::Wss() { if (!get_wss_) { - LOG(WARNING) << "Trying to read working set when there is none"; + LOG(WARNING) << "Trying to read process working set for " << pid_ + << " using invalid object"; } return wss_; @@ -83,22 +95,6 @@ const std::vector& ProcMemInfo::SwapOffsets() const { return swap_offsets_; } -bool ProcMemInfo::WssReset() { - if (!get_wss_) { - LOG(ERROR) << "Trying to reset working set from a memory usage counting object"; - return false; - } - - std::string clear_refs_path = ::android::base::StringPrintf("/proc/%d/clear_refs", pid_); - if (!::android::base::WriteStringToFile("1\n", clear_refs_path)) { - PLOG(ERROR) << "Failed to write to " << clear_refs_path; - return false; - } - - wss_.clear(); - return true; -} - bool ProcMemInfo::ReadMaps(bool get_wss) { // parse and read /proc//maps std::string maps_file = ::android::base::StringPrintf("/proc/%d/maps", pid_); diff --git a/libmeminfo/tools/procmem.cpp b/libmeminfo/tools/procmem.cpp index 3571e4173..d30c2f2ba 100644 --- a/libmeminfo/tools/procmem.cpp +++ b/libmeminfo/tools/procmem.cpp @@ -66,14 +66,12 @@ static void scan_usage(std::stringstream& ss, const MemUsage& usage, const std:: // clear string stream first. ss.str(""); // TODO: use ::android::base::StringPrintf instead of here. - if (!show_wss) - ss << std::setw(6) << usage.vss/1024 << padding; - ss << std::setw(6) << usage.rss/1024 << padding << std::setw(6) - << usage.pss/1024 << padding << std::setw(6) << usage.uss/1024 << padding - << std::setw(6) << usage.shared_clean/1024 << padding << std::setw(6) - << usage.shared_dirty/1024 << padding << std::setw(6) - << usage.private_clean/1024 << padding << std::setw(6) - << usage.private_dirty/1024 << padding; + if (!show_wss) ss << std::setw(6) << usage.vss / 1024 << padding; + ss << std::setw(6) << usage.rss / 1024 << padding << std::setw(6) << usage.pss / 1024 << padding + << std::setw(6) << usage.uss / 1024 << padding << std::setw(6) << usage.shared_clean / 1024 + << padding << std::setw(6) << usage.shared_dirty / 1024 << padding << std::setw(6) + << usage.private_clean / 1024 << padding << std::setw(6) << usage.private_dirty / 1024 + << padding; } static int show(ProcMemInfo& proc, bool hide_zeroes, bool show_wss) { @@ -158,14 +156,14 @@ int main(int argc, char* argv[]) { } bool need_wss = wss_reset || show_wss; - ProcMemInfo proc(pid, need_wss); if (wss_reset) { - if (!proc.WssReset()) { + if (!ProcMemInfo::ResetWorkingSet(pid)) { std::cerr << "Failed to reset working set of pid : " << pid << std::endl; exit(EXIT_FAILURE); } return 0; } + ProcMemInfo proc(pid, need_wss); return show(proc, hide_zeroes, show_wss); } From c6497eb1dc39294f017de06f97739ea7bcb964fb Mon Sep 17 00:00:00 2001 From: Sandeep Patil Date: Tue, 20 Nov 2018 09:31:36 -0800 Subject: [PATCH 5/6] libmeminfo: defer maps reading only when required for procmeminfo This restores the original behavior. The main reason it should be this way is to make the class generic for all things memory under /proc//. For example, with the current behavior, a program that only needs to read /proc//smaps_rollup will end up wasting time and memory by parsing /proc//maps when the object is being constructed. Same goes for a program that only wants to reset the working set. The 'ProcMemInfo' object still retains the property that it can only be used once to read maps and the object must be destroyed + recreated to update the stats. Bug: 114325007 Bug: 111694435 Test: libmeminfo_test 1 Test: # adb push /google/data/ro/users/ss/sspatil/test-memutils.sh /data/local/tmp/ # adb push procmem2 /data/local/tmp && adb push procrank2 /data/local/tmp # adb root && adb shell $ cd /data/local/tmp/ $ chmod +x test-memutils.sh $ ./test-memutils.sh 2>&1 | tee test.log Change-Id: I856d3b78a0088cff02cbd010b29ffbe0e35f5ee2 Signed-off-by: Sandeep Patil --- libmeminfo/include/meminfo/procmeminfo.h | 2 +- libmeminfo/libmeminfo_test.cpp | 38 +++++++++++++++++++++ libmeminfo/procmeminfo.cpp | 43 ++++++++++++++++++++---- 3 files changed, 76 insertions(+), 7 deletions(-) diff --git a/libmeminfo/include/meminfo/procmeminfo.h b/libmeminfo/include/meminfo/procmeminfo.h index 0d6f652ab..92375d31c 100644 --- a/libmeminfo/include/meminfo/procmeminfo.h +++ b/libmeminfo/include/meminfo/procmeminfo.h @@ -37,7 +37,7 @@ class ProcMemInfo final { const std::vector& Maps(); const MemUsage& Usage(); const MemUsage& Wss(); - const std::vector& SwapOffsets() const; + const std::vector& SwapOffsets(); ~ProcMemInfo() = default; diff --git a/libmeminfo/libmeminfo_test.cpp b/libmeminfo/libmeminfo_test.cpp index e28c35249..b7a4b6b6f 100644 --- a/libmeminfo/libmeminfo_test.cpp +++ b/libmeminfo/libmeminfo_test.cpp @@ -245,6 +245,44 @@ TEST_F(ValidatePageAcct, TestPageIdle) { } } +TEST(TestProcMemInfo, TestMapsEmpty) { + ProcMemInfo proc_mem(pid); + const std::vector& maps = proc_mem.Maps(); + EXPECT_GT(maps.size(), 0); +} + +TEST(TestProcMemInfo, TestUsageEmpty) { + // If we created the object for getting working set, + // the usage must be empty + ProcMemInfo proc_mem(pid, true); + const MemUsage& usage = proc_mem.Usage(); + EXPECT_EQ(usage.rss, 0); + EXPECT_EQ(usage.vss, 0); + EXPECT_EQ(usage.pss, 0); + EXPECT_EQ(usage.uss, 0); + EXPECT_EQ(usage.swap, 0); +} + +TEST(TestProcMemInfoWssReset, TestWssEmpty) { + // If we created the object for getting usage, + // the working set must be empty + ProcMemInfo proc_mem(pid, false); + const MemUsage& wss = proc_mem.Wss(); + EXPECT_EQ(wss.rss, 0); + EXPECT_EQ(wss.vss, 0); + EXPECT_EQ(wss.pss, 0); + EXPECT_EQ(wss.uss, 0); + EXPECT_EQ(wss.swap, 0); +} + +TEST(TestProcMemInfoWssReset, TestSwapOffsetsEmpty) { + // If we created the object for getting working set, + // the swap offsets must be empty + ProcMemInfo proc_mem(pid, true); + const std::vector& swap_offsets = proc_mem.SwapOffsets(); + EXPECT_EQ(swap_offsets.size(), 0); +} + TEST(ValidateProcMemInfoFlags, TestPageFlags1) { // Create proc object using libpagemap pm_kernel_t* ker; diff --git a/libmeminfo/procmeminfo.cpp b/libmeminfo/procmeminfo.cpp index 378f923b3..8867e147f 100644 --- a/libmeminfo/procmeminfo.cpp +++ b/libmeminfo/procmeminfo.cpp @@ -64,13 +64,13 @@ bool ProcMemInfo::ResetWorkingSet(pid_t pid) { } ProcMemInfo::ProcMemInfo(pid_t pid, bool get_wss, uint64_t pgflags, uint64_t pgflags_mask) - : pid_(pid), get_wss_(get_wss), pgflags_(pgflags), pgflags_mask_(pgflags_mask) { - if (!ReadMaps(get_wss_)) { - LOG(ERROR) << "Failed to read maps for Process " << pid_; - } -} + : pid_(pid), get_wss_(get_wss), pgflags_(pgflags), pgflags_mask_(pgflags_mask) {} const std::vector& ProcMemInfo::Maps() { + if (maps_.empty() && !ReadMaps(get_wss_)) { + LOG(ERROR) << "Failed to read maps for Process " << pid_; + } + return maps_; } @@ -78,7 +78,13 @@ const MemUsage& ProcMemInfo::Usage() { if (get_wss_) { LOG(WARNING) << "Trying to read process memory usage for " << pid_ << " using invalid object"; + return usage_; } + + if (maps_.empty() && !ReadMaps(get_wss_)) { + LOG(ERROR) << "Failed to get memory usage for Process " << pid_; + } + return usage_; } @@ -86,16 +92,39 @@ const MemUsage& ProcMemInfo::Wss() { if (!get_wss_) { LOG(WARNING) << "Trying to read process working set for " << pid_ << " using invalid object"; + return wss_; + } + + if (maps_.empty() && !ReadMaps(get_wss_)) { + LOG(ERROR) << "Failed to get working set for Process " << pid_; } return wss_; } -const std::vector& ProcMemInfo::SwapOffsets() const { +const std::vector& ProcMemInfo::SwapOffsets() { + if (get_wss_) { + LOG(WARNING) << "Trying to read process swap offsets for " << pid_ + << " using invalid object"; + return swap_offsets_; + } + + if (maps_.empty() && !ReadMaps(get_wss_)) { + LOG(ERROR) << "Failed to get swap offsets for Process " << pid_; + } + return swap_offsets_; } bool ProcMemInfo::ReadMaps(bool get_wss) { + // Each object reads /proc//maps only once. This is done to make sure programs that are + // running for the lifetime of the system can recycle the objects and don't have to + // unnecessarily retain and update this object in memory (which can get significantly large). + // E.g. A program that only needs to reset the working set will never all ->Maps(), ->Usage(). + // E.g. A program that is monitoring smaps_rollup, may never call ->maps(), Usage(), so it + // doesn't make sense for us to parse and retain unnecessary memory accounting stats by default. + if (!maps_.empty()) return true; + // parse and read /proc//maps std::string maps_file = ::android::base::StringPrintf("/proc/%d/maps", pid_); if (!::android::procinfo::ReadMapFile( @@ -164,6 +193,7 @@ bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) { uint64_t page_frame = PAGE_PFN(p); if (!pinfo.PageFlags(page_frame, &pg_flags[i])) { LOG(ERROR) << "Failed to get page flags for " << page_frame << " in process " << pid_; + swap_offsets_.clear(); return false; } @@ -172,6 +202,7 @@ bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) { if (!pinfo.PageMapCount(page_frame, &pg_counts[i])) { LOG(ERROR) << "Failed to get page count for " << page_frame << " in process " << pid_; + swap_offsets_.clear(); return false; } From a14119d069ddc09e46263a37b40b73ffc91685a8 Mon Sep 17 00:00:00 2001 From: Sandeep Patil Date: Fri, 16 Nov 2018 09:18:57 -0800 Subject: [PATCH 6/6] Add procrank2 Implement procrank using new libmeminfo APIs. procrank2 is functionally equivalent to procrank except for the part where the old procrank would print processes under each oomadj level if called with 'procrank -c -o'. The output format of 'procrank2' is identical to that of 'procrank'. 'procrank2' also gives significant performance boost over 'procrank' but does cost a more memory as the library is written to 99% use case where callers almost always want to read the memory usage of hte process. Bug: 114325007 Bug: 111694435 Test: # adb push /google/data/ro/users/ss/sspatil/test-memutils.sh /data/local/tmp/ # adb push procmem2 /data/local/tmp && adb push procrank2 /data/local/tmp # adb root && adb shell $ cd /data/local/tmp/ $ chmod +x test-memutils.sh $ ./test-memutils.sh 2>&1 | tee test.log Test: procrank: 0m00.43s real 0m00.11s user 0m00.32s system procrank2: 0m00.42s real 0m00.05s user 0m00.36s system Change-Id: I37b71a4625248e333ba01801c0d8d7ef348e4cfa Signed-off-by: Sandeep Patil --- libmeminfo/tools/Android.bp | 15 + libmeminfo/tools/procrank.cpp | 530 ++++++++++++++++++++++++++++++++++ 2 files changed, 545 insertions(+) create mode 100644 libmeminfo/tools/procrank.cpp diff --git a/libmeminfo/tools/Android.bp b/libmeminfo/tools/Android.bp index 0870130ea..7c4166023 100644 --- a/libmeminfo/tools/Android.bp +++ b/libmeminfo/tools/Android.bp @@ -25,3 +25,18 @@ cc_binary { "libmeminfo", ], } + +cc_binary { + name: "procrank2", + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + ], + + srcs: ["procrank.cpp"], + shared_libs: [ + "libbase", + "libmeminfo", + ], +} diff --git a/libmeminfo/tools/procrank.cpp b/libmeminfo/tools/procrank.cpp new file mode 100644 index 000000000..e39e7fa56 --- /dev/null +++ b/libmeminfo/tools/procrank.cpp @@ -0,0 +1,530 @@ +/* + * 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 +#include + +using ::android::meminfo::MemUsage; +using ::android::meminfo::ProcMemInfo; + +struct ProcessRecord { + public: + ProcessRecord(pid_t pid, bool get_wss = false, uint64_t pgflags = 0, uint64_t pgflags_mask = 0) + : pid_(-1), + procmem_(nullptr), + oomadj_(OOM_SCORE_ADJ_MAX + 1), + cmdline_(""), + proportional_swap_(0), + unique_swap_(0), + zswap_(0) { + std::unique_ptr procmem = + std::make_unique(pid, get_wss, pgflags, pgflags_mask); + if (procmem == nullptr) { + std::cerr << "Failed to create ProcMemInfo for: " << pid << std::endl; + return; + } + + std::string fname = ::android::base::StringPrintf("/proc/%d/oom_score_adj", pid); + auto oomscore_fp = + std::unique_ptr{fopen(fname.c_str(), "re"), fclose}; + if (oomscore_fp == nullptr) { + std::cerr << "Failed to open oom_score_adj file: " << fname << std::endl; + return; + } + + if (fscanf(oomscore_fp.get(), "%d\n", &oomadj_) != 1) { + std::cerr << "Failed to read oomadj from: " << fname << std::endl; + return; + } + + fname = ::android::base::StringPrintf("/proc/%d/cmdline", pid); + if (!::android::base::ReadFileToString(fname, &cmdline_)) { + std::cerr << "Failed to read cmdline from: " << fname << std::endl; + cmdline_ = ""; + } + // 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_.resize(strlen(cmdline_.c_str())); + procmem_ = std::move(procmem); + pid_ = pid; + } + + bool valid() const { return pid_ != -1; } + + void CalculateSwap(const uint16_t* swap_offset_array, float zram_compression_ratio) { + const std::vector& swp_offs = procmem_->SwapOffsets(); + for (auto& off : swp_offs) { + proportional_swap_ += getpagesize() / swap_offset_array[off]; + unique_swap_ += swap_offset_array[off] == 1 ? getpagesize() : 0; + zswap_ = proportional_swap_ * zram_compression_ratio; + } + } + + // Getters + pid_t pid() const { return pid_; } + const std::string& cmdline() const { return cmdline_; } + int32_t oomadj() const { return oomadj_; } + uint64_t proportional_swap() const { return proportional_swap_; } + uint64_t unique_swap() const { return unique_swap_; } + uint64_t zswap() const { return zswap_; } + + // Wrappers to ProcMemInfo + const std::vector& SwapOffsets() const { return procmem_->SwapOffsets(); } + const MemUsage& Usage() const { return procmem_->Usage(); } + const MemUsage& Wss() const { return procmem_->Wss(); } + + private: + pid_t pid_; + std::unique_ptr procmem_; + int32_t oomadj_; + std::string cmdline_; + uint64_t proportional_swap_; + uint64_t unique_swap_; + uint64_t zswap_; +}; + +// Show working set instead of memory consumption +bool show_wss = false; +// Reset working set of each process +bool reset_wss = false; +// Show per-process oom_score_adj column +bool show_oomadj = false; +// True if the device has swap enabled +bool has_swap = false; +// True, if device has zram enabled +bool has_zram = false; +// If zram is enabled, the compression ratio is zram used / swap used. +float zram_compression_ratio = 0.0; +// Sort process in reverse, default is descending +bool reverse_sort = false; + +// Calculated total memory usage across all processes in the system +uint64_t total_pss = 0; +uint64_t total_uss = 0; +uint64_t total_swap = 0; +uint64_t total_pswap = 0; +uint64_t total_uswap = 0; +uint64_t total_zswap = 0; + +static void usage(const char* myname) { + std::cerr << "Usage: " << myname << " [ -W ] [ -v | -r | -p | -u | -s | -h ]" << std::endl + << " -v Sort by VSS." << std::endl + << " -r Sort by RSS." << std::endl + << " -p Sort by PSS." << std::endl + << " -u Sort by USS." << std::endl + << " -s Sort by swap." << std::endl + << " (Default sort order is PSS.)" << std::endl + << " -R Reverse sort order (default is descending)." << std::endl + << " -c Only show cached (storage backed) pages" << std::endl + << " -C Only show non-cached (ram/swap backed) pages" << std::endl + << " -k Only show pages collapsed by KSM" << std::endl + << " -w Display statistics for working set only." << std::endl + << " -W Reset working set of all processes." << std::endl + << " -o Show and sort by oom score against lowmemorykiller thresholds." + << std::endl + << " -h Display this help screen." << std::endl; +} + +static bool read_all_pids(std::vector* pids, std::function for_each_pid) { + pids->clear(); + 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; + pids->push_back(pid); + } + + return true; +} + +static bool count_swap_offsets(const ProcessRecord& proc, uint16_t* swap_offset_array, + uint32_t size) { + const std::vector& swp_offs = proc.SwapOffsets(); + for (auto& off : swp_offs) { + if (off >= size) { + std::cerr << "swap offset " << off << " is out of bounds for process: " << proc.pid() + << std::endl; + return false; + } + + if (swap_offset_array[off] == USHRT_MAX) { + std::cerr << "swap offset " << off << " ref count overflow in process: " << proc.pid() + << std::endl; + return false; + } + + swap_offset_array[off]++; + } + + return true; +} + +static void print_header(std::stringstream& ss) { + ss.str(""); + ss << ::android::base::StringPrintf("%5s ", "PID"); + if (show_oomadj) { + ss << ::android::base::StringPrintf("%5s ", "oom"); + } + + if (show_wss) { + ss << ::android::base::StringPrintf("%7s %7s %7s ", "WRss", "WPss", "WUss"); + // now swap statistics here, working set pages by definition shouldn't end up in swap. + } else { + ss << ::android::base::StringPrintf("%8s %7s %7s %7s ", "Vss", "Rss", "Pss", "Uss"); + if (has_swap) { + ss << ::android::base::StringPrintf("%7s %7s %7s ", "Swap", "PSwap", "USwap"); + if (has_zram) { + ss << ::android::base::StringPrintf("%7s ", "ZSwap"); + } + } + } + + ss << "cmdline"; +} + +static void print_process_record(std::stringstream& ss, ProcessRecord& proc) { + ss << ::android::base::StringPrintf("%5d ", proc.pid()); + if (show_oomadj) { + ss << ::android::base::StringPrintf("%5d ", proc.oomadj()); + } + + if (show_wss) { + ss << ::android::base::StringPrintf("%6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ", + proc.Wss().rss / 1024, proc.Wss().pss / 1024, + proc.Wss().uss / 1024); + } else { + ss << ::android::base::StringPrintf("%7" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 + "K ", + proc.Usage().vss / 1024, proc.Usage().rss / 1024, + proc.Usage().pss / 1024, proc.Usage().uss / 1024); + if (has_swap) { + ss << ::android::base::StringPrintf("%6" PRIu64 "K ", proc.Usage().swap / 1024); + ss << ::android::base::StringPrintf("%6" PRIu64 "K ", proc.proportional_swap() / 1024); + ss << ::android::base::StringPrintf("%6" PRIu64 "K ", proc.unique_swap() / 1024); + if (has_zram) { + ss << ::android::base::StringPrintf("%6" PRIu64 "K ", (proc.zswap() / 1024)); + } + } + } +} + +static void print_processes(std::stringstream& ss, std::vector& procs, + uint16_t* swap_offset_array) { + for (auto& proc : procs) { + total_pss += show_wss ? proc.Wss().pss : proc.Usage().pss; + total_uss += show_wss ? proc.Wss().uss : proc.Usage().uss; + if (!show_wss && has_swap) { + proc.CalculateSwap(swap_offset_array, zram_compression_ratio); + total_swap += proc.Usage().swap; + total_pswap += proc.proportional_swap(); + total_uswap += proc.unique_swap(); + if (has_zram) { + total_zswap += proc.zswap(); + } + } + + print_process_record(ss, proc); + ss << proc.cmdline() << std::endl; + } +} + +static void print_separator(std::stringstream& ss) { + ss << ::android::base::StringPrintf("%5s ", ""); + if (show_oomadj) { + ss << ::android::base::StringPrintf("%5s ", ""); + } + + if (show_wss) { + ss << ::android::base::StringPrintf("%7s %7s %7s ", "", "------", "------"); + } else { + ss << ::android::base::StringPrintf("%8s %7s %7s %7s ", "", "", "------", "------"); + if (has_swap) { + ss << ::android::base::StringPrintf("%7s %7s %7s ", "------", "------", "------"); + if (has_zram) { + ss << ::android::base::StringPrintf("%7s ", "------"); + } + } + } + + ss << ::android::base::StringPrintf("%s", "------"); +} + +static void print_totals(std::stringstream& ss) { + ss << ::android::base::StringPrintf("%5s ", ""); + if (show_oomadj) { + ss << ::android::base::StringPrintf("%5s ", ""); + } + + if (show_wss) { + ss << ::android::base::StringPrintf("%7s %6" PRIu64 "K %6" PRIu64 "K ", "", + total_pss / 1024, total_uss / 1024); + } else { + ss << ::android::base::StringPrintf("%8s %7s %6" PRIu64 "K %6" PRIu64 "K ", "", "", + total_pss / 1024, total_uss / 1024); + if (has_swap) { + ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_swap / 1024); + ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_pswap / 1024); + ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_uswap / 1024); + if (has_zram) { + ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_zswap / 1024); + } + } + } + ss << "TOTAL"; +} + +static void print_sysmeminfo(std::stringstream& ss, ::android::meminfo::SysMemInfo& smi) { + if (has_swap) { + ss << ::android::base::StringPrintf("ZRAM: %" PRIu64 "K physical used for %" PRIu64 + "K in swap " + "(%" PRIu64 "K total swap)", + smi.mem_zram_kb(), + (smi.mem_swap_kb() - smi.mem_swap_free_kb()), + smi.mem_swap_kb()) + << std::endl; + } + + ss << ::android::base::StringPrintf(" RAM: %" PRIu64 "K total, %" PRIu64 "K free, %" PRIu64 + "K buffers, " + "%" PRIu64 "K cached, %" PRIu64 "K shmem, %" PRIu64 + "K slab", + smi.mem_total_kb(), smi.mem_free_kb(), smi.mem_buffers_kb(), + smi.mem_cached_kb(), smi.mem_shmem_kb(), smi.mem_slab_kb()); +} + +int main(int argc, char* argv[]) { + auto pss_sort = [](ProcessRecord& a, ProcessRecord& b) { + MemUsage stats_a = show_wss ? a.Wss() : a.Usage(); + MemUsage stats_b = show_wss ? b.Wss() : b.Usage(); + return reverse_sort ? stats_a.pss < stats_b.pss : stats_a.pss > stats_b.pss; + }; + + auto uss_sort = [](ProcessRecord& a, ProcessRecord& b) { + MemUsage stats_a = show_wss ? a.Wss() : a.Usage(); + MemUsage stats_b = show_wss ? b.Wss() : b.Usage(); + return reverse_sort ? stats_a.uss < stats_b.uss : stats_a.uss > stats_b.uss; + }; + + auto rss_sort = [](ProcessRecord& a, ProcessRecord& b) { + MemUsage stats_a = show_wss ? a.Wss() : a.Usage(); + MemUsage stats_b = show_wss ? b.Wss() : b.Usage(); + return reverse_sort ? stats_a.rss < stats_b.pss : stats_a.pss > stats_b.pss; + }; + + auto vss_sort = [](ProcessRecord& a, ProcessRecord& b) { + MemUsage stats_a = show_wss ? a.Wss() : a.Usage(); + MemUsage stats_b = show_wss ? b.Wss() : b.Usage(); + return reverse_sort ? stats_a.vss < stats_b.vss : stats_a.vss > stats_b.vss; + }; + + auto swap_sort = [](ProcessRecord& a, ProcessRecord& b) { + MemUsage stats_a = show_wss ? a.Wss() : a.Usage(); + MemUsage stats_b = show_wss ? b.Wss() : b.Usage(); + return reverse_sort ? stats_a.swap < stats_b.swap : stats_a.swap > stats_b.swap; + }; + + auto oomadj_sort = [](ProcessRecord& a, ProcessRecord& b) { + return reverse_sort ? a.oomadj() < b.oomadj() : a.oomadj() > b.oomadj(); + }; + + // default PSS sort + std::function proc_sort = pss_sort; + + // count all pages by default + uint64_t pgflags = 0; + uint64_t pgflags_mask = 0; + + int opt; + while ((opt = getopt(argc, argv, "cChkoprRsuvwW")) != -1) { + switch (opt) { + case 'c': + pgflags = 0; + pgflags_mask = (1 << KPF_SWAPBACKED); + break; + case 'C': + pgflags = (1 << KPF_SWAPBACKED); + pgflags_mask = (1 << KPF_SWAPBACKED); + break; + case 'h': + usage(argv[0]); + return 0; + break; + case 'k': + pgflags = (1 << KPF_KSM); + pgflags_mask = (1 << KPF_KSM); + break; + case 'o': + proc_sort = oomadj_sort; + show_oomadj = true; + break; + case 'p': + proc_sort = pss_sort; + break; + case 'r': + proc_sort = rss_sort; + break; + case 'R': + reverse_sort = true; + break; + case 's': + proc_sort = swap_sort; + break; + case 'u': + proc_sort = uss_sort; + break; + case 'v': + proc_sort = vss_sort; + break; + case 'w': + show_wss = true; + break; + case 'W': + reset_wss = true; + break; + default: + abort(); + } + } + + std::vector pids; + std::vector procs; + if (reset_wss) { + if (!read_all_pids(&pids, + [&](pid_t pid) -> bool { return ProcMemInfo::ResetWorkingSet(pid); })) { + std::cerr << "Failed to reset working set of all processes" << std::endl; + exit(EXIT_FAILURE); + } + // we are done, all other options passed to procrank are ignored in the presence of '-W' + return 0; + } + + ::android::meminfo::SysMemInfo smi; + if (!smi.ReadMemInfo()) { + std::cerr << "Failed to get system memory info" << std::endl; + exit(EXIT_FAILURE); + } + + // Figure out swap and zram + uint64_t swap_total = smi.mem_swap_kb() * 1024; + has_swap = swap_total > 0; + // Allocate the swap array + auto swap_offset_array = std::make_unique(swap_total / getpagesize()); + if (has_swap) { + has_zram = smi.mem_zram_kb() > 0; + if (has_zram) { + zram_compression_ratio = static_cast(smi.mem_zram_kb()) / + (smi.mem_swap_kb() - smi.mem_swap_free_kb()); + } + } + + auto mark_swap_usage = [&](pid_t pid) -> bool { + ProcessRecord proc(pid, show_wss, pgflags, pgflags_mask); + if (!proc.valid()) { + std::cerr << "Failed to create process record for: " << pid << std::endl; + return false; + } + + // Skip processes with no memory mappings + uint64_t vss = show_wss ? proc.Wss().vss : proc.Usage().vss; + if (vss == 0) return true; + + // collect swap_offset counts from all processes in 1st pass + if (!show_wss && has_swap && + !count_swap_offsets(proc, swap_offset_array.get(), swap_total / getpagesize())) { + std::cerr << "Failed to count swap offsets for process: " << pid << std::endl; + return false; + } + + procs.push_back(std::move(proc)); + return true; + }; + + // Get a list of all pids currently running in the system in + // 1st pass through all processes. Mark each swap offset used by the process as we find them + // for calculating proportional swap usage later. + if (!read_all_pids(&pids, mark_swap_usage)) { + std::cerr << "Failed to read all pids from the system" << std::endl; + exit(EXIT_FAILURE); + } + + std::stringstream ss; + if (procs.empty()) { + // This would happen in corner cases where procrank is being run to find KSM usage on a + // system with no KSM and combined with working set determination as follows + // procrank -w -u -k + // procrank -w -s -k + // procrank -w -o -k + ss << "" << std::endl << std::endl; + print_sysmeminfo(ss, smi); + ss << std::endl; + std::cout << ss.str(); + return 0; + } + + // Sort all process records, default is PSS descending + std::sort(procs.begin(), procs.end(), proc_sort); + + // start dumping output in string stream + print_header(ss); + ss << std::endl; + + // 2nd pass to calculate and get per process stats to add them up + print_processes(ss, procs, swap_offset_array.get()); + + // Add separator to output + print_separator(ss); + ss << std::endl; + + // Add totals to output + print_totals(ss); + ss << std::endl << std::endl; + + // Add system information at the end + print_sysmeminfo(ss, smi); + ss << std::endl; + + // dump on the screen + std::cout << ss.str(); + + return 0; +}