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/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..92375d31c 100644 --- a/libmeminfo/include/meminfo/procmeminfo.h +++ b/libmeminfo/include/meminfo/procmeminfo.h @@ -29,13 +29,16 @@ namespace meminfo { class ProcMemInfo final { // Per-process memory accounting public: - ProcMemInfo(pid_t pid, bool get_wss = false); + // 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(); const MemUsage& Usage(); const MemUsage& Wss(); + const std::vector& SwapOffsets(); - bool WssReset(); ~ProcMemInfo() = default; private: @@ -44,11 +47,14 @@ class ProcMemInfo final { pid_t pid_; bool get_wss_; + uint64_t pgflags_; + uint64_t pgflags_mask_; std::vector maps_; 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..885be1d67 100644 --- a/libmeminfo/include/meminfo/sysmeminfo.h +++ b/libmeminfo/include/meminfo/sysmeminfo.h @@ -28,6 +28,21 @@ 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* 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,24 +53,25 @@ 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_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 7a2be4172..b7a4b6b6f 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 { @@ -118,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) { @@ -221,6 +245,93 @@ 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; + 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 @@ -288,6 +399,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/procmeminfo.cpp b/libmeminfo/procmeminfo.cpp index fe91d250b..8867e147f 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; @@ -51,48 +53,78 @@ 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) { - if (!ReadMaps(get_wss_)) { - LOG(ERROR) << "Failed to read maps for Process " << pid_; - } -} - -const std::vector& ProcMemInfo::Maps() { - return maps_; -} - -const MemUsage& ProcMemInfo::Usage() { - if (get_wss_) { - LOG(WARNING) << "Trying to read memory usage from working set object"; - } - return usage_; -} - -const MemUsage& ProcMemInfo::Wss() { - if (!get_wss_) { - LOG(WARNING) << "Trying to read working set when there is none"; - } - - return wss_; -} - -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_); +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; } - wss_.clear(); 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) {} + +const std::vector& ProcMemInfo::Maps() { + if (maps_.empty() && !ReadMaps(get_wss_)) { + LOG(ERROR) << "Failed to read maps for Process " << pid_; + } + + return maps_; +} + +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_; +} + +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() { + 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( @@ -115,8 +147,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,18 +185,24 @@ 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; } 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; } + // 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_; + swap_offsets_.clear(); return false; } diff --git a/libmeminfo/sysmeminfo.cpp b/libmeminfo/sysmeminfo.cpp index 50fa2136d..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,9 +41,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::kMemMapped, SysMemInfo::kMemVmallocUsed, + SysMemInfo::kMemPageTables, SysMemInfo::kMemKernelStack, }; bool SysMemInfo::ReadMemInfo(const std::string& path) { @@ -99,6 +105,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 +114,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,11 +126,75 @@ bool SysMemInfo::ReadMemInfo(const std::vector& tags, const std::st p++; } if (*p) p++; + lineno++; } return true; } #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 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/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); } 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; +}