Snap for 10447354 from 9351dcecd2a462c9ff908dc915994b746c0697ee to mainline-wifi-release
Change-Id: I0c7ae9ae2ec784e35025473b8eb80c658994c761
diff --git a/OWNERS b/OWNERS
index 5a7a0bd..dd9c4c8 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,2 +1,6 @@
-# Bug component: 391836
+# Bug component: 1293033
surenb@google.com
+tjmercier@google.com
+kaleshsingh@google.com
+jyescas@google.com
+carlosgalo@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 6b7fd71..c8dbf77 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,2 +1,5 @@
[Builtin Hooks]
clang_format = true
+
+[Builtin Hooks Options]
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 8dae108..50532e2 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "libmeminfo_test"
}
],
- "hwasan-postsubmit": [
+ "hwasan-presubmit": [
{
"name": "libmeminfo_test"
}
diff --git a/include/meminfo/meminfo.h b/include/meminfo/meminfo.h
index 545f27d..199f7c8 100644
--- a/include/meminfo/meminfo.h
+++ b/include/meminfo/meminfo.h
@@ -45,6 +45,7 @@
uint64_t file_pmd_mapped;
uint64_t shared_hugetlb;
uint64_t private_hugetlb;
+ uint64_t locked;
uint64_t thp;
@@ -64,6 +65,7 @@
file_pmd_mapped(0),
shared_hugetlb(0),
private_hugetlb(0),
+ locked(0),
thp(0) {}
~MemUsage() = default;
diff --git a/include/meminfo/procmeminfo.h b/include/meminfo/procmeminfo.h
index 8e55b88..b53faa6 100644
--- a/include/meminfo/procmeminfo.h
+++ b/include/meminfo/procmeminfo.h
@@ -19,6 +19,7 @@
#include <sys/types.h>
#include <string>
+#include <string_view>
#include <vector>
#include "meminfo.h"
@@ -49,14 +50,20 @@
const std::vector<Vma>& MapsWithoutUsageStats();
// If MapsWithoutUsageStats was called, this function will fill in
- // usage stats for this single vma.
- bool FillInVmaStats(Vma& vma);
+ // usage stats for this single vma. If 'use_kb' is true, the vma's
+ // usage will be populated in kilobytes instead of bytes.
+ bool FillInVmaStats(Vma& vma, bool use_kb = false);
- // Collect all 'vma' or 'maps' from /proc/<pid>/smaps and store them in 'maps_'. Returns a
- // constant reference to the vma vector after the collection is done.
+ // If ReadMaps (with get_usage_stats == false) or MapsWithoutUsageStats was
+ // called, this function will fill in usage stats for all vmas in 'maps_'.
+ bool GetUsageStats(bool get_wss, bool use_pageidle = false, bool swap_only = false);
+
+ // Collect all 'vma' or 'maps' from /proc/<pid>/smaps and store them in 'maps_'. If
+ // 'collect_usage' is 'true', this method will populate 'usage_' as vmas are being
+ // collected. Returns a constant reference to the vma vector after the collection is done.
//
// Each 'struct Vma' is *fully* populated by this method (unlike SmapsOrRollup).
- const std::vector<Vma>& Smaps(const std::string& path = "");
+ const std::vector<Vma>& Smaps(const std::string& path = "", bool collect_usage = false);
// If 'use_smaps' is 'true' this method reads /proc/<pid>/smaps and calls the callback()
// for each vma or map that it finds, else if 'use_smaps' is false /proc/<pid>/maps is
@@ -69,6 +76,16 @@
// Returns false in case of failure during parsing.
bool ForEachVmaFromMaps(const VmaCallback& callback);
+ // Similar to other VMA reading methods, except this one allows passing a reusable buffer
+ // to store the /proc/<pid>/maps content
+ bool ForEachVmaFromMaps(const VmaCallback& callback, std::string& mapsBuffer);
+
+ // Takes the existing VMAs in 'maps_' and calls the callback() for each one
+ // of them. This is intended to avoid parsing /proc/<pid>/maps or
+ // /proc/<pid>/smaps twice.
+ // Returns false if 'maps_' is empty.
+ bool ForEachExistingVma(const VmaCallback& callback);
+
// Used to parse either of /proc/<pid>/{smaps, smaps_rollup} and record the process's
// Pss and Private memory usage in 'stats'. In particular, the method only populates the fields
// of the MemUsage structure that are intended to be used by Android's periodic Pss collection.
@@ -138,5 +155,14 @@
// as /proc/<pid>/smaps or /proc/<pid>/smaps_rollup
bool SmapsOrRollupPssFromFile(const std::string& path, uint64_t* pss);
+// The output format that can be specified by user.
+enum class Format { INVALID = 0, RAW, JSON, CSV };
+
+Format GetFormat(std::string_view arg);
+
+std::string EscapeCsvString(const std::string& raw);
+
+std::string EscapeJsonString(const std::string& raw);
+
} // namespace meminfo
} // namespace android
diff --git a/include/meminfo/sysmeminfo.h b/include/meminfo/sysmeminfo.h
index a17f8ef..a695584 100644
--- a/include/meminfo/sysmeminfo.h
+++ b/include/meminfo/sysmeminfo.h
@@ -52,15 +52,24 @@
static constexpr const char kMemActive[] = "Active:";
static constexpr const char kMemInactive[] = "Inactive:";
static constexpr const char kMemUnevictable[] = "Unevictable:";
-
+ static constexpr const char kMemAvailable[] = "MemAvailable:";
+ static constexpr const char kMemActiveAnon[] = "Active(anon):";
+ static constexpr const char kMemInactiveAnon[] = "Inactive(anon):";
+ static constexpr const char kMemActiveFile[] = "Active(file):";
+ static constexpr const char kMemInactiveFile[] = "Inactive(file):";
+ static constexpr const char kMemCmaTotal[] = "CmaTotal:";
+ static constexpr const char kMemCmaFree[] = "CmaFree:";
static constexpr std::initializer_list<std::string_view> kDefaultSysMemInfoTags = {
- 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, SysMemInfo::kMemKReclaimable,
- SysMemInfo::kMemActive, SysMemInfo::kMemInactive, SysMemInfo::kMemUnevictable,
+ 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, SysMemInfo::kMemKReclaimable,
+ SysMemInfo::kMemActive, SysMemInfo::kMemInactive, SysMemInfo::kMemUnevictable,
+ SysMemInfo::kMemAvailable, SysMemInfo::kMemActiveAnon, SysMemInfo::kMemInactiveAnon,
+ SysMemInfo::kMemActiveFile, SysMemInfo::kMemInactiveFile, SysMemInfo::kMemCmaTotal,
+ SysMemInfo::kMemCmaFree,
};
SysMemInfo() = default;
@@ -78,31 +87,48 @@
uint64_t ReadVmallocInfo();
// getters
- 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_reclaimable_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_[kMemKernelStack]; }
- uint64_t mem_kreclaimable_kb() { return mem_in_kb_[kMemKReclaimable]; }
- uint64_t mem_active_kb() { return mem_in_kb_[kMemActive]; }
- uint64_t mem_inactive_kb() { return mem_in_kb_[kMemInactive]; }
- uint64_t mem_unevictable_kb() { return mem_in_kb_[kMemUnevictable]; }
- uint64_t mem_zram_kb(const char* zram_dev = nullptr);
+ uint64_t mem_total_kb() const { return find_mem_by_tag(kMemTotal); }
+ uint64_t mem_free_kb() const { return find_mem_by_tag(kMemFree); }
+ uint64_t mem_buffers_kb() const { return find_mem_by_tag(kMemBuffers); }
+ uint64_t mem_cached_kb() const { return find_mem_by_tag(kMemCached); }
+ uint64_t mem_shmem_kb() const { return find_mem_by_tag(kMemShmem); }
+ uint64_t mem_slab_kb() const { return find_mem_by_tag(kMemSlab); }
+ uint64_t mem_slab_reclaimable_kb() const { return find_mem_by_tag(kMemSReclaim); }
+ uint64_t mem_slab_unreclaimable_kb() const { return find_mem_by_tag(kMemSUnreclaim); }
+ uint64_t mem_swap_kb() const { return find_mem_by_tag(kMemSwapTotal); }
+ uint64_t mem_swap_free_kb() const { return find_mem_by_tag(kMemSwapFree); }
+ uint64_t mem_mapped_kb() const { return find_mem_by_tag(kMemMapped); }
+ uint64_t mem_vmalloc_used_kb() const { return find_mem_by_tag(kMemVmallocUsed); }
+ uint64_t mem_page_tables_kb() const { return find_mem_by_tag(kMemPageTables); }
+ uint64_t mem_kernel_stack_kb() const { return find_mem_by_tag(kMemKernelStack); }
+ uint64_t mem_kreclaimable_kb() const { return find_mem_by_tag(kMemKReclaimable); }
+ uint64_t mem_active_kb() const { return find_mem_by_tag(kMemActive); }
+ uint64_t mem_inactive_kb() const { return find_mem_by_tag(kMemInactive); }
+ uint64_t mem_unevictable_kb() const { return find_mem_by_tag(kMemUnevictable); }
+ uint64_t mem_available_kb() const { return find_mem_by_tag(kMemAvailable); }
+ uint64_t mem_active_anon_kb() const { return find_mem_by_tag(kMemActiveAnon); }
+ uint64_t mem_inactive_anon_kb() const { return find_mem_by_tag(kMemInactiveAnon); }
+ uint64_t mem_active_file_kb() const { return find_mem_by_tag(kMemActiveFile); }
+ uint64_t mem_inactive_file_kb() const { return find_mem_by_tag(kMemInactiveFile); }
+ uint64_t mem_cma_total_kb() const { return find_mem_by_tag(kMemCmaTotal); }
+ uint64_t mem_cma_free_kb() const { return find_mem_by_tag(kMemCmaFree); }
+ uint64_t mem_zram_kb(const char* zram_dev = nullptr) const;
+ uint64_t mem_compacted_kb(const char* zram_dev = nullptr);
private:
std::map<std::string_view, uint64_t> mem_in_kb_;
- bool MemZramDevice(const char* zram_dev, uint64_t* mem_zram_dev);
+ bool MemZramDevice(const char* zram_dev, uint64_t* mem_zram_dev) const;
+ bool GetTotalMemCompacted(const char* zram_dev, uint64_t* out_mem_compacted);
bool ReadMemInfo(const char* path, size_t ntags, const std::string_view* tags,
std::function<void(std::string_view, uint64_t)> store_val);
+ // Convenience function to avoid duplicating code for each memory category.
+ uint64_t find_mem_by_tag(const char kTag[]) const {
+ auto it = mem_in_kb_.find(kTag);
+ if (it != mem_in_kb_.end()) {
+ return it->second;
+ }
+ return 0;
+ }
};
// Parse /proc/vmallocinfo and return total physical memory mapped
diff --git a/libdmabufinfo/TEST_MAPPING b/libdmabufinfo/TEST_MAPPING
index 1a8a5c6..29b7dfa 100644
--- a/libdmabufinfo/TEST_MAPPING
+++ b/libdmabufinfo/TEST_MAPPING
@@ -1,15 +1,7 @@
{
"presubmit": [
{
- "name": "dmabufinfo_test",
- "options": [
- {
- "include-filter": "*DmaBufSysfsStatsParser.*"
- },
- {
- "include-filter": "*DmaBufProcessStatsTest.*"
- }
- ]
+ "name": "dmabufinfo_test"
}
]
}
diff --git a/libdmabufinfo/dmabuf_sysfs_stats.cpp b/libdmabufinfo/dmabuf_sysfs_stats.cpp
index e5a6486..38eb5c2 100644
--- a/libdmabufinfo/dmabuf_sysfs_stats.cpp
+++ b/libdmabufinfo/dmabuf_sysfs_stats.cpp
@@ -30,7 +30,7 @@
namespace android {
namespace dmabufinfo {
-static bool ReadUintFromFile(const std::string& path, unsigned int* val) {
+static bool ReadUintFromFile(const std::string& path, uint64_t* val) {
std::string temp;
if (!android::base::ReadFileToString(path, &temp)) {
@@ -52,6 +52,12 @@
return android::base::ReadFileToString(exporter_path, exporter);
}
+bool ReadBufferSize(unsigned int inode, uint64_t* size, const std::string& dmabuf_sysfs_path) {
+ std::string size_path =
+ ::android::base::StringPrintf("%s/%u/size", dmabuf_sysfs_path.c_str(), inode);
+ return ReadUintFromFile(size_path, size);
+}
+
bool GetDmabufSysfsStats(DmabufSysfsStats* stats, const std::string& dmabuf_sysfs_stats_path) {
std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(dmabuf_sysfs_stats_path.c_str()), closedir);
@@ -130,7 +136,7 @@
"%s/%s", dmabuf_sysfs_stats_path.c_str(), dent->d_name);
// Read size of the buffer
- unsigned int buf_size = 0;
+ uint64_t buf_size = 0;
std::string size_path = buf_entry_path + "/size";
if (!ReadUintFromFile(size_path, &buf_size)) return false;
*total_exported += buf_size;
diff --git a/libdmabufinfo/dmabufinfo.cpp b/libdmabufinfo/dmabufinfo.cpp
index cb639c1..ff532b0 100644
--- a/libdmabufinfo/dmabufinfo.cpp
+++ b/libdmabufinfo/dmabufinfo.cpp
@@ -23,6 +23,7 @@
#include <unistd.h>
#include <filesystem>
+#include <fstream>
#include <memory>
#include <string>
#include <vector>
@@ -55,8 +56,8 @@
const std::string& procfs_path) {
std::string fdinfo =
::android::base::StringPrintf("%s/%d/fdinfo/%d", procfs_path.c_str(), pid, fd);
- auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(fdinfo.c_str(), "re"), fclose};
- if (fp == nullptr) {
+ std::ifstream fp(fdinfo);
+ if (!fp) {
if (errno == ENOENT) {
return NOT_FOUND;
}
@@ -64,45 +65,43 @@
return ERROR;
}
- char* line = nullptr;
- size_t len = 0;
- while (getline(&line, &len, fp.get()) > 0) {
+ for (std::string file_line; getline(fp, file_line);) {
+ const char* line = file_line.c_str();
switch (line[0]) {
case 'c':
if (strncmp(line, "count:", 6) == 0) {
- char* c = line + 6;
+ const char* c = line + 6;
*count = strtoull(c, nullptr, 10);
}
break;
case 'e':
if (strncmp(line, "exp_name:", 9) == 0) {
- char* c = line + 9;
+ const char* c = line + 9;
*exporter = ::android::base::Trim(c);
*is_dmabuf_file = true;
}
break;
case 'n':
if (strncmp(line, "name:", 5) == 0) {
- char* c = line + 5;
- *name = ::android::base::Trim(std::string(c));
+ const char* c = line + 5;
+ *name = ::android::base::Trim(c);
}
break;
case 's':
if (strncmp(line, "size:", 5) == 0) {
- char* c = line + 5;
+ const char* c = line + 5;
*size = strtoull(c, nullptr, 10);
}
break;
case 'i':
if (strncmp(line, "ino:", 4) == 0) {
- char* c = line + 4;
+ const char* c = line + 4;
*inode = strtoull(c, nullptr, 10);
}
break;
}
}
- free(line);
return OK;
}
@@ -203,15 +202,12 @@
const std::string& procfs_path,
const std::string& dmabuf_sysfs_path) {
std::string mapspath = ::android::base::StringPrintf("%s/%d/maps", procfs_path.c_str(), pid);
- auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(mapspath.c_str(), "re"), fclose};
- if (fp == nullptr) {
- LOG(ERROR) << "Failed to open maps for pid: " << pid;
+ std::ifstream fp(mapspath);
+ if (!fp) {
+ LOG(ERROR) << "Failed to open " << mapspath << " for pid: " << pid;
return false;
}
- char* line = nullptr;
- size_t len = 0;
-
// Process the map if it is dmabuf. Add map reference to existing object in 'dmabufs'
// if it was already found. If it wasn't create a new one and append it to 'dmabufs'
auto account_dmabuf = [&](const android::procinfo::MapInfo& mapinfo) {
@@ -231,56 +227,32 @@
// We have a new buffer, but unknown count and name and exporter name
// Try to lookup exporter name in sysfs
std::string exporter;
- if (!ReadBufferExporter(mapinfo.inode, &exporter, dmabuf_sysfs_path)) {
+ bool sysfs_stats = ReadBufferExporter(mapinfo.inode, &exporter, dmabuf_sysfs_path);
+ if (!sysfs_stats) {
exporter = "<unknown>";
}
- DmaBuffer& dbuf = dmabufs->emplace_back(mapinfo.inode, mapinfo.end - mapinfo.start, 0,
- exporter, "<unknown>");
+
+ // Using the VMA range as the size of the buffer can be misleading,
+ // due to partially mapped buffers or VMAs that extend beyond the
+ // buffer size.
+ //
+ // Attempt to retrieve the real buffer size from sysfs.
+ uint64_t size = 0;
+ if (!sysfs_stats || !ReadBufferSize(mapinfo.inode, &size, dmabuf_sysfs_path)) {
+ size = mapinfo.end - mapinfo.start;
+ }
+
+ DmaBuffer& dbuf = dmabufs->emplace_back(mapinfo.inode, size, 0, exporter, "<unknown>");
dbuf.AddMapRef(pid);
};
- while (getline(&line, &len, fp.get()) > 0) {
- if (!::android::procinfo::ReadMapFileContent(line, account_dmabuf)) {
- LOG(ERROR) << "Failed to parse maps for pid: " << pid;
+ for (std::string line; getline(fp, line);) {
+ if (!::android::procinfo::ReadMapFileContent(line.data(), account_dmabuf)) {
+ LOG(ERROR) << "Failed to parse " << mapspath << " for pid: " << pid;
return false;
}
}
- free(line);
- return true;
-}
-
-bool ReadDmaBufInfo(std::vector<DmaBuffer>* dmabufs, const std::string& path) {
- auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
- if (fp == nullptr) {
- LOG(ERROR) << "Failed to open dmabuf info from debugfs";
- return false;
- }
-
- char* line = nullptr;
- size_t len = 0;
- dmabufs->clear();
- while (getline(&line, &len, fp.get()) > 0) {
- // The new dmabuf bufinfo format adds inode number and a name at the end
- // We are looking for lines as follows:
- // size flags mode count exp_name ino name
- // 01048576 00000002 00000007 00000001 ion 00018758 CAMERA
- // 01048576 00000002 00000007 00000001 ion 00018758
- uint64_t size, count, inode;
- char* exporter_name = nullptr;
- char* name = nullptr;
- int matched = sscanf(line, "%" SCNu64 "%*x %*x %" SCNu64 " %ms %" SCNu64 " %ms", &size,
- &count, &exporter_name, &inode, &name);
- if (matched < 4) {
- continue;
- }
- dmabufs->emplace_back((ino_t)inode, size, count, exporter_name, matched > 4 ? name : "");
- free(exporter_name);
- free(name);
- }
-
- free(line);
-
return true;
}
@@ -302,7 +274,7 @@
return true;
}
-bool ReadDmaBufs(std::vector<DmaBuffer>* bufs) {
+bool ReadProcfsDmaBufs(std::vector<DmaBuffer>* bufs) {
bufs->clear();
std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir("/proc"), closedir);
diff --git a/libdmabufinfo/dmabufinfo_test.cpp b/libdmabufinfo/dmabufinfo_test.cpp
index c7f6581..02a8b17 100644
--- a/libdmabufinfo/dmabufinfo_test.cpp
+++ b/libdmabufinfo/dmabufinfo_test.cpp
@@ -21,6 +21,7 @@
#include <sys/mman.h>
#include <sys/types.h>
+#include <filesystem>
#include <fstream>
#include <string>
#include <unordered_map>
@@ -191,45 +192,6 @@
EXPECT_EQ((_ref != _maprefs.end()), _expect); \
} while (0)
-TEST(DmaBufInfoParser, TestReadDmaBufInfo) {
- std::string bufinfo = R"bufinfo(00045056 00000002 00000007 00000002 ion 00022069
- Attached Devices:
-Total 0 devices attached
-01048576 00000002 00000007 00000001 ion 00019834 CAMERA
- Attached Devices:
- soc:qcom,cam_smmu:msm_cam_smmu_icp
-Total 1 devices attached)bufinfo";
-
- TemporaryFile tf;
- ASSERT_TRUE(tf.fd != -1);
- ASSERT_TRUE(::android::base::WriteStringToFd(bufinfo, tf.fd));
- std::string path = std::string(tf.path);
-
- std::vector<DmaBuffer> dmabufs;
- EXPECT_TRUE(ReadDmaBufInfo(&dmabufs, path));
-
- EXPECT_EQ(dmabufs.size(), 2UL);
-
- EXPECT_EQ(dmabufs[0].size(), 45056UL);
- EXPECT_EQ(dmabufs[0].inode(), 22069UL);
- EXPECT_EQ(dmabufs[0].count(), 2UL);
- EXPECT_EQ(dmabufs[0].exporter(), "ion");
- EXPECT_TRUE(dmabufs[0].name().empty());
- EXPECT_EQ(dmabufs[0].total_refs(), 0ULL);
- EXPECT_TRUE(dmabufs[0].fdrefs().empty());
- EXPECT_TRUE(dmabufs[0].maprefs().empty());
-
- EXPECT_EQ(dmabufs[1].size(), 1048576UL);
- EXPECT_EQ(dmabufs[1].inode(), 19834UL);
- EXPECT_EQ(dmabufs[1].count(), 1UL);
- EXPECT_EQ(dmabufs[1].exporter(), "ion");
- EXPECT_FALSE(dmabufs[1].name().empty());
- EXPECT_EQ(dmabufs[1].name(), "CAMERA");
- EXPECT_EQ(dmabufs[1].total_refs(), 0ULL);
- EXPECT_TRUE(dmabufs[1].fdrefs().empty());
- EXPECT_TRUE(dmabufs[1].maprefs().empty());
-}
-
class DmaBufSysfsStatsParser : public ::testing::Test {
public:
virtual void SetUp() {
@@ -440,7 +402,7 @@
AddMapEntries(map_entries);
AddSysfsDmaBufStats(2, 1024, 2); // Dmabuf 1
- AddSysfsDmaBufStats(3, 1024, 1); // Dmabuf 2
+ AddSysfsDmaBufStats(3, 2048, 1); // Dmabuf 2
std::vector<DmaBuffer> dmabufs;
ASSERT_TRUE(ReadDmaBufMapRefs(pid, &dmabufs, procfs_path, dmabuf_sysfs_path));
@@ -490,6 +452,13 @@
bool is_valid() { return (ion_fd >= 0 && ion_heap_mask > 0); }
+ bool is_using_dmabuf_heaps() {
+ // We can verify that a device is running on dmabuf-heaps by checking that
+ // the `dev/ion` is missing, while `dev/dma_heap` is present.
+ // https://source.android.com/docs/core/architecture/kernel/dma-buf-heaps
+ return !fs::is_directory("/dev/ion") && fs::is_directory("/dev/dma_heap");
+ }
+
unique_fd allocate(uint64_t size, const std::string& name) {
int fd;
int err = ion_alloc_fd(ion_fd, size, 0, ion_heap_mask, 0, &fd);
@@ -570,6 +539,11 @@
TEST_F(DmaBufTester, TestFdRef) {
// Test if a dma buffer is found while the corresponding file descriptor
// is open
+
+ if (is_using_dmabuf_heaps()) {
+ GTEST_SKIP();
+ }
+
ASSERT_TRUE(is_valid());
pid_t pid = getpid();
std::vector<DmaBuffer> dmabufs;
@@ -594,6 +568,11 @@
TEST_F(DmaBufTester, TestMapRef) {
// Test to make sure we can find a buffer if the fd is closed but the buffer
// is mapped
+
+ if (is_using_dmabuf_heaps()) {
+ GTEST_SKIP();
+ }
+
ASSERT_TRUE(is_valid());
pid_t pid = getpid();
std::vector<DmaBuffer> dmabufs;
@@ -635,6 +614,10 @@
// Each time a shared buffer is received over a socket, the remote process
// will take an extra reference on it.
+ if (is_using_dmabuf_heaps()) {
+ GTEST_SKIP();
+ }
+
ASSERT_TRUE(is_valid());
pid_t pid = getpid();
@@ -678,6 +661,10 @@
// dup()ing an fd will make this process take an extra reference on the
// shared buffer.
+ if (is_using_dmabuf_heaps()) {
+ GTEST_SKIP();
+ }
+
ASSERT_TRUE(is_valid());
pid_t pid = getpid();
@@ -708,6 +695,11 @@
TEST_F(DmaBufTester, ForkTest) {
// fork()ing a child will cause the child to automatically take a reference
// on any existing shared buffers.
+
+ if (is_using_dmabuf_heaps()) {
+ GTEST_SKIP();
+ }
+
ASSERT_TRUE(is_valid());
pid_t pid = getpid();
diff --git a/libdmabufinfo/include/dmabufinfo/dmabuf_sysfs_stats.h b/libdmabufinfo/include/dmabufinfo/dmabuf_sysfs_stats.h
index a7c0841..e09764d 100644
--- a/libdmabufinfo/include/dmabufinfo/dmabuf_sysfs_stats.h
+++ b/libdmabufinfo/include/dmabufinfo/dmabuf_sysfs_stats.h
@@ -30,9 +30,9 @@
* @size: Size of the buffer.
*/
struct DmabufInfo {
- unsigned int inode;
+ unsigned long inode;
std::string exp_name;
- unsigned int size;
+ uint64_t size;
};
struct DmabufTotal {
@@ -84,5 +84,9 @@
bool ReadBufferExporter(unsigned int inode, std::string* exporter,
const std::string& dmabuf_sysfs_path = "/sys/kernel/dmabuf/buffers");
+/* Reads the size of the DMA buffer with @inode */
+bool ReadBufferSize(unsigned int inode, uint64_t* size,
+ const std::string& dmabuf_sysfs_path = "/sys/kernel/dmabuf/buffers");
+
} // namespace dmabufinfo
} // namespace android
diff --git a/libdmabufinfo/include/dmabufinfo/dmabufinfo.h b/libdmabufinfo/include/dmabufinfo/dmabufinfo.h
index bb4aff1..9b5c945 100644
--- a/libdmabufinfo/include/dmabufinfo/dmabufinfo.h
+++ b/libdmabufinfo/include/dmabufinfo/dmabufinfo.h
@@ -62,7 +62,7 @@
void SetName(const std::string& name) { name_ = name; }
void SetExporter(const std::string& exporter) { exporter_ = exporter; }
void SetCount(uint64_t count) { count_ = count; }
- uint64_t Pss(pid_t pid) const { return maprefs_.count(pid) > 0 ? size_ / maprefs_.size() : 0; }
+ uint64_t Pss() const { return size_ / pids_.size(); }
bool operator==(const DmaBuffer& rhs) {
return (inode_ == rhs.inode()) && (size_ == rhs.size()) && (name_ == rhs.name()) &&
@@ -88,13 +88,6 @@
}
};
-// Read and return current dma buf objects from
-// DEBUGFS/dma_buf/bufinfo. The references to each dma buffer are not
-// populated here and will return an empty vector.
-// Returns false if something went wrong with the function, true otherwise.
-bool ReadDmaBufInfo(std::vector<DmaBuffer>* dmabufs,
- const std::string& path = "/sys/kernel/debug/dma_buf/bufinfo");
-
// Read and return dmabuf objects for a given process without the help
// of DEBUGFS
// Returns false if something went wrong with the function, true otherwise.
@@ -130,7 +123,7 @@
// Writes DmaBuffer info into an existing vector (which will be cleared first.)
// Will include all DmaBuffers, whether thay are retained or mapped.
// Returns true on success, otherwise false.
-bool ReadDmaBufs(std::vector<DmaBuffer>* bufs);
+bool ReadProcfsDmaBufs(std::vector<DmaBuffer>* bufs);
} // namespace dmabufinfo
} // namespace android
diff --git a/libdmabufinfo/tools/dmabuf_dump.cpp b/libdmabufinfo/tools/dmabuf_dump.cpp
index 65ebd08..8798539 100644
--- a/libdmabufinfo/tools/dmabuf_dump.cpp
+++ b/libdmabufinfo/tools/dmabuf_dump.cpp
@@ -135,16 +135,12 @@
// Create a reverse map from pid to dmabufs
std::unordered_map<pid_t, std::set<ino_t>> pid_to_inodes = {};
- uint64_t total_size = 0; // Total size of dmabufs in the system
- uint64_t kernel_rss = 0; // Total size of dmabufs NOT mapped or opened by a process
+ uint64_t userspace_size = 0; // Size of userspace dmabufs in the system
for (auto& buf : bufs) {
for (auto pid : buf.pids()) {
pid_to_inodes[pid].insert(buf.inode());
}
- total_size += buf.size();
- if (buf.fdrefs().empty() && buf.maprefs().empty()) {
- kernel_rss += buf.size();
- }
+ userspace_size += buf.size();
}
// Create an inode to dmabuf map. We know inodes are unique..
std::unordered_map<ino_t, DmaBuffer> inode_to_dmabuf;
@@ -161,12 +157,11 @@
printf("%22s %16s %16s %16s %16s\n", "Name", "Rss", "Pss", "nr_procs", "Inode");
for (auto& inode : inodes) {
DmaBuffer& buf = inode_to_dmabuf[inode];
- uint64_t proc_pss = buf.Pss(pid);
printf("%22s %13" PRIu64 " kB %13" PRIu64 " kB %16zu %16" PRIuMAX "\n",
buf.name().empty() ? "<unknown>" : buf.name().c_str(), buf.size() / 1024,
- proc_pss / 1024, buf.pids().size(), static_cast<uintmax_t>(buf.inode()));
+ buf.Pss() / 1024, buf.pids().size(), static_cast<uintmax_t>(buf.inode()));
rss += buf.size();
- pss += proc_pss;
+ pss += buf.Pss();
}
printf("%22s %13" PRIu64 " kB %13" PRIu64 " kB %16s\n", "PROCESS TOTAL", rss / 1024,
pss / 1024, "");
@@ -174,9 +169,21 @@
total_rss += rss;
total_pss += pss;
}
+
+ uint64_t kernel_rss = 0; // Total size of dmabufs NOT mapped or opened by a process
+ if (android::dmabufinfo::GetDmabufTotalExportedKb(&kernel_rss)) {
+ kernel_rss *= 1024; // KiB -> bytes
+ if (kernel_rss >= userspace_size)
+ kernel_rss -= userspace_size;
+ else
+ printf("Warning: Total dmabufs < userspace dmabufs\n");
+ } else {
+ printf("Warning: Could not get total exported dmabufs. Kernel size will be 0.\n");
+ }
printf("dmabuf total: %" PRIu64 " kB kernel_rss: %" PRIu64 " kB userspace_rss: %" PRIu64
" kB userspace_pss: %" PRIu64 " kB\n ",
- total_size / 1024, kernel_rss / 1024, total_rss / 1024, total_pss / 1024);
+ (userspace_size + kernel_rss) / 1024, kernel_rss / 1024, total_rss / 1024,
+ total_pss / 1024);
}
static void DumpDmabufSysfsStats() {
@@ -193,7 +200,7 @@
printf("\n\n----------------------- DMA-BUF per-buffer stats -----------------------\n");
printf(" Dmabuf Inode | Size(bytes) | Exporter Name |\n");
for (const auto& buf : buffer_stats) {
- printf("%16u |%16u | %16s \n", buf.inode, buf.size, buf.exp_name.c_str());
+ printf("%16lu |%" PRIu64 " | %16s \n", buf.inode, buf.size, buf.exp_name.c_str());
}
printf("\n\n----------------------- DMA-BUF exporter stats -----------------------\n");
@@ -261,8 +268,8 @@
exit(EXIT_FAILURE);
}
} else {
- if (!ReadDmaBufs(&bufs)) {
- fprintf(stderr, "Failed to ReadDmaBufs, check logcat for info\n");
+ if (!ReadProcfsDmaBufs(&bufs)) {
+ fprintf(stderr, "Failed to ReadProcfsDmaBufs, check logcat for info\n");
exit(EXIT_FAILURE);
}
}
diff --git a/libmeminfo_test.cpp b/libmeminfo_test.cpp
index 20159bf..8b1fe59 100644
--- a/libmeminfo_test.cpp
+++ b/libmeminfo_test.cpp
@@ -136,6 +136,31 @@
}
}
+TEST(ProcMemInfo, MapsUsageFillInAll) {
+ ProcMemInfo proc_mem(pid);
+ const std::vector<Vma>& maps = proc_mem.MapsWithoutUsageStats();
+ EXPECT_FALSE(maps.empty());
+ for (auto& map : maps) {
+ ASSERT_EQ(0, map.usage.vss);
+ ASSERT_EQ(0, map.usage.rss);
+ ASSERT_EQ(0, map.usage.pss);
+ ASSERT_EQ(0, map.usage.uss);
+ ASSERT_EQ(0, map.usage.swap);
+ ASSERT_EQ(0, map.usage.swap_pss);
+ ASSERT_EQ(0, map.usage.private_clean);
+ ASSERT_EQ(0, map.usage.private_dirty);
+ ASSERT_EQ(0, map.usage.shared_clean);
+ ASSERT_EQ(0, map.usage.shared_dirty);
+ }
+ // GetUsageStats' non-default parameter get_wss is false by default in
+ // ProcMemInfo's constructor.
+ ASSERT_TRUE(proc_mem.GetUsageStats(false));
+ for (auto& map : maps) {
+ // Check that at least one usage stat was updated.
+ ASSERT_NE(0, map.usage.vss);
+ }
+}
+
TEST(ProcMemInfo, PageMapPresent) {
static constexpr size_t kNumPages = 20;
size_t pagesize = getpagesize();
@@ -339,6 +364,154 @@
EXPECT_EQ(pss, 19119);
}
+TEST(ProcMemInfo, ForEachExistingVmaTest) {
+ std::string exec_dir = ::android::base::GetExecutableDirectory();
+ std::string path = ::android::base::StringPrintf("%s/testdata1/smaps_short", exec_dir.c_str());
+ ProcMemInfo proc_mem(pid);
+ // Populate maps_.
+ proc_mem.Smaps(path);
+ std::vector<Vma> vmas;
+ auto collect_vmas = [&](const Vma& v) { vmas.push_back(v); };
+ EXPECT_TRUE(proc_mem.ForEachExistingVma(collect_vmas));
+
+ // The size of vmas is not checked because Smaps() will return 5 vmas on x86
+ // and 6 vmas otherwise, as [vsyscall] is not processed on x86.
+
+ // Expect values to be equal to what we have in testdata1/smaps_short
+ // Check for names
+ EXPECT_EQ(vmas[0].name, "[anon:dalvik-zygote-jit-code-cache]");
+ EXPECT_EQ(vmas[1].name, "/system/framework/x86_64/boot-framework.art");
+ EXPECT_TRUE(vmas[2].name == "[anon:libc_malloc]" ||
+ android::base::StartsWith(vmas[2].name, "[anon:scudo:"))
+ << "Unknown map name " << vmas[2].name;
+ EXPECT_EQ(vmas[3].name, "/system/priv-app/SettingsProvider/oat/x86_64/SettingsProvider.odex");
+ EXPECT_EQ(vmas[4].name, "/system/lib64/libhwui.so");
+
+ // Check start address
+ EXPECT_EQ(vmas[0].start, 0x54c00000);
+ EXPECT_EQ(vmas[1].start, 0x701ea000);
+ EXPECT_EQ(vmas[2].start, 0x70074dd8d000);
+ EXPECT_EQ(vmas[3].start, 0x700755a2d000);
+ EXPECT_EQ(vmas[4].start, 0x7007f85b0000);
+
+ // Check end address
+ EXPECT_EQ(vmas[0].end, 0x56c00000);
+ EXPECT_EQ(vmas[1].end, 0x70cdb000);
+ EXPECT_EQ(vmas[2].end, 0x70074ee0d000);
+ EXPECT_EQ(vmas[3].end, 0x700755a6e000);
+ EXPECT_EQ(vmas[4].end, 0x7007f8b9b000);
+
+ // Check Flags
+ EXPECT_EQ(vmas[0].flags, PROT_READ | PROT_EXEC);
+ EXPECT_EQ(vmas[1].flags, PROT_READ | PROT_WRITE);
+ EXPECT_EQ(vmas[2].flags, PROT_READ | PROT_WRITE);
+ EXPECT_EQ(vmas[3].flags, PROT_READ | PROT_EXEC);
+ EXPECT_EQ(vmas[4].flags, PROT_READ | PROT_EXEC);
+
+ // Check Shared
+ EXPECT_FALSE(vmas[0].is_shared);
+ EXPECT_FALSE(vmas[1].is_shared);
+ EXPECT_FALSE(vmas[2].is_shared);
+ EXPECT_FALSE(vmas[3].is_shared);
+ EXPECT_FALSE(vmas[4].is_shared);
+
+ // Check Offset
+ EXPECT_EQ(vmas[0].offset, 0x0);
+ EXPECT_EQ(vmas[1].offset, 0x0);
+ EXPECT_EQ(vmas[2].offset, 0x0);
+ EXPECT_EQ(vmas[3].offset, 0x00016000);
+ EXPECT_EQ(vmas[4].offset, 0x001ee000);
+
+ // Check Inode
+ EXPECT_EQ(vmas[0].inode, 0);
+ EXPECT_EQ(vmas[1].inode, 3165);
+ EXPECT_EQ(vmas[2].inode, 0);
+ EXPECT_EQ(vmas[3].inode, 1947);
+ EXPECT_EQ(vmas[4].inode, 1537);
+
+ // Check smaps specific fields
+ ASSERT_EQ(vmas[0].usage.vss, 32768);
+ EXPECT_EQ(vmas[1].usage.vss, 11204);
+ EXPECT_EQ(vmas[2].usage.vss, 16896);
+ EXPECT_EQ(vmas[3].usage.vss, 260);
+ EXPECT_EQ(vmas[4].usage.vss, 6060);
+
+ EXPECT_EQ(vmas[0].usage.rss, 2048);
+ EXPECT_EQ(vmas[1].usage.rss, 11188);
+ EXPECT_EQ(vmas[2].usage.rss, 15272);
+ EXPECT_EQ(vmas[3].usage.rss, 260);
+ EXPECT_EQ(vmas[4].usage.rss, 4132);
+
+ EXPECT_EQ(vmas[0].usage.pss, 113);
+ EXPECT_EQ(vmas[1].usage.pss, 2200);
+ EXPECT_EQ(vmas[2].usage.pss, 15272);
+ EXPECT_EQ(vmas[3].usage.pss, 260);
+ EXPECT_EQ(vmas[4].usage.pss, 1274);
+
+ EXPECT_EQ(vmas[0].usage.uss, 0);
+ EXPECT_EQ(vmas[1].usage.uss, 1660);
+ EXPECT_EQ(vmas[2].usage.uss, 15272);
+ EXPECT_EQ(vmas[3].usage.uss, 260);
+ EXPECT_EQ(vmas[4].usage.uss, 0);
+
+ EXPECT_EQ(vmas[0].usage.private_clean, 0);
+ EXPECT_EQ(vmas[1].usage.private_clean, 0);
+ EXPECT_EQ(vmas[2].usage.private_clean, 0);
+ EXPECT_EQ(vmas[3].usage.private_clean, 260);
+ EXPECT_EQ(vmas[4].usage.private_clean, 0);
+
+ EXPECT_EQ(vmas[0].usage.private_dirty, 0);
+ EXPECT_EQ(vmas[1].usage.private_dirty, 1660);
+ EXPECT_EQ(vmas[2].usage.private_dirty, 15272);
+ EXPECT_EQ(vmas[3].usage.private_dirty, 0);
+ EXPECT_EQ(vmas[4].usage.private_dirty, 0);
+
+ EXPECT_EQ(vmas[0].usage.shared_clean, 0);
+ EXPECT_EQ(vmas[1].usage.shared_clean, 80);
+ EXPECT_EQ(vmas[2].usage.shared_clean, 0);
+ EXPECT_EQ(vmas[3].usage.shared_clean, 0);
+ EXPECT_EQ(vmas[4].usage.shared_clean, 4132);
+
+ EXPECT_EQ(vmas[0].usage.shared_dirty, 2048);
+ EXPECT_EQ(vmas[1].usage.shared_dirty, 9448);
+ EXPECT_EQ(vmas[2].usage.shared_dirty, 0);
+ EXPECT_EQ(vmas[3].usage.shared_dirty, 0);
+ EXPECT_EQ(vmas[4].usage.shared_dirty, 0);
+
+ EXPECT_EQ(vmas[0].usage.swap, 0);
+ EXPECT_EQ(vmas[1].usage.swap, 0);
+ EXPECT_EQ(vmas[2].usage.swap, 0);
+ EXPECT_EQ(vmas[3].usage.swap, 0);
+ EXPECT_EQ(vmas[4].usage.swap, 0);
+
+ EXPECT_EQ(vmas[0].usage.swap_pss, 0);
+ EXPECT_EQ(vmas[1].usage.swap_pss, 0);
+ EXPECT_EQ(vmas[2].usage.swap_pss, 0);
+ EXPECT_EQ(vmas[3].usage.swap_pss, 0);
+ EXPECT_EQ(vmas[4].usage.swap_pss, 0);
+
+#ifndef __x86_64__
+ // vmas[5] will not exist on x86, as [vsyscall] would not be processed.
+ EXPECT_EQ(vmas[5].name, "[vsyscall]");
+ EXPECT_EQ(vmas[5].start, 0xffffffffff600000);
+ EXPECT_EQ(vmas[5].end, 0xffffffffff601000);
+ EXPECT_EQ(vmas[5].flags, PROT_READ | PROT_EXEC);
+ EXPECT_FALSE(vmas[5].is_shared);
+ EXPECT_EQ(vmas[5].offset, 0x0);
+ EXPECT_EQ(vmas[5].inode, 0);
+ EXPECT_EQ(vmas[5].usage.vss, 4);
+ EXPECT_EQ(vmas[5].usage.rss, 0);
+ EXPECT_EQ(vmas[5].usage.pss, 0);
+ EXPECT_EQ(vmas[5].usage.uss, 0);
+ EXPECT_EQ(vmas[5].usage.private_clean, 0);
+ EXPECT_EQ(vmas[5].usage.private_dirty, 0);
+ EXPECT_EQ(vmas[5].usage.shared_clean, 0);
+ EXPECT_EQ(vmas[5].usage.shared_dirty, 0);
+ EXPECT_EQ(vmas[5].usage.swap, 0);
+ EXPECT_EQ(vmas[5].usage.swap_pss, 0);
+#endif
+}
+
TEST(ProcMemInfo, ForEachVmaFromFile_SmapsTest) {
// Parse smaps file correctly to make callbacks for each virtual memory area (vma)
std::string exec_dir = ::android::base::GetExecutableDirectory();
@@ -683,6 +856,30 @@
#endif
}
+TEST(ProcMemInfo, SmapsPopulatesUsageTest) {
+ std::string exec_dir = ::android::base::GetExecutableDirectory();
+ std::string path = ::android::base::StringPrintf("%s/testdata1/smaps_short", exec_dir.c_str());
+ ProcMemInfo proc_mem(pid);
+ auto vmas = proc_mem.Smaps(path, true);
+
+ // Expect values to be equal to sums of usage in testdata1/smaps_short. For
+ // this data, only vss differs on x86.
+#ifndef __x86_64__
+ EXPECT_EQ(proc_mem.Usage().vss, 67192);
+#else
+ EXPECT_EQ(proc_mem.Usage().vss, 67188);
+#endif
+ EXPECT_EQ(proc_mem.Usage().rss, 32900);
+ EXPECT_EQ(proc_mem.Usage().pss, 19119);
+ EXPECT_EQ(proc_mem.Usage().uss, 17192);
+ EXPECT_EQ(proc_mem.Usage().private_clean, 260);
+ EXPECT_EQ(proc_mem.Usage().private_dirty, 16932);
+ EXPECT_EQ(proc_mem.Usage().shared_clean, 4212);
+ EXPECT_EQ(proc_mem.Usage().shared_dirty, 11496);
+ EXPECT_EQ(proc_mem.Usage().swap, 0);
+ EXPECT_EQ(proc_mem.Usage().swap_pss, 0);
+}
+
TEST(SysMemInfo, TestSysMemInfoFile) {
std::string meminfo = R"meminfo(MemTotal: 3019740 kB
MemFree: 1809728 kB
@@ -754,6 +951,13 @@
EXPECT_EQ(mi.mem_active_kb(), 445856);
EXPECT_EQ(mi.mem_inactive_kb(), 459092);
EXPECT_EQ(mi.mem_unevictable_kb(), 3096);
+ EXPECT_EQ(mi.mem_available_kb(), 2546560);
+ EXPECT_EQ(mi.mem_active_anon_kb(), 78492);
+ EXPECT_EQ(mi.mem_inactive_anon_kb(), 2240);
+ EXPECT_EQ(mi.mem_active_file_kb(), 367364);
+ EXPECT_EQ(mi.mem_inactive_file_kb(), 456852);
+ EXPECT_EQ(mi.mem_cma_total_kb(), 131072);
+ EXPECT_EQ(mi.mem_cma_free_kb(), 130380);
}
TEST(SysMemInfo, TestEmptyFile) {
@@ -798,6 +1002,13 @@
MEMINFO_ACTIVE,
MEMINFO_INACTIVE,
MEMINFO_UNEVICTABLE,
+ MEMINFO_AVAILABLE,
+ MEMINFO_ACTIVE_ANON,
+ MEMINFO_INACTIVE_ANON,
+ MEMINFO_ACTIVE_FILE,
+ MEMINFO_INACTIVE_FILE,
+ MEMINFO_CMA_TOTAL,
+ MEMINFO_CMA_FREE,
MEMINFO_COUNT
};
@@ -880,6 +1091,13 @@
EXPECT_EQ(mem[MEMINFO_ACTIVE], 445856);
EXPECT_EQ(mem[MEMINFO_INACTIVE], 459092);
EXPECT_EQ(mem[MEMINFO_UNEVICTABLE], 3096);
+ EXPECT_EQ(mem[MEMINFO_AVAILABLE], 2546560);
+ EXPECT_EQ(mem[MEMINFO_ACTIVE_ANON], 78492);
+ EXPECT_EQ(mem[MEMINFO_INACTIVE_ANON], 2240);
+ EXPECT_EQ(mem[MEMINFO_ACTIVE_FILE], 367364);
+ EXPECT_EQ(mem[MEMINFO_INACTIVE_FILE], 456852);
+ EXPECT_EQ(mem[MEMINFO_CMA_TOTAL], 131072);
+ EXPECT_EQ(mem[MEMINFO_CMA_FREE], 130380);
}
TEST(SysMemInfo, TestVmallocInfoNoMemory) {
diff --git a/libsmapinfo/Android.bp b/libsmapinfo/Android.bp
new file mode 100644
index 0000000..e421014
--- /dev/null
+++ b/libsmapinfo/Android.bp
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2022 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+ name: "smapinfo_defaults",
+ vendor_available: true,
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ shared_libs: [
+ "libbase",
+ "libmeminfo",
+ "libprocinfo",
+ ],
+}
+
+cc_library_shared {
+ name: "libsmapinfo",
+ host_supported: true,
+ defaults: ["smapinfo_defaults"],
+ export_include_dirs: ["include"],
+ srcs: ["processrecord.cpp",
+ "smapinfo.cpp"],
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+}
+
diff --git a/libsmapinfo/include/processrecord.h b/libsmapinfo/include/processrecord.h
new file mode 100644
index 0000000..e40cd43
--- /dev/null
+++ b/libsmapinfo/include/processrecord.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <cstdint>
+#include <functional>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include <meminfo/meminfo.h>
+#include <meminfo/procmeminfo.h>
+
+namespace android {
+namespace smapinfo {
+
+class ProcessRecord final {
+ public:
+ ProcessRecord(pid_t pid, bool get_wss, uint64_t pgflags, uint64_t pgflags_mask,
+ bool get_cmdline, bool get_oomadj, std::ostream& err);
+
+ bool valid() const;
+ void CalculateSwap(const std::vector<uint16_t>& swap_offset_array,
+ float 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<uint64_t>& SwapOffsets() const { return swap_offsets_; }
+ // show_wss may be used to return differentiated output in the future.
+ const ::android::meminfo::MemUsage& Usage([[maybe_unused]] bool show_wss) const {
+ return usage_or_wss_;
+ }
+ const std::vector<::android::meminfo::Vma>& Smaps() { return procmem_.Smaps(); }
+ bool ForEachExistingVma(const ::android::meminfo::VmaCallback& callback) {
+ return procmem_.ForEachExistingVma(callback);
+ }
+
+ private:
+ ::android::meminfo::ProcMemInfo procmem_;
+ pid_t pid_;
+ std::string cmdline_;
+ int32_t oomadj_;
+ uint64_t proportional_swap_;
+ uint64_t unique_swap_;
+ uint64_t zswap_;
+ ::android::meminfo::MemUsage usage_or_wss_;
+ std::vector<uint64_t> swap_offsets_;
+};
+
+} // namespace smapinfo
+} // namespace android
diff --git a/libsmapinfo/include/smapinfo.h b/libsmapinfo/include/smapinfo.h
new file mode 100644
index 0000000..59edd7f
--- /dev/null
+++ b/libsmapinfo/include/smapinfo.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <map>
+#include <ostream>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <meminfo/procmeminfo.h>
+#include <processrecord.h>
+
+namespace android {
+namespace smapinfo {
+
+// The user-specified order to sort processes.
+enum class SortOrder { BY_PSS = 0, BY_RSS, BY_USS, BY_VSS, BY_SWAP, BY_OOMADJ };
+
+// Populates the input set with all pids present in the /proc directory. Only
+// returns false if /proc could not be opened, returns true otherwise.
+bool get_all_pids(std::set<pid_t>* pids);
+
+// Sorts processes provided in 'pids' by memory usage (or oomadj score) and
+// prints them. Returns false in the following failure cases:
+// a) system memory information could not be read,
+// b) swap offsets could not be counted for some process,
+// c) reset_wss is true but the working set for some process could not be reset.
+bool run_procrank(uint64_t pgflags, uint64_t pgflags_mask, const std::set<pid_t>& pids,
+ bool get_oomadj, bool get_wss, SortOrder sort_order, bool reverse_sort,
+ std::map<pid_t, ProcessRecord>* processrecords_ptr, std::ostream& out,
+ std::ostream& err);
+
+// Sorts libraries used by processes in 'pids' by memory usage and prints them.
+// Returns false if any process's usage info could not be read.
+bool run_librank(uint64_t pgflags, uint64_t pgflags_mask, const std::set<pid_t>& pids,
+ const std::string& lib_prefix, bool all_libs,
+ const std::vector<std::string>& excluded_libs, uint16_t mapflags_mask,
+ android::meminfo::Format format, SortOrder sort_order, bool reverse_sort,
+ std::map<pid_t, ProcessRecord>* processrecords_ptr, std::ostream& out,
+ std::ostream& err);
+
+// Retrieves showmap information from the provided pid (or file) and prints it.
+// Returns false if there are no maps associated with 'pid' or if the file
+// denoted by 'filename' is malformed.
+bool run_showmap(pid_t pid, const std::string& filename, bool terse, bool verbose, bool show_addr,
+ bool quiet, android::meminfo::Format format,
+ std::map<pid_t, ProcessRecord>* processrecords_ptr, std::ostream& out,
+ std::ostream& err);
+
+// Runs procrank, librank, and showmap with a single read of smaps. Default
+// arguments are used for all tools (except quiet output for showmap). This
+// prints output that is specifically meant to be included in bug reports.
+// Returns false only in the case that /proc could not be opened.
+bool run_bugreport_procdump(std::ostream& out, std::ostream& err);
+
+} // namespace smapinfo
+} // namespace android
diff --git a/libsmapinfo/processrecord.cpp b/libsmapinfo/processrecord.cpp
new file mode 100644
index 0000000..615cfb8
--- /dev/null
+++ b/libsmapinfo/processrecord.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 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 <inttypes.h>
+#include <linux/oom.h>
+#include <stdlib.h>
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <meminfo/procmeminfo.h>
+
+#include <processrecord.h>
+
+namespace android {
+namespace smapinfo {
+
+using ::android::base::StringPrintf;
+using ::android::meminfo::MemUsage;
+using ::android::meminfo::ProcMemInfo;
+using ::android::meminfo::Vma;
+using ::android::meminfo::VmaCallback;
+
+ProcessRecord::ProcessRecord(pid_t pid, bool get_wss, uint64_t pgflags, uint64_t pgflags_mask,
+ bool get_cmdline, bool get_oomadj, std::ostream& err)
+ : procmem_(pid, get_wss, pgflags, pgflags_mask),
+ pid_(-1),
+ oomadj_(OOM_SCORE_ADJ_MAX + 1),
+ proportional_swap_(0),
+ unique_swap_(0),
+ zswap_(0) {
+ // cmdline_ only needs to be populated if this record will be used by procrank/librank.
+ if (get_cmdline) {
+ std::string fname = StringPrintf("/proc/%d/cmdline", pid);
+ if (!::android::base::ReadFileToString(fname, &cmdline_)) {
+ err << "Failed to read cmdline from: " << fname << "\n";
+ cmdline_ = "<unknown>";
+ }
+ // We deliberately don't read the /proc/<pid>/cmdline file directly into 'cmdline_'
+ // because some processes have cmdlines that end with "0x00 0x0A 0x00",
+ // e.g. xtra-daemon, lowi-server.
+ // The .c_str() assignment takes care of trimming the cmdline at the first 0x00. This is
+ // how the original procrank worked (luckily).
+ cmdline_.resize(strlen(cmdline_.c_str()));
+
+ // If there is no cmdline (empty, not <unknown>), a kernel thread will have comm. This only
+ // matters for bug reports, which output 'SHOW MAP <pid>: <cmdline>' as section titles.
+ if (cmdline_.empty()) {
+ fname = StringPrintf("/proc/%d/comm", pid);
+ if (!::android::base::ReadFileToString(fname, &cmdline_)) {
+ err << "Failed to read comm from: " << fname << "\n";
+ }
+ // comm seems to contain a trailing '\n' that isn't present in cmdline. dumpstate
+ // surrounds kernel thread names with brackets; this behavior is maintained here.
+ if (auto pos = cmdline_.find_last_of('\n'); pos != std::string::npos) {
+ cmdline_.erase(pos);
+ }
+ cmdline_ = StringPrintf("[%s]", cmdline_.c_str());
+ }
+ }
+
+ // oomadj_ only needs to be populated if this record will be used by procrank/librank.
+ if (get_oomadj) {
+ std::string fname = StringPrintf("/proc/%d/oom_score_adj", pid);
+ std::string oom_score;
+ if (!::android::base::ReadFileToString(fname, &oom_score)) {
+ err << "Failed to read oom_score_adj file: " << fname << "\n";
+ return;
+ }
+ if (!::android::base::ParseInt(::android::base::Trim(oom_score), &oomadj_)) {
+ err << "Failed to parse oomadj from: " << fname << "\n";
+ return;
+ }
+ }
+
+ // We want to use Smaps() to populate procmem_'s maps before calling Wss() or Usage(), as
+ // these will fall back on the slower ReadMaps().
+ //
+ // This Smaps() call is temporarily disabled because it results in
+ // procmem_'s swap_offsets_ not being populated, causing procrank to not
+ // report PSwap/USwap/ZSwap. Wss() and Usage() both fall back to ReadMaps(),
+ // which will populate swap_offsets_.
+ //
+ // procmem_.Smaps("", true);
+ usage_or_wss_ = get_wss ? procmem_.Wss() : procmem_.Usage();
+ swap_offsets_ = procmem_.SwapOffsets();
+ pid_ = pid;
+}
+
+bool ProcessRecord::valid() const {
+ return pid_ != -1;
+}
+
+void ProcessRecord::CalculateSwap(const std::vector<uint16_t>& swap_offset_array,
+ float zram_compression_ratio) {
+ for (auto& off : swap_offsets_) {
+ proportional_swap_ += getpagesize() / swap_offset_array[off];
+ unique_swap_ += swap_offset_array[off] == 1 ? getpagesize() : 0;
+ zswap_ = proportional_swap_ * zram_compression_ratio;
+ }
+ // This is divided by 1024 to convert to KB.
+ proportional_swap_ /= 1024;
+ unique_swap_ /= 1024;
+ zswap_ /= 1024;
+}
+
+} // namespace smapinfo
+} // namespace android
diff --git a/libsmapinfo/smapinfo.cpp b/libsmapinfo/smapinfo.cpp
new file mode 100644
index 0000000..2c2c0f6
--- /dev/null
+++ b/libsmapinfo/smapinfo.cpp
@@ -0,0 +1,1274 @@
+/*
+ * Copyright (C) 2022 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 <inttypes.h>
+#include <linux/oom.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <iomanip>
+#include <iostream>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <meminfo/sysmeminfo.h>
+
+#include <processrecord.h>
+#include <smapinfo.h>
+
+namespace android {
+namespace smapinfo {
+
+using ::android::base::StringPrintf;
+using ::android::meminfo::EscapeCsvString;
+using ::android::meminfo::EscapeJsonString;
+using ::android::meminfo::Format;
+using ::android::meminfo::MemUsage;
+using ::android::meminfo::Vma;
+
+bool get_all_pids(std::set<pid_t>* pids) {
+ pids->clear();
+ std::unique_ptr<DIR, int (*)(DIR*)> procdir(opendir("/proc"), closedir);
+ if (!procdir) return false;
+
+ struct dirent* dir;
+ pid_t pid;
+ while ((dir = readdir(procdir.get()))) {
+ if (!::android::base::ParseInt(dir->d_name, &pid)) continue;
+ pids->insert(pid);
+ }
+ return true;
+}
+
+namespace procrank {
+
+static bool count_swap_offsets(const ProcessRecord& proc, std::vector<uint16_t>& swap_offset_array,
+ std::ostream& err) {
+ const std::vector<uint64_t>& swp_offs = proc.SwapOffsets();
+ for (auto& off : swp_offs) {
+ if (off >= swap_offset_array.size()) {
+ err << "swap offset " << off << " is out of bounds for process: " << proc.pid() << "\n";
+ return false;
+ }
+ if (swap_offset_array[off] == USHRT_MAX) {
+ err << "swap offset " << off << " ref count overflow in process: " << proc.pid()
+ << "\n";
+ return false;
+ }
+ swap_offset_array[off]++;
+ }
+ return true;
+}
+
+struct params {
+ // Calculated total memory usage across all processes in the system.
+ uint64_t total_pss;
+ uint64_t total_uss;
+ uint64_t total_swap;
+ uint64_t total_pswap;
+ uint64_t total_uswap;
+ uint64_t total_zswap;
+
+ // Print options.
+ bool show_oomadj;
+ bool show_wss;
+ bool swap_enabled;
+ bool zram_enabled;
+
+ // If zram is enabled, the compression ratio is zram used / swap used.
+ float zram_compression_ratio;
+};
+
+static std::function<bool(ProcessRecord& a, ProcessRecord& b)> select_sort(struct params* params,
+ SortOrder sort_order) {
+ // Create sort function based on sort_order.
+ std::function<bool(ProcessRecord & a, ProcessRecord & b)> proc_sort;
+ switch (sort_order) {
+ case (SortOrder::BY_OOMADJ):
+ proc_sort = [](ProcessRecord& a, ProcessRecord& b) { return a.oomadj() > b.oomadj(); };
+ break;
+ case (SortOrder::BY_RSS):
+ proc_sort = [=](ProcessRecord& a, ProcessRecord& b) {
+ return a.Usage(params->show_wss).rss > b.Usage(params->show_wss).rss;
+ };
+ break;
+ case (SortOrder::BY_SWAP):
+ proc_sort = [=](ProcessRecord& a, ProcessRecord& b) {
+ return a.Usage(params->show_wss).swap > b.Usage(params->show_wss).swap;
+ };
+ break;
+ case (SortOrder::BY_USS):
+ proc_sort = [=](ProcessRecord& a, ProcessRecord& b) {
+ return a.Usage(params->show_wss).uss > b.Usage(params->show_wss).uss;
+ };
+ break;
+ case (SortOrder::BY_VSS):
+ proc_sort = [=](ProcessRecord& a, ProcessRecord& b) {
+ return a.Usage(params->show_wss).vss > b.Usage(params->show_wss).vss;
+ };
+ break;
+ case (SortOrder::BY_PSS):
+ default:
+ proc_sort = [=](ProcessRecord& a, ProcessRecord& b) {
+ return a.Usage(params->show_wss).pss > b.Usage(params->show_wss).pss;
+ };
+ break;
+ }
+ return proc_sort;
+}
+
+static bool populate_procs(struct params* params, uint64_t pgflags, uint64_t pgflags_mask,
+ std::vector<uint16_t>& swap_offset_array, const std::set<pid_t>& pids,
+ std::vector<ProcessRecord>* procs,
+ std::map<pid_t, ProcessRecord>* processrecords_ptr, std::ostream& err) {
+ // Fall back to using an empty map of ProcessRecords if nullptr was passed in.
+ std::map<pid_t, ProcessRecord> processrecords;
+ if (!processrecords_ptr) {
+ processrecords_ptr = &processrecords;
+ }
+ // Mark each swap offset used by the process as we find them for calculating
+ // proportional swap usage later.
+ for (pid_t pid : pids) {
+ // Check if a ProcessRecord already exists for this pid, create one if one does not exist.
+ auto iter = processrecords_ptr->find(pid);
+ ProcessRecord& proc =
+ (iter != processrecords_ptr->end())
+ ? iter->second
+ : processrecords_ptr
+ ->emplace(pid, ProcessRecord(pid, params->show_wss, pgflags,
+ pgflags_mask, true,
+ params->show_oomadj, err))
+ .first->second;
+
+ if (!proc.valid()) {
+ // Check to see if the process is still around, skip the process if the proc
+ // directory is inaccessible. It was most likely killed while creating the process
+ // record.
+ std::string procdir = StringPrintf("/proc/%d", pid);
+ if (access(procdir.c_str(), F_OK | R_OK)) continue;
+
+ // Warn if we failed to gather process stats even while it is still alive.
+ // Return success here, so we continue to print stats for other processes.
+ err << "warning: failed to create process record for: " << pid << "\n";
+ continue;
+ }
+
+ // Skip processes with no memory mappings.
+ uint64_t vss = proc.Usage(params->show_wss).vss;
+ if (vss == 0) continue;
+
+ // Collect swap_offset counts from all processes in 1st pass.
+ if (!params->show_wss && params->swap_enabled &&
+ !count_swap_offsets(proc, swap_offset_array, err)) {
+ err << "Failed to count swap offsets for process: " << pid << "\n";
+ err << "Failed to read all pids from the system\n";
+ return false;
+ }
+
+ procs->push_back(proc);
+ }
+ return true;
+}
+
+static void print_header(struct params* params, std::ostream& out) {
+ out << StringPrintf("%5s ", "PID");
+ if (params->show_oomadj) {
+ out << StringPrintf("%5s ", "oom");
+ }
+
+ if (params->show_wss) {
+ out << StringPrintf("%7s %7s %7s ", "WRss", "WPss", "WUss");
+ } else {
+ // Swap statistics here, as working set pages by definition shouldn't end up in swap.
+ out << StringPrintf("%8s %7s %7s %7s ", "Vss", "Rss", "Pss", "Uss");
+ if (params->swap_enabled) {
+ out << StringPrintf("%7s %7s %7s ", "Swap", "PSwap", "USwap");
+ if (params->zram_enabled) {
+ out << StringPrintf("%7s ", "ZSwap");
+ }
+ }
+ }
+
+ out << "cmdline\n";
+}
+
+static void print_divider(struct params* params, std::ostream& out) {
+ out << StringPrintf("%5s ", "");
+ if (params->show_oomadj) {
+ out << StringPrintf("%5s ", "");
+ }
+
+ if (params->show_wss) {
+ out << StringPrintf("%7s %7s %7s ", "", "------", "------");
+ } else {
+ out << StringPrintf("%8s %7s %7s %7s ", "", "", "------", "------");
+ if (params->swap_enabled) {
+ out << StringPrintf("%7s %7s %7s ", "------", "------", "------");
+ if (params->zram_enabled) {
+ out << StringPrintf("%7s ", "------");
+ }
+ }
+ }
+
+ out << StringPrintf("%s\n", "------");
+}
+
+static void print_processrecord(struct params* params, ProcessRecord& proc, std::ostream& out) {
+ out << StringPrintf("%5d ", proc.pid());
+ if (params->show_oomadj) {
+ out << StringPrintf("%5d ", proc.oomadj());
+ }
+
+ if (params->show_wss) {
+ out << StringPrintf("%6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ",
+ proc.Usage(params->show_wss).rss, proc.Usage(params->show_wss).pss,
+ proc.Usage(params->show_wss).uss);
+ } else {
+ out << StringPrintf("%7" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ",
+ proc.Usage(params->show_wss).vss, proc.Usage(params->show_wss).rss,
+ proc.Usage(params->show_wss).pss, proc.Usage(params->show_wss).uss);
+ if (params->swap_enabled) {
+ out << StringPrintf("%6" PRIu64 "K ", proc.Usage(params->show_wss).swap);
+ out << StringPrintf("%6" PRIu64 "K ", proc.proportional_swap());
+ out << StringPrintf("%6" PRIu64 "K ", proc.unique_swap());
+ if (params->zram_enabled) {
+ out << StringPrintf("%6" PRIu64 "K ", proc.zswap());
+ }
+ }
+ }
+ out << proc.cmdline() << "\n";
+}
+
+static void print_totals(struct params* params, std::ostream& out) {
+ out << StringPrintf("%5s ", "");
+ if (params->show_oomadj) {
+ out << StringPrintf("%5s ", "");
+ }
+
+ if (params->show_wss) {
+ out << StringPrintf("%7s %6" PRIu64 "K %6" PRIu64 "K ", "", params->total_pss,
+ params->total_uss);
+ } else {
+ out << StringPrintf("%8s %7s %6" PRIu64 "K %6" PRIu64 "K ", "", "", params->total_pss,
+ params->total_uss);
+ if (params->swap_enabled) {
+ out << StringPrintf("%6" PRIu64 "K ", params->total_swap);
+ out << StringPrintf("%6" PRIu64 "K ", params->total_pswap);
+ out << StringPrintf("%6" PRIu64 "K ", params->total_uswap);
+ if (params->zram_enabled) {
+ out << StringPrintf("%6" PRIu64 "K ", params->total_zswap);
+ }
+ }
+ }
+ out << "TOTAL\n\n";
+}
+
+static void print_sysmeminfo(struct params* params, const ::android::meminfo::SysMemInfo& smi,
+ std::ostream& out) {
+ if (params->swap_enabled) {
+ out << StringPrintf("ZRAM: %" PRIu64 "K physical used for %" PRIu64 "K in swap (%" PRIu64
+ "K total swap)\n",
+ smi.mem_zram_kb(), (smi.mem_swap_kb() - smi.mem_swap_free_kb()),
+ smi.mem_swap_kb());
+ }
+
+ out << StringPrintf(" RAM: %" PRIu64 "K total, %" PRIu64 "K free, %" PRIu64
+ "K buffers, %" PRIu64 "K cached, %" PRIu64 "K shmem, %" PRIu64 "K slab\n",
+ smi.mem_total_kb(), smi.mem_free_kb(), smi.mem_buffers_kb(),
+ smi.mem_cached_kb(), smi.mem_shmem_kb(), smi.mem_slab_kb());
+}
+
+static void add_to_totals(struct params* params, ProcessRecord& proc,
+ const std::vector<uint16_t>& swap_offset_array) {
+ params->total_pss += proc.Usage(params->show_wss).pss;
+ params->total_uss += proc.Usage(params->show_wss).uss;
+ if (!params->show_wss && params->swap_enabled) {
+ proc.CalculateSwap(swap_offset_array, params->zram_compression_ratio);
+ params->total_swap += proc.Usage(params->show_wss).swap;
+ params->total_pswap += proc.proportional_swap();
+ params->total_uswap += proc.unique_swap();
+ if (params->zram_enabled) {
+ params->total_zswap += proc.zswap();
+ }
+ }
+}
+
+} // namespace procrank
+
+bool run_procrank(uint64_t pgflags, uint64_t pgflags_mask, const std::set<pid_t>& pids,
+ bool get_oomadj, bool get_wss, SortOrder sort_order, bool reverse_sort,
+ std::map<pid_t, ProcessRecord>* processrecords_ptr, std::ostream& out,
+ std::ostream& err) {
+ ::android::meminfo::SysMemInfo smi;
+ if (!smi.ReadMemInfo()) {
+ err << "Failed to get system memory info\n";
+ return false;
+ }
+
+ struct procrank::params params = {
+ .total_pss = 0,
+ .total_uss = 0,
+ .total_swap = 0,
+ .total_pswap = 0,
+ .total_uswap = 0,
+ .total_zswap = 0,
+ .show_oomadj = get_oomadj,
+ .show_wss = get_wss,
+ .swap_enabled = false,
+ .zram_enabled = false,
+ .zram_compression_ratio = 0.0,
+ };
+
+ // Figure out swap and zram.
+ uint64_t swap_total = smi.mem_swap_kb() * 1024;
+ params.swap_enabled = swap_total > 0;
+ // Allocate the swap array.
+ std::vector<uint16_t> swap_offset_array(swap_total / getpagesize() + 1, 0);
+ if (params.swap_enabled) {
+ params.zram_enabled = smi.mem_zram_kb() > 0;
+ if (params.zram_enabled) {
+ params.zram_compression_ratio = static_cast<float>(smi.mem_zram_kb()) /
+ (smi.mem_swap_kb() - smi.mem_swap_free_kb());
+ }
+ }
+
+ std::vector<ProcessRecord> procs;
+ if (!procrank::populate_procs(¶ms, pgflags, pgflags_mask, swap_offset_array, pids, &procs,
+ processrecords_ptr, err)) {
+ return false;
+ }
+
+ 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
+ out << "<empty>\n\n";
+ procrank::print_sysmeminfo(¶ms, smi, out);
+ return true;
+ }
+
+ // Create sort function based on sort_order, default is PSS descending.
+ std::function<bool(ProcessRecord & a, ProcessRecord & b)> proc_sort =
+ procrank::select_sort(¶ms, sort_order);
+
+ // Sort all process records, default is PSS descending.
+ if (reverse_sort) {
+ std::sort(procs.rbegin(), procs.rend(), proc_sort);
+ } else {
+ std::sort(procs.begin(), procs.end(), proc_sort);
+ }
+
+ procrank::print_header(¶ms, out);
+
+ for (auto& proc : procs) {
+ procrank::add_to_totals(¶ms, proc, swap_offset_array);
+ procrank::print_processrecord(¶ms, proc, out);
+ }
+
+ procrank::print_divider(¶ms, out);
+ procrank::print_totals(¶ms, out);
+ procrank::print_sysmeminfo(¶ms, smi, out);
+
+ return true;
+}
+
+namespace librank {
+
+static void add_mem_usage(MemUsage* to, const MemUsage& from) {
+ to->vss += from.vss;
+ to->rss += from.rss;
+ to->pss += from.pss;
+ to->uss += from.uss;
+
+ to->swap += from.swap;
+
+ to->private_clean += from.private_clean;
+ to->private_dirty += from.private_dirty;
+ to->shared_clean += from.shared_clean;
+ to->shared_dirty += from.shared_dirty;
+}
+
+// Represents a specific process's usage of a library.
+struct LibProcRecord {
+ public:
+ LibProcRecord(ProcessRecord& proc) : pid_(-1), oomadj_(OOM_SCORE_ADJ_MAX + 1) {
+ pid_ = proc.pid();
+ cmdline_ = proc.cmdline();
+ oomadj_ = proc.oomadj();
+ usage_.clear();
+ }
+
+ bool valid() const { return pid_ != -1; }
+ void AddUsage(const MemUsage& mem_usage) { add_mem_usage(&usage_, mem_usage); }
+
+ // Getters
+ pid_t pid() const { return pid_; }
+ const std::string& cmdline() const { return cmdline_; }
+ int32_t oomadj() const { return oomadj_; }
+ const MemUsage& usage() const { return usage_; }
+
+ private:
+ pid_t pid_;
+ std::string cmdline_;
+ int32_t oomadj_;
+ MemUsage usage_;
+};
+
+// Represents all processes' usage of a specific library.
+struct LibRecord {
+ public:
+ LibRecord(const std::string& name) : name_(name) {}
+
+ void AddUsage(const LibProcRecord& proc, const MemUsage& mem_usage) {
+ auto [it, inserted] = procs_.insert(std::pair<pid_t, LibProcRecord>(proc.pid(), proc));
+ // Adds to proc's PID's contribution to usage of this lib, as well as total lib usage.
+ it->second.AddUsage(mem_usage);
+ add_mem_usage(&usage_, mem_usage);
+ }
+ uint64_t pss() const { return usage_.pss; }
+
+ // Getters
+ const std::string& name() const { return name_; }
+ const std::map<pid_t, LibProcRecord>& processes() const { return procs_; }
+
+ private:
+ std::string name_;
+ MemUsage usage_;
+ std::map<pid_t, LibProcRecord> procs_;
+};
+
+static std::function<bool(LibProcRecord& a, LibProcRecord& b)> select_sort(SortOrder sort_order) {
+ // Create sort function based on sort_order.
+ std::function<bool(LibProcRecord & a, LibProcRecord & b)> proc_sort;
+ switch (sort_order) {
+ case (SortOrder::BY_RSS):
+ proc_sort = [](LibProcRecord& a, LibProcRecord& b) {
+ return a.usage().rss > b.usage().rss;
+ };
+ break;
+ case (SortOrder::BY_USS):
+ proc_sort = [](LibProcRecord& a, LibProcRecord& b) {
+ return a.usage().uss > b.usage().uss;
+ };
+ break;
+ case (SortOrder::BY_VSS):
+ proc_sort = [](LibProcRecord& a, LibProcRecord& b) {
+ return a.usage().vss > b.usage().vss;
+ };
+ break;
+ case (SortOrder::BY_OOMADJ):
+ proc_sort = [](LibProcRecord& a, LibProcRecord& b) { return a.oomadj() > b.oomadj(); };
+ break;
+ case (SortOrder::BY_PSS):
+ default:
+ proc_sort = [](LibProcRecord& a, LibProcRecord& b) {
+ return a.usage().pss > b.usage().pss;
+ };
+ break;
+ }
+ return proc_sort;
+}
+
+struct params {
+ // Filtering options.
+ std::string lib_prefix;
+ bool all_libs;
+ const std::vector<std::string>& excluded_libs;
+ uint16_t mapflags_mask;
+
+ // Print options.
+ Format format;
+ bool swap_enabled;
+ bool show_oomadj;
+};
+
+static bool populate_libs(struct params* params, uint64_t pgflags, uint64_t pgflags_mask,
+ const std::set<pid_t>& pids,
+ std::map<std::string, LibRecord>& lib_name_map,
+ std::map<pid_t, ProcessRecord>* processrecords_ptr, std::ostream& err) {
+ // Fall back to using an empty map of ProcessRecords if nullptr was passed in.
+ std::map<pid_t, ProcessRecord> processrecords;
+ if (!processrecords_ptr) {
+ processrecords_ptr = &processrecords;
+ }
+ for (pid_t pid : pids) {
+ // Check if a ProcessRecord already exists for this pid, create one if one does not exist.
+ auto iter = processrecords_ptr->find(pid);
+ ProcessRecord& proc =
+ (iter != processrecords_ptr->end())
+ ? iter->second
+ : processrecords_ptr
+ ->emplace(pid, ProcessRecord(pid, false, pgflags, pgflags_mask,
+ true, params->show_oomadj, err))
+ .first->second;
+
+ if (!proc.valid()) {
+ err << "error: failed to create process record for: " << pid << "\n";
+ return false;
+ }
+
+ const std::vector<Vma>& maps = proc.Smaps();
+ if (maps.size() == 0) {
+ continue;
+ }
+
+ LibProcRecord record(proc);
+ for (const Vma& map : maps) {
+ // Skip library/map if the prefix for the path doesn't match.
+ if (!params->lib_prefix.empty() &&
+ !::android::base::StartsWith(map.name, params->lib_prefix)) {
+ continue;
+ }
+ // Skip excluded library/map names.
+ if (!params->all_libs &&
+ (std::find(params->excluded_libs.begin(), params->excluded_libs.end(), map.name) !=
+ params->excluded_libs.end())) {
+ continue;
+ }
+ // Skip maps based on map permissions.
+ if (params->mapflags_mask &&
+ ((map.flags & (PROT_READ | PROT_WRITE | PROT_EXEC)) != params->mapflags_mask)) {
+ continue;
+ }
+
+ // Add memory for lib usage.
+ auto [it, inserted] = lib_name_map.emplace(map.name, LibRecord(map.name));
+ it->second.AddUsage(record, map.usage);
+
+ if (!params->swap_enabled && map.usage.swap) {
+ params->swap_enabled = true;
+ }
+ }
+ }
+ return true;
+}
+
+static void print_header(struct params* params, std::ostream& out) {
+ switch (params->format) {
+ case Format::RAW:
+ // clang-format off
+ out << std::setw(7) << "RSStot"
+ << std::setw(10) << "VSS"
+ << std::setw(9) << "RSS"
+ << std::setw(9) << "PSS"
+ << std::setw(9) << "USS"
+ << " ";
+ //clang-format on
+ if (params->swap_enabled) {
+ out << std::setw(7) << "Swap"
+ << " ";
+ }
+ if (params->show_oomadj) {
+ out << std::setw(7) << "Oom"
+ << " ";
+ }
+ out << "Name/PID\n";
+ break;
+ case Format::CSV:
+ out << "\"Library\",\"Total_RSS\",\"Process\",\"PID\",\"VSS\",\"RSS\",\"PSS\",\"USS\"";
+ if (params->swap_enabled) {
+ out << ",\"Swap\"";
+ }
+ if (params->show_oomadj) {
+ out << ",\"Oomadj\"";
+ }
+ out << "\n";
+ break;
+ case Format::JSON:
+ default:
+ break;
+ }
+}
+
+static void print_library(struct params* params, const LibRecord& lib,
+ std::ostream& out) {
+ if (params->format == Format::RAW) {
+ // clang-format off
+ out << std::setw(6) << lib.pss() << "K"
+ << std::setw(10) << ""
+ << std::setw(9) << ""
+ << std::setw(9) << ""
+ << std::setw(9) << ""
+ << " ";
+ // clang-format on
+ if (params->swap_enabled) {
+ out << std::setw(7) << ""
+ << " ";
+ }
+ if (params->show_oomadj) {
+ out << std::setw(7) << ""
+ << " ";
+ }
+ out << lib.name() << "\n";
+ }
+}
+
+static void print_proc_as_raw(struct params* params, const LibProcRecord& p, std::ostream& out) {
+ const MemUsage& usage = p.usage();
+ // clang-format off
+ out << std::setw(7) << ""
+ << std::setw(9) << usage.vss << "K "
+ << std::setw(6) << usage.rss << "K "
+ << std::setw(6) << usage.pss << "K "
+ << std::setw(6) << usage.uss << "K ";
+ // clang-format on
+ if (params->swap_enabled) {
+ out << std::setw(6) << usage.swap << "K ";
+ }
+ if (params->show_oomadj) {
+ out << std::setw(7) << p.oomadj() << " ";
+ }
+ out << " " << p.cmdline() << " [" << p.pid() << "]\n";
+}
+
+static void print_proc_as_json(struct params* params, const LibRecord& l, const LibProcRecord& p,
+ std::ostream& out) {
+ const MemUsage& usage = p.usage();
+ // clang-format off
+ out << "{\"Library\":" << EscapeJsonString(l.name())
+ << ",\"Total_RSS\":" << l.pss()
+ << ",\"Process\":" << EscapeJsonString(p.cmdline())
+ << ",\"PID\":\"" << p.pid() << "\""
+ << ",\"VSS\":" << usage.vss
+ << ",\"RSS\":" << usage.rss
+ << ",\"PSS\":" << usage.pss
+ << ",\"USS\":" << usage.uss;
+ // clang-format on
+ if (params->swap_enabled) {
+ out << ",\"Swap\":" << usage.swap;
+ }
+ if (params->show_oomadj) {
+ out << ",\"Oom\":" << p.oomadj();
+ }
+ out << "}\n";
+}
+
+static void print_proc_as_csv(struct params* params, const LibRecord& l, const LibProcRecord& p,
+ std::ostream& out) {
+ const MemUsage& usage = p.usage();
+ // clang-format off
+ out << EscapeCsvString(l.name())
+ << "," << l.pss()
+ << "," << EscapeCsvString(p.cmdline())
+ << ",\"[" << p.pid() << "]\""
+ << "," << usage.vss
+ << "," << usage.rss
+ << "," << usage.pss
+ << "," << usage.uss;
+ // clang-format on
+ if (params->swap_enabled) {
+ out << "," << usage.swap;
+ }
+ if (params->show_oomadj) {
+ out << "," << p.oomadj();
+ }
+ out << "\n";
+}
+
+static void print_procs(struct params* params, const LibRecord& lib,
+ const std::vector<LibProcRecord>& procs, std::ostream& out) {
+ for (const LibProcRecord& p : procs) {
+ switch (params->format) {
+ case Format::RAW:
+ print_proc_as_raw(params, p, out);
+ break;
+ case Format::JSON:
+ print_proc_as_json(params, lib, p, out);
+ break;
+ case Format::CSV:
+ print_proc_as_csv(params, lib, p, out);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+} // namespace librank
+
+bool run_librank(uint64_t pgflags, uint64_t pgflags_mask, const std::set<pid_t>& pids,
+ const std::string& lib_prefix, bool all_libs,
+ const std::vector<std::string>& excluded_libs, uint16_t mapflags_mask,
+ Format format, SortOrder sort_order, bool reverse_sort,
+ std::map<pid_t, ProcessRecord>* processrecords_ptr, std::ostream& out,
+ std::ostream& err) {
+ struct librank::params params = {
+ .lib_prefix = lib_prefix,
+ .all_libs = all_libs,
+ .excluded_libs = excluded_libs,
+ .mapflags_mask = mapflags_mask,
+ .format = format,
+ .swap_enabled = false,
+ .show_oomadj = (sort_order == SortOrder::BY_OOMADJ),
+ };
+
+ // Fills in usage info for each LibRecord.
+ std::map<std::string, librank::LibRecord> lib_name_map;
+ if (!librank::populate_libs(¶ms, pgflags, pgflags_mask, pids, lib_name_map,
+ processrecords_ptr, err)) {
+ return false;
+ }
+
+ librank::print_header(¶ms, out);
+
+ // Create vector of all LibRecords, sorted by descending PSS.
+ std::vector<librank::LibRecord> libs;
+ libs.reserve(lib_name_map.size());
+ for (const auto& [k, v] : lib_name_map) {
+ libs.push_back(v);
+ }
+ std::sort(libs.begin(), libs.end(),
+ [](const librank::LibRecord& l1, const librank::LibRecord& l2) {
+ return l1.pss() > l2.pss();
+ });
+
+ std::function<bool(librank::LibProcRecord & a, librank::LibProcRecord & b)> libproc_sort =
+ librank::select_sort(sort_order);
+ for (librank::LibRecord& lib : libs) {
+ // Sort all processes for this library, default is PSS-descending.
+ std::vector<librank::LibProcRecord> procs;
+ procs.reserve(lib.processes().size());
+ for (const auto& [k, v] : lib.processes()) {
+ procs.push_back(v);
+ }
+ if (reverse_sort) {
+ std::sort(procs.rbegin(), procs.rend(), libproc_sort);
+ } else {
+ std::sort(procs.begin(), procs.end(), libproc_sort);
+ }
+
+ librank::print_library(¶ms, lib, out);
+ librank::print_procs(¶ms, lib, procs, out);
+ }
+
+ return true;
+}
+
+namespace showmap {
+
+// These are defined as static variables instead of a struct (as in procrank::params and
+// librank::params) because the collect_vma callback references them.
+static bool show_addr;
+static bool verbose;
+
+static std::string get_vma_name(const Vma& vma, bool total, bool is_bss) {
+ if (total) {
+ return "TOTAL";
+ }
+ std::string vma_name = vma.name;
+ if (is_bss) {
+ vma_name.append(" [bss]");
+ }
+ return vma_name;
+}
+
+static std::string get_flags(const Vma& vma, bool total) {
+ std::string flags_str("---");
+ if (verbose && !total) {
+ if (vma.flags & PROT_READ) flags_str[0] = 'r';
+ if (vma.flags & PROT_WRITE) flags_str[1] = 'w';
+ if (vma.flags & PROT_EXEC) flags_str[2] = 'x';
+ }
+ return flags_str;
+}
+
+struct VmaInfo {
+ Vma vma;
+ bool is_bss;
+ uint32_t count;
+
+ VmaInfo() = default;
+ VmaInfo(const Vma& v) : vma(v), is_bss(false), count(1) {}
+ VmaInfo(const Vma& v, bool bss) : vma(v), is_bss(bss), count(1) {}
+ VmaInfo(const Vma& v, const std::string& name, bool bss) : vma(v), is_bss(bss), count(1) {
+ vma.name = name;
+ }
+
+ void to_raw(bool total, std::ostream& out) const;
+ void to_csv(bool total, std::ostream& out) const;
+ void to_json(bool total, std::ostream& out) const;
+};
+
+void VmaInfo::to_raw(bool total, std::ostream& out) const {
+ if (show_addr) {
+ if (total) {
+ out << " ";
+ } else {
+ out << std::hex << std::setw(16) << vma.start << " " << std::setw(16) << vma.end << " "
+ << std::dec;
+ }
+ }
+ // clang-format off
+ out << std::setw(8) << vma.usage.vss << " "
+ << std::setw(8) << vma.usage.rss << " "
+ << std::setw(8) << vma.usage.pss << " "
+ << std::setw(8) << vma.usage.shared_clean << " "
+ << std::setw(8) << vma.usage.shared_dirty << " "
+ << std::setw(8) << vma.usage.private_clean << " "
+ << std::setw(8) << vma.usage.private_dirty << " "
+ << std::setw(8) << vma.usage.swap << " "
+ << std::setw(8) << vma.usage.swap_pss << " "
+ << std::setw(9) << vma.usage.anon_huge_pages << " "
+ << std::setw(9) << vma.usage.shmem_pmd_mapped << " "
+ << std::setw(9) << vma.usage.file_pmd_mapped << " "
+ << std::setw(8) << vma.usage.shared_hugetlb << " "
+ << std::setw(8) << vma.usage.private_hugetlb << " "
+ << std::setw(8) << vma.usage.locked << " ";
+ // clang-format on
+ if (!verbose && !show_addr) {
+ out << std::setw(4) << count << " ";
+ }
+ if (verbose) {
+ if (total) {
+ out << " ";
+ } else {
+ out << std::setw(5) << get_flags(vma, total) << " ";
+ }
+ }
+ out << get_vma_name(vma, total, is_bss) << "\n";
+}
+
+void VmaInfo::to_csv(bool total, std::ostream& out) const {
+ // clang-format off
+ out << vma.usage.vss
+ << "," << vma.usage.rss
+ << "," << vma.usage.pss
+ << "," << vma.usage.shared_clean
+ << "," << vma.usage.shared_dirty
+ << "," << vma.usage.private_clean
+ << "," << vma.usage.private_dirty
+ << "," << vma.usage.swap
+ << "," << vma.usage.swap_pss
+ << "," << vma.usage.anon_huge_pages
+ << "," << vma.usage.shmem_pmd_mapped
+ << "," << vma.usage.file_pmd_mapped
+ << "," << vma.usage.shared_hugetlb
+ << "," << vma.usage.private_hugetlb
+ << "," << vma.usage.locked;
+ // clang-format on
+ if (show_addr) {
+ out << ",";
+ if (total) {
+ out << ",";
+ } else {
+ out << std::hex << vma.start << "," << vma.end << std::dec;
+ }
+ }
+ if (!verbose && !show_addr) {
+ out << "," << count;
+ }
+ if (verbose) {
+ out << ",";
+ if (!total) {
+ out << EscapeCsvString(get_flags(vma, total));
+ }
+ }
+ out << "," << EscapeCsvString(get_vma_name(vma, total, is_bss)) << "\n";
+}
+
+void VmaInfo::to_json(bool total, std::ostream& out) const {
+ // clang-format off
+ out << "{\"virtual size\":" << vma.usage.vss
+ << ",\"RSS\":" << vma.usage.rss
+ << ",\"PSS\":" << vma.usage.pss
+ << ",\"shared clean\":" << vma.usage.shared_clean
+ << ",\"shared dirty\":" << vma.usage.shared_dirty
+ << ",\"private clean\":" << vma.usage.private_clean
+ << ",\"private dirty\":" << vma.usage.private_dirty
+ << ",\"swap\":" << vma.usage.swap
+ << ",\"swapPSS\":" << vma.usage.swap_pss
+ << ",\"Anon HugePages\":" << vma.usage.anon_huge_pages
+ << ",\"Shmem PmdMapped\":" << vma.usage.shmem_pmd_mapped
+ << ",\"File PmdMapped\":" << vma.usage.file_pmd_mapped
+ << ",\"Shared Hugetlb\":" << vma.usage.shared_hugetlb
+ << ",\"Private Hugetlb\":" << vma.usage.private_hugetlb
+ << ",\"Locked\":" << vma.usage.locked;
+ // clang-format on
+ if (show_addr) {
+ if (total) {
+ out << ",\"start addr\":\"\",\"end addr\":\"\"";
+ } else {
+ out << ",\"start addr\":\"" << std::hex << vma.start << "\",\"end addr\":\"" << vma.end
+ << "\"" << std::dec;
+ }
+ }
+ if (!verbose && !show_addr) {
+ out << ",\"#\":" << count;
+ }
+ if (verbose) {
+ out << ",\"flags\":" << EscapeJsonString(get_flags(vma, total));
+ }
+ out << ",\"object\":" << EscapeJsonString(get_vma_name(vma, total, is_bss)) << "}";
+}
+
+static bool is_library(const std::string& name) {
+ return (name.size() > 4) && (name[0] == '/') && ::android::base::EndsWith(name, ".so");
+}
+
+static void infer_vma_name(VmaInfo& current, const VmaInfo& recent) {
+ if (current.vma.name.empty()) {
+ if (recent.vma.end == current.vma.start && is_library(recent.vma.name)) {
+ current.vma.name = recent.vma.name;
+ current.is_bss = true;
+ } else {
+ current.vma.name = "[anon]";
+ }
+ }
+}
+
+static void add_mem_usage(MemUsage* to, const MemUsage& from) {
+ to->vss += from.vss;
+ to->rss += from.rss;
+ to->pss += from.pss;
+
+ to->swap += from.swap;
+ to->swap_pss += from.swap_pss;
+
+ to->private_clean += from.private_clean;
+ to->private_dirty += from.private_dirty;
+ to->shared_clean += from.shared_clean;
+ to->shared_dirty += from.shared_dirty;
+
+ to->anon_huge_pages += from.anon_huge_pages;
+ to->shmem_pmd_mapped += from.shmem_pmd_mapped;
+ to->file_pmd_mapped += from.file_pmd_mapped;
+ to->shared_hugetlb += from.shared_hugetlb;
+ to->private_hugetlb += from.private_hugetlb;
+}
+
+// A multimap is used instead of a map to allow for duplicate keys in case verbose output is used.
+static std::multimap<std::string, VmaInfo> vmas;
+
+static void collect_vma(const Vma& vma) {
+ static VmaInfo recent;
+ VmaInfo current(vma);
+
+ std::string key;
+ if (show_addr) {
+ // vma.end is included in case vma.start is identical for two VMAs.
+ key = StringPrintf("%16" PRIx64 "%16" PRIx64, vma.start, vma.end);
+ } else {
+ key = vma.name;
+ }
+
+ if (vmas.empty()) {
+ vmas.emplace(key, current);
+ recent = current;
+ return;
+ }
+
+ infer_vma_name(current, recent);
+ recent = current;
+
+ // If sorting by address, the VMA can be placed into the map as-is.
+ if (show_addr) {
+ vmas.emplace(key, current);
+ return;
+ }
+
+ // infer_vma_name() may have changed current.vma.name, so this key needs to be set again before
+ // using it to sort by name. For verbose output, the VMA can immediately be placed into the map.
+ key = current.vma.name;
+ if (verbose) {
+ vmas.emplace(key, current);
+ return;
+ }
+
+ // Coalesces VMAs' usage by name, if !show_addr && !verbose.
+ auto iter = vmas.find(key);
+ if (iter == vmas.end()) {
+ vmas.emplace(key, current);
+ return;
+ }
+
+ VmaInfo& match = iter->second;
+ add_mem_usage(&match.vma.usage, current.vma.usage);
+ match.is_bss &= current.is_bss;
+}
+
+static void print_text_header(std::ostream& out) {
+ if (show_addr) {
+ out << " start end ";
+ }
+ out << " virtual shared shared private private "
+ "Anon Shmem File Shared Private\n";
+ if (show_addr) {
+ out << " addr addr ";
+ }
+ out << " size RSS PSS clean dirty clean dirty swap swapPSS "
+ "HugePages PmdMapped PmdMapped Hugetlb Hugetlb Locked ";
+ if (!verbose && !show_addr) {
+ out << " # ";
+ }
+ if (verbose) {
+ out << "flags ";
+ }
+ out << "object\n";
+}
+
+static void print_text_divider(std::ostream& out) {
+ if (show_addr) {
+ out << "---------------- ---------------- ";
+ }
+ out << "-------- -------- -------- -------- -------- -------- -------- -------- -------- "
+ "--------- --------- --------- -------- -------- -------- ";
+ if (!verbose && !show_addr) {
+ out << "---- ";
+ }
+ if (verbose) {
+ out << "----- ";
+ }
+ out << "------------------------------\n";
+}
+
+static void print_csv_header(std::ostream& out) {
+ out << "\"virtual size\",\"RSS\",\"PSS\",\"shared clean\",\"shared dirty\",\"private clean\","
+ "\"private dirty\",\"swap\",\"swapPSS\",\"Anon HugePages\",\"Shmem PmdMapped\","
+ "\"File PmdMapped\",\"Shared Hugetlb\",\"Private Hugetlb\",\"Locked\"";
+ if (show_addr) {
+ out << ",\"start addr\",\"end addr\"";
+ }
+ if (!verbose && !show_addr) {
+ out << ",\"#\"";
+ }
+ if (verbose) {
+ out << ",\"flags\"";
+ }
+ out << ",\"object\"\n";
+}
+
+static void print_header(Format format, std::ostream& out) {
+ switch (format) {
+ case Format::RAW:
+ print_text_header(out);
+ print_text_divider(out);
+ break;
+ case Format::CSV:
+ print_csv_header(out);
+ break;
+ case Format::JSON:
+ out << "[";
+ break;
+ default:
+ break;
+ }
+}
+
+static void print_vmainfo(const VmaInfo& v, Format format, std::ostream& out) {
+ switch (format) {
+ case Format::RAW:
+ v.to_raw(false, out);
+ break;
+ case Format::CSV:
+ v.to_csv(false, out);
+ break;
+ case Format::JSON:
+ v.to_json(false, out);
+ out << ",";
+ break;
+ default:
+ break;
+ }
+}
+
+static void print_vmainfo_totals(const VmaInfo& total_usage, Format format, std::ostream& out) {
+ switch (format) {
+ case Format::RAW:
+ print_text_divider(out);
+ print_text_header(out);
+ print_text_divider(out);
+ total_usage.to_raw(true, out);
+ break;
+ case Format::CSV:
+ total_usage.to_csv(true, out);
+ break;
+ case Format::JSON:
+ total_usage.to_json(true, out);
+ out << "]\n";
+ break;
+ default:
+ break;
+ }
+}
+
+} // namespace showmap
+
+bool run_showmap(pid_t pid, const std::string& filename, bool terse, bool verbose, bool show_addr,
+ bool quiet, Format format, std::map<pid_t, ProcessRecord>* processrecords_ptr,
+ std::ostream& out, std::ostream& err) {
+ // Accumulated vmas are cleared to account for sequential showmap calls by bugreport_procdump.
+ showmap::vmas.clear();
+
+ showmap::show_addr = show_addr;
+ showmap::verbose = verbose;
+
+ bool success;
+ if (!filename.empty()) {
+ success = ::android::meminfo::ForEachVmaFromFile(filename, showmap::collect_vma);
+ } else if (!processrecords_ptr) {
+ ProcessRecord proc(pid, false, 0, 0, false, false, err);
+ success = proc.ForEachExistingVma(showmap::collect_vma);
+ } else {
+ // Check if a ProcessRecord already exists for this pid, create one if one does not exist.
+ auto iter = processrecords_ptr->find(pid);
+ ProcessRecord& proc =
+ (iter != processrecords_ptr->end())
+ ? iter->second
+ : processrecords_ptr
+ ->emplace(pid, ProcessRecord(pid, false, 0, 0, false, false, err))
+ .first->second;
+ success = proc.ForEachExistingVma(showmap::collect_vma);
+ }
+
+ if (!success) {
+ if (!quiet) {
+ if (!filename.empty()) {
+ err << "Failed to parse file " << filename << "\n";
+ } else {
+ err << "No maps for pid " << pid << "\n";
+ }
+ }
+ return false;
+ }
+
+ showmap::print_header(format, out);
+
+ showmap::VmaInfo total_usage;
+ for (const auto& entry : showmap::vmas) {
+ const showmap::VmaInfo& v = entry.second;
+ showmap::add_mem_usage(&total_usage.vma.usage, v.vma.usage);
+ total_usage.count += v.count;
+ if (terse && !(v.vma.usage.private_dirty || v.vma.usage.private_clean)) {
+ continue;
+ }
+ showmap::print_vmainfo(v, format, out);
+ }
+ showmap::print_vmainfo_totals(total_usage, format, out);
+
+ return true;
+}
+
+namespace bugreport_procdump {
+
+static void create_processrecords(const std::set<pid_t>& pids,
+ std::map<pid_t, ProcessRecord>& processrecords,
+ std::ostream& err) {
+ for (pid_t pid : pids) {
+ ProcessRecord proc(pid, false, 0, 0, true, false, err);
+ if (!proc.valid()) {
+ err << "Could not create a ProcessRecord for pid " << pid << "\n";
+ continue;
+ }
+ processrecords.emplace(pid, std::move(proc));
+ }
+}
+
+static void print_section_start(const std::string& name, std::ostream& out) {
+ out << "------ " << name << " ------\n";
+}
+
+static void print_section_end(const std::string& name,
+ const std::chrono::time_point<std::chrono::steady_clock>& start,
+ std::ostream& out) {
+ // std::ratio<1> represents the period for one second.
+ using floatsecs = std::chrono::duration<float, std::ratio<1>>;
+ auto end = std::chrono::steady_clock::now();
+ std::streamsize precision = out.precision();
+ out << "------ " << std::setprecision(3) << std::fixed << floatsecs(end - start).count()
+ << " was the duration of '" << name << "' ------\n";
+ out << std::setprecision(precision) << std::defaultfloat;
+}
+
+static void call_smaps_of_all_processes(const std::string& filename, bool terse, bool verbose,
+ bool show_addr, bool quiet, Format format,
+ std::map<pid_t, ProcessRecord>& processrecords,
+ std::ostream& out, std::ostream& err) {
+ for (const auto& [pid, record] : processrecords) {
+ std::string showmap_title = StringPrintf("SHOW MAP %d: %s", pid, record.cmdline().c_str());
+
+ auto showmap_start = std::chrono::steady_clock::now();
+ print_section_start(showmap_title, out);
+ run_showmap(pid, filename, terse, verbose, show_addr, quiet, format, &processrecords, out,
+ err);
+ print_section_end(showmap_title, showmap_start, out);
+ }
+}
+
+static void call_librank(const std::set<pid_t>& pids,
+ std::map<pid_t, ProcessRecord>& processrecords, std::ostream& out,
+ std::ostream& err) {
+ auto librank_start = std::chrono::steady_clock::now();
+ print_section_start("LIBRANK", out);
+ run_librank(0, 0, pids, "", false, {"[heap]", "[stack]"}, 0, Format::RAW, SortOrder::BY_PSS,
+ false, &processrecords, out, err);
+ print_section_end("LIBRANK", librank_start, out);
+}
+
+static void call_procrank(const std::set<pid_t>& pids,
+ std::map<pid_t, ProcessRecord>& processrecords, std::ostream& out,
+ std::ostream& err) {
+ auto procrank_start = std::chrono::steady_clock::now();
+ print_section_start("PROCRANK", out);
+ run_procrank(0, 0, pids, false, false, SortOrder::BY_PSS, false, &processrecords, out, err);
+ print_section_end("PROCRANK", procrank_start, out);
+}
+
+} // namespace bugreport_procdump
+
+bool run_bugreport_procdump(std::ostream& out, std::ostream& err) {
+ std::set<pid_t> pids;
+ if (!::android::smapinfo::get_all_pids(&pids)) {
+ err << "Failed to get all pids.\n";
+ return false;
+ }
+
+ // create_processrecords is the only expensive call in this function, as showmap, librank, and
+ // procrank will only print already-collected information. This duration is captured by
+ // dumpstate in the BUGREPORT PROCDUMP section.
+ std::map<pid_t, ProcessRecord> processrecords;
+ bugreport_procdump::create_processrecords(pids, processrecords, err);
+
+ // pids without associated ProcessRecords are removed so that librank/procrank do not fall back
+ // to creating new ProcessRecords for them.
+ for (pid_t pid : pids) {
+ if (processrecords.find(pid) == processrecords.end()) {
+ pids.erase(pid);
+ }
+ }
+
+ auto all_smaps_start = std::chrono::steady_clock::now();
+ bugreport_procdump::print_section_start("SMAPS OF ALL PROCESSES", out);
+ bugreport_procdump::call_smaps_of_all_processes("", false, false, false, true, Format::RAW,
+ processrecords, out, err);
+ bugreport_procdump::print_section_end("SMAPS OF ALL PROCESSES", all_smaps_start, out);
+
+ bugreport_procdump::call_librank(pids, processrecords, out, err);
+ bugreport_procdump::call_procrank(pids, processrecords, out, err);
+
+ return true;
+}
+
+} // namespace smapinfo
+} // namespace android
diff --git a/procmeminfo.cpp b/procmeminfo.cpp
index 13a17da..b0a0491 100644
--- a/procmeminfo.cpp
+++ b/procmeminfo.cpp
@@ -66,6 +66,27 @@
to->shared_dirty += from.shared_dirty;
}
+// Converts MemUsage stats from KB to B in case usage is expected in bytes.
+static void convert_usage_kb_to_b(MemUsage& usage) {
+ // These stats are only populated if /proc/<pid>/smaps is read, so they are excluded:
+ // swap_pss, anon_huge_pages, shmem_pmdmapped, file_pmd_mapped, shared_hugetlb, private_hugetlb.
+ constexpr int conversion_factor = 1024;
+ usage.vss *= conversion_factor;
+ usage.rss *= conversion_factor;
+ usage.pss *= conversion_factor;
+ usage.uss *= conversion_factor;
+
+ usage.swap *= conversion_factor;
+
+ usage.private_clean *= conversion_factor;
+ usage.private_dirty *= conversion_factor;
+
+ usage.shared_clean *= conversion_factor;
+ usage.shared_dirty *= conversion_factor;
+
+ usage.thp *= conversion_factor;
+}
+
// Returns true if the line was valid smaps stats line false otherwise.
static bool parse_smaps_field(const char* line, MemUsage* stats) {
const char *end = line;
@@ -123,6 +144,11 @@
stats->file_pmd_mapped = strtoull(c, nullptr, 10);
}
break;
+ case 'L':
+ if (strncmp(line, "Locked:", 7) == 0) {
+ stats->locked = strtoull(c, nullptr, 10);
+ }
+ break;
}
return true;
}
@@ -167,7 +193,7 @@
return maps_;
}
-const std::vector<Vma>& ProcMemInfo::Smaps(const std::string& path) {
+const std::vector<Vma>& ProcMemInfo::Smaps(const std::string& path, bool collect_usage) {
if (!maps_.empty()) {
return maps_;
}
@@ -176,6 +202,9 @@
if (std::find(g_excluded_vmas.begin(), g_excluded_vmas.end(), vma.name) ==
g_excluded_vmas.end()) {
maps_.emplace_back(vma);
+ if (collect_usage) {
+ add_mem_usage(&usage_, vma.usage);
+ }
}
};
if (path.empty() && !ForEachVma(collect_vmas)) {
@@ -225,6 +254,16 @@
return ForEachVmaFromFile(path, callback, use_smaps);
}
+bool ProcMemInfo::ForEachExistingVma(const VmaCallback& callback) {
+ if (maps_.empty()) {
+ return false;
+ }
+ for (auto& vma : maps_) {
+ callback(vma);
+ }
+ return true;
+}
+
bool ProcMemInfo::ForEachVmaFromMaps(const VmaCallback& callback) {
Vma vma;
auto vmaCollect = [&callback,&vma](const uint64_t start, uint64_t end, uint16_t flags,
@@ -244,6 +283,26 @@
return success;
}
+bool ProcMemInfo::ForEachVmaFromMaps(const VmaCallback& callback, std::string& mapsBuffer) {
+ Vma vma;
+ vma.name.reserve(256);
+ auto vmaCollect = [&callback,&vma](const uint64_t start, uint64_t end, uint16_t flags,
+ uint64_t pgoff, ino_t inode, const char* name, bool shared) {
+ vma.start = start;
+ vma.end = end;
+ vma.flags = flags;
+ vma.offset = pgoff;
+ vma.name = name;
+ vma.inode = inode;
+ vma.is_shared = shared;
+ callback(vma);
+ };
+
+ bool success = ::android::procinfo::ReadProcessMaps(pid_, vmaCollect, mapsBuffer);
+
+ return success;
+}
+
bool ProcMemInfo::SmapsOrRollup(MemUsage* stats) const {
std::string path = ::android::base::StringPrintf(
"/proc/%d/%s", pid_, IsSmapsRollupSupported() ? "smaps_rollup" : "smaps");
@@ -337,6 +396,14 @@
return true;
}
+ if (!GetUsageStats(get_wss, use_pageidle, swap_only)) {
+ maps_.clear();
+ return false;
+ }
+ return true;
+}
+
+bool ProcMemInfo::GetUsageStats(bool get_wss, bool use_pageidle, bool swap_only) {
::android::base::unique_fd pagemap_fd(GetPagemapFd(pid_));
if (pagemap_fd == -1) {
return false;
@@ -346,7 +413,6 @@
if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss, use_pageidle, swap_only)) {
LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start << "-"
<< vma.end << "]";
- maps_.clear();
return false;
}
add_mem_usage(&usage_, vma.usage);
@@ -355,7 +421,7 @@
return true;
}
-bool ProcMemInfo::FillInVmaStats(Vma& vma) {
+bool ProcMemInfo::FillInVmaStats(Vma& vma, bool use_kb) {
::android::base::unique_fd pagemap_fd(GetPagemapFd(pid_));
if (pagemap_fd == -1) {
return false;
@@ -366,6 +432,9 @@
<< vma.end << "]";
return false;
}
+ if (!use_kb) {
+ convert_usage_kb_to_b(vma.usage);
+ }
return true;
}
@@ -377,9 +446,9 @@
return false;
}
- uint64_t pagesz = getpagesize();
- size_t num_pages = (vma.end - vma.start) / pagesz;
- size_t first_page = vma.start / pagesz;
+ uint64_t pagesz_kb = getpagesize() / 1024;
+ size_t num_pages = (vma.end - vma.start) / (pagesz_kb * 1024);
+ size_t first_page = vma.start / (pagesz_kb * 1024);
std::vector<uint64_t> page_cache;
size_t cur_page_cache_index = 0;
@@ -417,7 +486,7 @@
if (!PAGE_PRESENT(page_info) && !PAGE_SWAPPED(page_info)) continue;
if (PAGE_SWAPPED(page_info)) {
- vma.usage.swap += pagesz;
+ vma.usage.swap += pagesz_kb;
swap_offsets_.emplace_back(PAGE_SWAP_OFFSET(page_info));
continue;
}
@@ -434,7 +503,7 @@
}
if (KPAGEFLAG_THP(cur_page_flags)) {
- vma.usage.thp += pagesz;
+ vma.usage.thp += pagesz_kb;
}
// skip unwanted pages from the count
@@ -464,22 +533,22 @@
// This effectively makes vss = rss for the working set is requested.
// The libpagemap implementation returns vss > rss for
// working set, which doesn't make sense.
- vma.usage.vss += pagesz;
+ vma.usage.vss += pagesz_kb;
}
- vma.usage.rss += pagesz;
- vma.usage.uss += is_private ? pagesz : 0;
- vma.usage.pss += pagesz / cur_page_counts;
+ vma.usage.rss += pagesz_kb;
+ vma.usage.uss += is_private ? pagesz_kb : 0;
+ vma.usage.pss += pagesz_kb / cur_page_counts;
if (is_private) {
- vma.usage.private_dirty += is_dirty ? pagesz : 0;
- vma.usage.private_clean += is_dirty ? 0 : pagesz;
+ vma.usage.private_dirty += is_dirty ? pagesz_kb : 0;
+ vma.usage.private_clean += is_dirty ? 0 : pagesz_kb;
} else {
- vma.usage.shared_dirty += is_dirty ? pagesz : 0;
- vma.usage.shared_clean += is_dirty ? 0 : pagesz;
+ vma.usage.shared_dirty += is_dirty ? pagesz_kb : 0;
+ vma.usage.shared_clean += is_dirty ? 0 : pagesz_kb;
}
}
if (!get_wss) {
- vma.usage.vss += pagesz * num_pages;
+ vma.usage.vss += pagesz_kb * num_pages;
}
return true;
}
@@ -639,5 +708,69 @@
return true;
}
+Format GetFormat(std::string_view arg) {
+ if (arg == "json") {
+ return Format::JSON;
+ }
+ if (arg == "csv") {
+ return Format::CSV;
+ }
+ if (arg == "raw") {
+ return Format::RAW;
+ }
+ return Format::INVALID;
+}
+
+std::string EscapeCsvString(const std::string& raw) {
+ std::string ret;
+ for (auto it = raw.cbegin(); it != raw.cend(); it++) {
+ switch (*it) {
+ case '"':
+ ret += "\"";
+ break;
+ default:
+ ret += *it;
+ break;
+ }
+ }
+ return '"' + ret + '"';
+}
+
+std::string EscapeJsonString(const std::string& raw) {
+ std::string ret;
+ for (auto it = raw.cbegin(); it != raw.cend(); it++) {
+ switch (*it) {
+ case '\\':
+ ret += "\\\\";
+ break;
+ case '"':
+ ret += "\\\"";
+ break;
+ case '/':
+ ret += "\\/";
+ break;
+ case '\b':
+ ret += "\\b";
+ break;
+ case '\f':
+ ret += "\\f";
+ break;
+ case '\n':
+ ret += "\\n";
+ break;
+ case '\r':
+ ret += "\\r";
+ break;
+ case '\t':
+ ret += "\\t";
+ break;
+ default:
+ ret += *it;
+ break;
+ }
+ }
+ return '"' + ret + '"';
+}
+
} // namespace meminfo
} // namespace android
diff --git a/sysmeminfo.cpp b/sysmeminfo.cpp
index b7a9c70..e3fb78a 100644
--- a/sysmeminfo.cpp
+++ b/sysmeminfo.cpp
@@ -145,7 +145,7 @@
return true;
}
-uint64_t SysMemInfo::mem_zram_kb(const char* zram_dev_cstr) {
+uint64_t SysMemInfo::mem_zram_kb(const char* zram_dev_cstr) const {
uint64_t mem_zram_total = 0;
if (zram_dev_cstr) {
if (!MemZramDevice(zram_dev_cstr, &mem_zram_total)) {
@@ -174,7 +174,7 @@
return mem_zram_total / 1024;
}
-bool SysMemInfo::MemZramDevice(const char* zram_dev, uint64_t* mem_zram_dev) {
+bool SysMemInfo::MemZramDevice(const char* zram_dev, uint64_t* mem_zram_dev) const {
std::string mmstat = ::android::base::StringPrintf("%s/%s", zram_dev, "mm_stat");
auto mmstat_fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(mmstat.c_str(), "re"), fclose};
if (mmstat_fp != nullptr) {
@@ -204,6 +204,61 @@
return false;
}
+uint64_t SysMemInfo::mem_compacted_kb(const char* zram_dev_cstr) {
+ uint64_t mem_compacted_total = 0;
+ if (zram_dev_cstr) {
+ // Fast-path, single device
+ if (!GetTotalMemCompacted(zram_dev_cstr, &mem_compacted_total)) {
+ return 0;
+ }
+ return mem_compacted_total / 1024;
+ }
+
+ // Slow path - multiple devices
+ constexpr uint32_t kMaxZramDevices = 256;
+ for (uint32_t i = 0; i < kMaxZramDevices; i++) {
+ std::string zram_dev_abspath = ::android::base::StringPrintf("/sys/block/zram%u/", i);
+ if (access(zram_dev_abspath.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_compacted;
+ if (!GetTotalMemCompacted(zram_dev_abspath.c_str(), &mem_compacted)) {
+ return 0;
+ }
+
+ mem_compacted_total += mem_compacted;
+ }
+
+ return mem_compacted_total / 1024; // transform to KBs
+}
+
+// Returns the total memory compacted in bytes which corresponds to the following formula
+// compacted memory = uncompressed memory size - compressed memory size
+bool SysMemInfo::GetTotalMemCompacted(const char* zram_dev, uint64_t* out_mem_compacted) {
+ std::string mmstat = ::android::base::StringPrintf("%s/%s", zram_dev, "mm_stat");
+ auto mmstat_fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(mmstat.c_str(), "re"), fclose};
+ if (mmstat_fp != nullptr) {
+ uint64_t uncompressed_size_bytes;
+ uint64_t compressed_size_bytes;
+
+ if (fscanf(mmstat_fp.get(), "%" SCNu64 "%" SCNu64, &uncompressed_size_bytes,
+ &compressed_size_bytes) != 2) {
+ PLOG(ERROR) << "Malformed mm_stat file in: " << zram_dev;
+ *out_mem_compacted = 0;
+ return false;
+ }
+
+ *out_mem_compacted = uncompressed_size_bytes - compressed_size_bytes;
+ return true;
+ }
+
+ *out_mem_compacted = 0;
+ return false;
+}
+
// Public methods
uint64_t ReadVmallocInfo(const char* path) {
uint64_t vmalloc_total = 0;
@@ -322,7 +377,7 @@
bool ReadPerProcessGpuMem([[maybe_unused]] std::unordered_map<uint32_t, uint64_t>* out) {
#if defined(__ANDROID__) && !defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__)
- static constexpr const char kBpfGpuMemTotalMap[] = "/sys/fs/bpf/map_gpu_mem_gpu_mem_total_map";
+ static constexpr const char kBpfGpuMemTotalMap[] = "/sys/fs/bpf/map_gpuMem_gpu_mem_total_map";
// Use the read-only wrapper BpfMapRO to properly retrieve the read-only map.
auto map = bpf::BpfMapRO<uint64_t, uint64_t>(kBpfGpuMemTotalMap);
@@ -371,7 +426,7 @@
bool ReadProcessGpuUsageKb([[maybe_unused]] uint32_t pid, [[maybe_unused]] uint32_t gpu_id,
uint64_t* size) {
#if defined(__ANDROID__) && !defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__)
- static constexpr const char kBpfGpuMemTotalMap[] = "/sys/fs/bpf/map_gpu_mem_gpu_mem_total_map";
+ static constexpr const char kBpfGpuMemTotalMap[] = "/sys/fs/bpf/map_gpuMem_gpu_mem_total_map";
uint64_t gpu_mem;
diff --git a/tools/Android.bp b/tools/Android.bp
index d336434..71fb2e0 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -27,6 +27,7 @@
shared_libs: [
"libbase",
"libmeminfo",
+ "libsmapinfo",
],
}
@@ -56,6 +57,7 @@
"libbase",
"libmeminfo",
"libprocinfo",
+ "libsmapinfo",
],
}
@@ -71,6 +73,7 @@
shared_libs: [
"libbase",
"libmeminfo",
+ "libsmapinfo",
],
target: {
@@ -93,3 +96,18 @@
"libmeminfo",
],
}
+
+cc_binary {
+ name: "bugreport_procdump",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ srcs: ["bugreport_procdump.cpp"],
+ shared_libs: [
+ "libbase",
+ "libmeminfo",
+ "libsmapinfo",
+ ],
+}
diff --git a/tools/bugreport_procdump.cpp b/tools/bugreport_procdump.cpp
new file mode 100644
index 0000000..bce2c47
--- /dev/null
+++ b/tools/bugreport_procdump.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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 <cstdlib>
+#include <iostream>
+
+#include <smapinfo.h>
+
+int main() {
+ bool success = ::android::smapinfo::run_bugreport_procdump(std::cout, std::cerr);
+ if (!success) {
+ exit(EXIT_FAILURE);
+ }
+ return 0;
+}
diff --git a/tools/librank.cpp b/tools/librank.cpp
index cfc3f3c..57c14c8 100644
--- a/tools/librank.cpp
+++ b/tools/librank.cpp
@@ -14,210 +14,53 @@
* limitations under the License.
*/
-#include <dirent.h>
-#include <errno.h>
#include <error.h>
#include <inttypes.h>
#include <linux/kernel-page-flags.h>
-#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
-#include <unistd.h>
-#include <algorithm>
+#include <iostream>
#include <map>
#include <memory>
-#include <vector>
+#include <set>
-#include <android-base/file.h>
#include <android-base/parseint.h>
-#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-
#include <meminfo/procmeminfo.h>
-using ::android::meminfo::MemUsage;
-using ::android::meminfo::ProcMemInfo;
-using ::android::meminfo::Vma;
+#include <processrecord.h>
+#include <smapinfo.h>
-// The output format that can be specifid by user.
-enum Format {
- RAW = 0,
- JSON,
- CSV
-};
+using ::android::meminfo::Format;
+using ::android::meminfo::GetFormat;
+using ::android::smapinfo::SortOrder;
[[noreturn]] static void usage(int exit_status) {
- fprintf(stderr,
- "Usage: %s [ -P | -L ] [ -v | -r | -p | -u | -s | -h ]\n"
- "\n"
- "Sort options:\n"
- " -v Sort processes by VSS.\n"
- " -r Sort processes by RSS.\n"
- " -p Sort processes by PSS.\n"
- " -u Sort processes by USS.\n"
- " -s Sort processes by swap.\n"
- " (Default sort order is PSS.)\n"
- " -a Show all mappings, including stack, heap and anon.\n"
- " -P /path Limit libraries displayed to those in path.\n"
- " -R Reverse sort order (default is descending).\n"
- " -m [r][w][x] Only list pages that exactly match permissions\n"
- " -c Only show cached (storage backed) pages\n"
- " -C Only show non-cached (ram/swap backed) pages\n"
- " -k Only show pages collapsed by KSM\n"
- " -f [raw][json][csv] Print output in the specified format.\n"
- " (Default format is raw text.)\n"
- " -h Display this help screen.\n",
- getprogname());
+ std::cerr << "Usage: " << getprogname() << " [ -P | -L ] [ -v | -r | -p | -u | -s | -h ]\n"
+ << "\n"
+ << "Sort options:\n"
+ << " -v Sort processes by VSS.\n"
+ << " -r Sort processes by RSS.\n"
+ << " -p Sort processes by PSS.\n"
+ << " -u Sort processes by USS.\n"
+ << " -o Sort (and show) processes by oom score.\n"
+ << " -s Sort processes by swap.\n"
+ << " (Default sort order is PSS.)\n"
+ << " -a Show all mappings, including stack, heap and anon.\n"
+ << " -P /path Limit libraries displayed to those in path.\n"
+ << " -R Reverse sort order (default is descending).\n"
+ << " -m [r][w][x] Only list pages that exactly match permissions\n"
+ << " -c Only show cached (storage backed) pages\n"
+ << " -C Only show non-cached (ram/swap backed) pages\n"
+ << " -k Only show pages collapsed by KSM\n"
+ << " -f [raw][json][csv] Print output in the specified format.\n"
+ << " (Default format is raw text.)\n"
+ << " -h Display this help screen.\n";
exit(exit_status);
}
-static void add_mem_usage(MemUsage* to, const MemUsage& from) {
- to->vss += from.vss;
- to->rss += from.rss;
- to->pss += from.pss;
- to->uss += from.uss;
-
- to->swap += from.swap;
-
- to->private_clean += from.private_clean;
- to->private_dirty += from.private_dirty;
-
- to->shared_clean += from.shared_clean;
- to->shared_dirty += from.shared_dirty;
-}
-
-struct ProcessRecord {
- public:
- ProcessRecord(pid_t pid) : pid_(-1), cmdline_("") {
- std::string fname = ::android::base::StringPrintf("/proc/%d/cmdline", pid);
- std::string cmdline;
- if (!::android::base::ReadFileToString(fname, &cmdline)) {
- fprintf(stderr, "Failed to read cmdline from: %s\n", fname.c_str());
- return;
- }
- // We deliberately don't read the proc/<pid>cmdline file directly into 'cmdline_'
- // because of some processes showing up cmdlines that end with "0x00 0x0A 0x00"
- // e.g. xtra-daemon, lowi-server
- // The .c_str() assignment below then takes care of trimming the cmdline at the first
- // 0x00. This is how original procrank worked (luckily)
- cmdline_ = cmdline.c_str();
- pid_ = pid;
- usage_.clear();
- }
-
- ~ProcessRecord() = default;
-
- bool valid() const { return pid_ != -1; }
-
- // Getters
- pid_t pid() const { return pid_; }
- const std::string& cmdline() const { return cmdline_; }
- const MemUsage& usage() const { return usage_; }
-
- // Add to the usage
- void AddUsage(const MemUsage& mem_usage) { add_mem_usage(&usage_, mem_usage); }
-
- private:
- pid_t pid_;
- std::string cmdline_;
- MemUsage usage_;
-};
-
-struct LibRecord {
- public:
- LibRecord(const std::string& name) : name_(name) {}
- ~LibRecord() = default;
-
- const std::string& name() const { return name_; }
- const MemUsage& usage() const { return usage_; }
- const std::map<pid_t, ProcessRecord>& processes() const { return procs_; }
- uint64_t pss() const { return usage_.pss; }
- void AddUsage(const ProcessRecord& proc, const MemUsage& mem_usage) {
- auto [it, inserted] = procs_.insert(std::pair<pid_t, ProcessRecord>(proc.pid(), proc));
- it->second.AddUsage(mem_usage);
- add_mem_usage(&usage_, mem_usage);
- }
-
- private:
- std::string name_;
- MemUsage usage_;
- std::map<pid_t, ProcessRecord> procs_;
-};
-
-// List of every library / map
-static std::map<std::string, LibRecord> g_libs;
-
-// List of library/map names that we don't want to show by default
-static const std::vector<std::string> g_excluded_libs = {"[heap]", "[stack]"};
-
-// Global flags affected by command line
-static uint64_t g_pgflags = 0;
-static uint64_t g_pgflags_mask = 0;
-static uint16_t g_mapflags_mask = 0;
-static bool g_all_libs = false;
-static bool g_has_swap = false;
-static bool g_reverse_sort = false;
-static std::string g_prefix_filter = "";
-
-static bool read_all_pids(std::function<bool(pid_t pid)> for_each_pid) {
- std::unique_ptr<DIR, int (*)(DIR*)> procdir(opendir("/proc"), closedir);
- if (!procdir) return false;
-
- struct dirent* dir;
- pid_t pid;
- while ((dir = readdir(procdir.get()))) {
- if (!::android::base::ParseInt(dir->d_name, &pid)) continue;
- if (!for_each_pid(pid)) return false;
- }
-
- return true;
-}
-
-static bool scan_libs_per_process(pid_t pid) {
- ProcMemInfo pmem(pid, false, g_pgflags, g_pgflags_mask);
- const std::vector<Vma> maps = pmem.Maps();
- if (maps.size() == 0) {
- // nothing to do here, continue
- return true;
- }
-
- ProcessRecord proc(pid);
- if (!proc.valid()) {
- fprintf(stderr, "Failed to create process record for process: %d\n", pid);
- return false;
- }
-
- for (auto& map : maps) {
- // skip library / map if prefix for the path doesn't match
- if (!g_prefix_filter.empty() && !::android::base::StartsWith(map.name, g_prefix_filter)) {
- continue;
- }
- // Skip maps based on map permissions
- if (g_mapflags_mask &&
- ((map.flags & (PROT_READ | PROT_WRITE | PROT_EXEC)) != g_mapflags_mask)) {
- continue;
- }
-
- // skip excluded library / map names
- if (!g_all_libs && (std::find(g_excluded_libs.begin(), g_excluded_libs.end(),
- map.name) != g_excluded_libs.end())) {
- continue;
- }
-
- auto [it, inserted] =
- g_libs.insert(std::pair<std::string, LibRecord>(map.name, LibRecord(map.name)));
- it->second.AddUsage(proc, map.usage);
-
- if (!g_has_swap && map.usage.swap) {
- g_has_swap = true;
- }
- }
-
- return true;
-}
-
static uint16_t parse_mapflags(const char* mapflags) {
uint16_t ret = 0;
for (const char* p = mapflags; *p; p++) {
@@ -235,247 +78,96 @@
error(EXIT_FAILURE, 0, "Invalid permissions string: %s, %s", mapflags, p);
}
}
-
return ret;
}
-static std::string escape_csv_string(const std::string& raw) {
- std::string ret;
- for (auto it = raw.cbegin(); it != raw.cend(); it++) {
- switch (*it) {
- case '"':
- ret += "\"\"";
- break;
- default:
- ret += *it;
- break;
- }
- }
- return '"' + ret + '"';
-}
-
-std::string to_csv(LibRecord& l, ProcessRecord& p) {
- const MemUsage& usage = p.usage();
- return escape_csv_string(l.name())
- + "," + std::to_string(l.pss() / 1024)
- + "," + escape_csv_string(p.cmdline())
- + ",\"[" + std::to_string(p.pid()) + "]\""
- + "," + std::to_string(usage.vss/1024)
- + "," + std::to_string(usage.rss/1024)
- + "," + std::to_string(usage.pss/1024)
- + "," + std::to_string(usage.uss/1024)
- + (g_has_swap ? "," + std::to_string(usage.swap/1024) : "");
-}
-
-static std::string escape_json_string(const std::string& raw) {
- std::string ret;
- for (auto it = raw.cbegin(); it != raw.cend(); it++) {
- switch (*it) {
- case '\\':
- ret += "\\\\";
- break;
- case '"':
- ret += "\\\"";
- break;
- case '/':
- ret += "\\/";
- break;
- case '\b':
- ret += "\\b";
- break;
- case '\f':
- ret += "\\f";
- break;
- case '\n':
- ret += "\\n";
- break;
- case '\r':
- ret += "\\r";
- break;
- case '\t':
- ret += "\\t";
- break;
- default:
- ret += *it;
- break;
- }
- }
- return '"' + ret + '"';
-}
-
-std::string to_json(LibRecord& l, ProcessRecord& p) {
- const MemUsage& usage = p.usage();
- return "{\"Library\":" + escape_json_string(l.name())
- + ",\"Total_RSS\":" + std::to_string(l.pss() / 1024)
- + ",\"Process\":" + escape_json_string(p.cmdline())
- + ",\"PID\":\"" + std::to_string(p.pid()) + "\""
- + ",\"VSS\":" + std::to_string(usage.vss/1024)
- + ",\"RSS\":" + std::to_string(usage.rss/1024)
- + ",\"PSS\":" + std::to_string(usage.pss/1024)
- + ",\"USS\":" + std::to_string(usage.uss/1024)
- + (g_has_swap ? ",\"Swap\":" + std::to_string(usage.swap/1024) : "")
- + "}";
-}
-
-static Format get_format(std::string arg) {
- if (arg.compare("json") == 0) {
- return JSON;
- }
- if (arg.compare("csv") == 0) {
- return CSV;
- }
- if (arg.compare("raw") == 0) {
- return RAW;
- }
- error(EXIT_FAILURE, 0, "Invalid format.");
- return RAW;
-}
-
int main(int argc, char* argv[]) {
+ uint64_t pgflags = 0;
+ uint64_t pgflags_mask = 0;
+
+ // Library filtering options.
+ std::string lib_prefix = "";
+ bool all_libs = false;
+ std::vector<std::string> excluded_libs = {"[heap]", "[stack]"};
+ uint16_t mapflags_mask = 0;
+
+ // Output format (raw text, JSON, CSV).
+ Format format = Format::RAW;
+
+ // Process sorting options.
+ SortOrder sort_order = SortOrder::BY_PSS;
+ bool reverse_sort = false;
+
int opt;
-
- auto pss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
- return g_reverse_sort ? a.usage().pss < b.usage().pss : a.usage().pss > b.usage().pss;
- };
-
- auto uss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
- return g_reverse_sort ? a.usage().uss < b.usage().uss : a.usage().uss > b.usage().uss;
- };
-
- auto vss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
- return g_reverse_sort ? a.usage().vss < b.usage().vss : a.usage().vss > b.usage().vss;
- };
-
- auto rss_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
- return g_reverse_sort ? a.usage().rss < b.usage().rss : a.usage().rss > b.usage().rss;
- };
-
- auto swap_sort = [](const ProcessRecord& a, const ProcessRecord& b) {
- return g_reverse_sort ? a.usage().swap < b.usage().swap : a.usage().swap > b.usage().swap;
- };
-
- std::function<bool(const ProcessRecord&, const ProcessRecord&)> sort_func = pss_sort;
-
- Format format = RAW;
- while ((opt = getopt(argc, argv, "acCf:hkm:pP:uvrsR")) != -1) {
+ while ((opt = getopt(argc, argv, "acCf:hkm:opP:uvrsR")) != -1) {
switch (opt) {
case 'a':
- g_all_libs = true;
+ all_libs = true;
break;
case 'c':
- g_pgflags = 0;
- g_pgflags_mask = (1 << KPF_SWAPBACKED);
+ pgflags = 0;
+ pgflags_mask = (1 << KPF_SWAPBACKED);
break;
case 'C':
- g_pgflags = g_pgflags_mask = (1 << KPF_SWAPBACKED);
+ pgflags = (1 << KPF_SWAPBACKED);
+ pgflags_mask = (1 << KPF_SWAPBACKED);
break;
case 'f':
- format = get_format(optarg);
+ format = GetFormat(optarg);
+ if (format == Format::INVALID) {
+ std::cerr << "Invalid format." << std::endl;
+ usage(EXIT_FAILURE);
+ }
break;
case 'h':
usage(EXIT_SUCCESS);
case 'k':
- g_pgflags = g_pgflags_mask = (1 << KPF_KSM);
+ pgflags = (1 << KPF_KSM);
+ pgflags_mask = (1 << KPF_KSM);
break;
case 'm':
- g_mapflags_mask = parse_mapflags(optarg);
+ mapflags_mask = parse_mapflags(optarg);
+ break;
+ case 'o':
+ sort_order = SortOrder::BY_OOMADJ;
break;
case 'p':
- sort_func = pss_sort;
+ sort_order = SortOrder::BY_PSS;
break;
case 'P':
- g_prefix_filter = optarg;
- break;
- case 'u':
- sort_func = uss_sort;
- break;
- case 'v':
- sort_func = vss_sort;
+ lib_prefix = optarg;
break;
case 'r':
- sort_func = rss_sort;
- break;
- case 's':
- sort_func = swap_sort;
+ sort_order = SortOrder::BY_RSS;
break;
case 'R':
- g_reverse_sort = true;
+ reverse_sort = true;
+ break;
+ case 's':
+ sort_order = SortOrder::BY_SWAP;
+ break;
+ case 'u':
+ sort_order = SortOrder::BY_USS;
+ break;
+ case 'v':
+ sort_order = SortOrder::BY_VSS;
break;
default:
usage(EXIT_FAILURE);
}
}
- if (!read_all_pids(scan_libs_per_process)) {
- error(EXIT_FAILURE, 0, "Failed to read all pids from the system");
+ std::set<pid_t> pids;
+ if (!::android::smapinfo::get_all_pids(&pids)) {
+ std::cerr << "Failed to get all pids." << std::endl;
+ exit(EXIT_FAILURE);
}
- switch (format) {
- case RAW:
- printf(" %6s %7s %6s %6s %6s ", "RSStot", "VSS", "RSS", "PSS", "USS");
- if (g_has_swap) {
- printf(" %6s ", "Swap");
- }
- printf("Name/PID\n");
- break;
- case CSV:
- printf("\"Library\",\"Total_RSS\",\"Process\",\"PID\",\"VSS\",\"RSS\",\"PSS\",\"USS\"");
- if (g_has_swap) {
- printf(", \"Swap\"");
- }
- printf("\n");
- break;
- case JSON:
- break;
+ bool success = ::android::smapinfo::run_librank(
+ pgflags, pgflags_mask, pids, lib_prefix, all_libs, excluded_libs, mapflags_mask, format,
+ sort_order, reverse_sort, nullptr, std::cout, std::cerr);
+ if (!success) {
+ exit(EXIT_FAILURE);
}
-
- std::vector<LibRecord> v_libs;
- v_libs.reserve(g_libs.size());
- std::transform(g_libs.begin(), g_libs.end(), std::back_inserter(v_libs),
- [] (std::pair<std::string, LibRecord> const& pair) { return pair.second; });
-
- // sort the libraries by their pss
- std::sort(v_libs.begin(), v_libs.end(),
- [](const LibRecord& l1, const LibRecord& l2) { return l1.pss() > l2.pss(); });
-
- for (auto& lib : v_libs) {
- if (format == RAW) {
- printf("%6" PRIu64 "K %7s %6s %6s %6s ", lib.pss() / 1024, "", "", "", "");
- if (g_has_swap) {
- printf(" %6s ", "");
- }
- printf("%s\n", lib.name().c_str());
- }
-
- // sort all mappings first
-
- std::vector<ProcessRecord> procs;
- procs.reserve(lib.processes().size());
- std::transform(lib.processes().begin(), lib.processes().end(), std::back_inserter(procs),
- [] (std::pair<pid_t, ProcessRecord> const& pair) { return pair.second; });
-
- std::sort(procs.begin(), procs.end(), sort_func);
-
- for (auto& p : procs) {
- const MemUsage& usage = p.usage();
- switch (format) {
- case RAW:
- printf(" %6s %7" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ", "",
- usage.vss / 1024, usage.rss / 1024, usage.pss / 1024, usage.uss / 1024);
- if (g_has_swap) {
- printf("%6" PRIu64 "K ", usage.swap / 1024);
- }
- printf(" %s [%d]\n", p.cmdline().c_str(), p.pid());
- break;
- case JSON:
- printf("%s\n", to_json(lib, p).c_str());
- break;
- case CSV:
- printf("%s\n", to_csv(lib, p).c_str());
- break;
- }
- }
- }
-
return 0;
}
diff --git a/tools/procmem.cpp b/tools/procmem.cpp
index 1fe8d50..619f490 100644
--- a/tools/procmem.cpp
+++ b/tools/procmem.cpp
@@ -85,15 +85,14 @@
static void print_stats(std::stringstream& ss, const MemUsage& stats) {
if (!show_wss) {
- ss << ::android::base::StringPrintf("%6" PRIu64 "K ", stats.vss / 1024);
+ ss << ::android::base::StringPrintf("%6" PRIu64 "K ", stats.vss);
}
ss << ::android::base::StringPrintf(
"%6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64
"K %6" PRIu64 "K %6" PRIu64 "K ",
- stats.rss / 1024, stats.pss / 1024, stats.uss / 1024, stats.shared_clean / 1024,
- stats.shared_dirty / 1024, stats.private_clean / 1024, stats.private_dirty / 1024,
- stats.thp / 1024);
+ stats.rss, stats.pss, stats.uss, stats.shared_clean, stats.shared_dirty,
+ stats.private_clean, stats.private_dirty, stats.thp);
}
static int show(const MemUsage& proc_stats, const std::vector<Vma>& maps) {
diff --git a/tools/procrank.cpp b/tools/procrank.cpp
index e4f247a..a5d022d 100644
--- a/tools/procrank.cpp
+++ b/tools/procrank.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,135 +14,26 @@
* limitations under the License.
*/
-#include <android-base/file.h>
-#include <android-base/parseint.h>
-#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
-#include <dirent.h>
-#include <errno.h>
+#include <getopt.h>
#include <inttypes.h>
#include <linux/kernel-page-flags.h>
-#include <linux/oom.h>
-#include <meminfo/procmeminfo.h>
-#include <meminfo/sysmeminfo.h>
-#include <procinfo/process.h>
-#include <stdio.h>
#include <stdlib.h>
-#include <sys/types.h>
-#include <unistd.h>
#include <iostream>
-#include <memory>
+#include <map>
#include <set>
-#include <sstream>
#include <unordered_map>
#include <vector>
-using ::android::meminfo::MemUsage;
-using ::android::meminfo::ProcMemInfo;
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <meminfo/procmeminfo.h>
+#include <procinfo/process.h>
-struct ProcessRecord {
- public:
- ProcessRecord(pid_t pid, bool get_wss = false, uint64_t pgflags = 0, uint64_t pgflags_mask = 0)
- : pid_(-1),
- oomadj_(OOM_SCORE_ADJ_MAX + 1),
- cmdline_(""),
- proportional_swap_(0),
- unique_swap_(0),
- zswap_(0) {
- std::unique_ptr<ProcMemInfo> procmem =
- std::make_unique<ProcMemInfo>(pid, get_wss, pgflags, pgflags_mask);
- if (procmem == nullptr) {
- std::cerr << "Failed to create ProcMemInfo for: " << pid << std::endl;
- return;
- }
+#include <processrecord.h>
+#include <smapinfo.h>
- std::string fname = ::android::base::StringPrintf("/proc/%d/oom_score_adj", pid);
- auto oomscore_fp =
- std::unique_ptr<FILE, decltype(&fclose)>{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_ = "<unknown>";
- }
- // We deliberately don't read the proc/<pid>cmdline file directly into 'cmdline_'
- // because of some processes showing up cmdlines that end with "0x00 0x0A 0x00"
- // e.g. xtra-daemon, lowi-server
- // The .c_str() assignment below then takes care of trimming the cmdline at the first
- // 0x00. This is how original procrank worked (luckily)
- cmdline_.resize(strlen(cmdline_.c_str()));
- usage_or_wss_ = get_wss ? procmem->Wss() : procmem->Usage();
- swap_offsets_ = procmem->SwapOffsets();
- pid_ = pid;
- }
-
- bool valid() const { return pid_ != -1; }
-
- void CalculateSwap(const std::vector<uint16_t>& swap_offset_array,
- float zram_compression_ratio) {
- for (auto& off : swap_offsets_) {
- 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<uint64_t>& SwapOffsets() const { return swap_offsets_; }
- const MemUsage& Usage() const { return usage_or_wss_; }
- const MemUsage& Wss() const { return usage_or_wss_; }
-
- private:
- pid_t pid_;
- int32_t oomadj_;
- std::string cmdline_;
- uint64_t proportional_swap_;
- uint64_t unique_swap_;
- uint64_t zswap_;
- MemUsage usage_or_wss_;
- std::vector<uint64_t> swap_offsets_;
-};
-
-// 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;
+using ::android::smapinfo::SortOrder;
[[noreturn]] static void usage(int exit_status) {
std::cerr << "Usage: " << getprogname() << " [ -W ] [ -v | -r | -p | -u | -s | -h ] [-d PID]"
@@ -166,219 +57,21 @@
exit(exit_status);
}
-static bool read_all_pids(std::set<pid_t>* pids) {
- pids->clear();
- std::unique_ptr<DIR, int (*)(DIR*)> procdir(opendir("/proc"), closedir);
- if (!procdir) return false;
-
- struct dirent* dir;
- pid_t pid;
- while ((dir = readdir(procdir.get()))) {
- if (!::android::base::ParseInt(dir->d_name, &pid)) continue;
- pids->insert(pid);
- }
-
- return true;
-}
-
-static bool count_swap_offsets(const ProcessRecord& proc,
- std::vector<uint16_t>& swap_offset_array) {
- const std::vector<uint64_t>& swp_offs = proc.SwapOffsets();
- for (auto& off : swp_offs) {
- if (off >= swap_offset_array.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<ProcessRecord>& procs,
- const std::vector<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.rss : stats_a.rss > stats_b.rss;
- };
-
- 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<bool(ProcessRecord & a, ProcessRecord & b)> proc_sort = pss_sort;
-
- // count all pages by default
+ // Count all pages by default.
uint64_t pgflags = 0;
uint64_t pgflags_mask = 0;
+ // Sort by PSS descending by default.
+ SortOrder sort_order = SortOrder::BY_PSS;
+ bool reverse_sort = false;
+
+ bool get_oomadj = false;
+ bool get_wss = false;
+ bool reset_wss = false;
+
std::vector<pid_t> descendant_filter;
+
int opt;
while ((opt = getopt(argc, argv, "cCd:hkoprRsuvwW")) != -1) {
switch (opt) {
@@ -406,29 +99,29 @@
pgflags_mask = (1 << KPF_KSM);
break;
case 'o':
- proc_sort = oomadj_sort;
- show_oomadj = true;
+ sort_order = SortOrder::BY_OOMADJ;
+ get_oomadj = true;
break;
case 'p':
- proc_sort = pss_sort;
+ sort_order = SortOrder::BY_PSS;
break;
case 'r':
- proc_sort = rss_sort;
+ sort_order = SortOrder::BY_RSS;
break;
case 'R':
reverse_sort = true;
break;
case 's':
- proc_sort = swap_sort;
+ sort_order = SortOrder::BY_SWAP;
break;
case 'u':
- proc_sort = uss_sort;
+ sort_order = SortOrder::BY_USS;
break;
case 'v':
- proc_sort = vss_sort;
+ sort_order = SortOrder::BY_VSS;
break;
case 'w':
- show_wss = true;
+ get_wss = true;
break;
case 'W':
reset_wss = true;
@@ -439,9 +132,8 @@
}
std::set<pid_t> pids;
- std::vector<ProcessRecord> procs;
- if (!read_all_pids(&pids)) {
- std::cerr << "Failed to read pids" << std::endl;
+ if (!::android::smapinfo::get_all_pids(&pids)) {
+ std::cerr << "Failed to get all pids." << std::endl;
exit(EXIT_FAILURE);
}
@@ -486,103 +178,20 @@
if (reset_wss) {
for (pid_t pid : pids) {
- if (!ProcMemInfo::ResetWorkingSet(pid)) {
+ if (!::android::meminfo::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'
+ // Other options passed to procrank are ignored if reset_wss is true.
return 0;
}
- ::android::meminfo::SysMemInfo smi;
- if (!smi.ReadMemInfo()) {
- std::cerr << "Failed to get system memory info" << std::endl;
+ bool success = ::android::smapinfo::run_procrank(pgflags, pgflags_mask, pids, get_oomadj,
+ get_wss, sort_order, reverse_sort, nullptr,
+ std::cout, std::cerr);
+ if (!success) {
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
- std::vector<uint16_t> swap_offset_array(swap_total / getpagesize() + 1, 0);
- if (has_swap) {
- has_zram = smi.mem_zram_kb() > 0;
- if (has_zram) {
- zram_compression_ratio = static_cast<float>(smi.mem_zram_kb()) /
- (smi.mem_swap_kb() - smi.mem_swap_free_kb());
- }
- }
-
- // Mark each swap offset used by the process as we find them for calculating proportional
- // swap usage later.
- for (pid_t pid : pids) {
- ProcessRecord proc(pid, show_wss, pgflags, pgflags_mask);
- if (!proc.valid()) {
- // Check to see if the process is still around, skip the process if the proc
- // directory is inaccessible. It was most likely killed while creating the process
- // record
- std::string procdir = ::android::base::StringPrintf("/proc/%d", pid);
- if (access(procdir.c_str(), F_OK | R_OK)) continue;
-
- // Warn if we failed to gather process stats even while it is still alive.
- // Return success here, so we continue to print stats for other processes.
- std::cerr << "warning: failed to create process record for: " << pid << std::endl;
- continue;
- }
-
- // Skip processes with no memory mappings
- uint64_t vss = show_wss ? proc.Wss().vss : proc.Usage().vss;
- if (vss == 0) continue;
-
- // collect swap_offset counts from all processes in 1st pass
- if (!show_wss && has_swap && !count_swap_offsets(proc, swap_offset_array)) {
- std::cerr << "Failed to count swap offsets for process: " << pid << std::endl;
- std::cerr << "Failed to read all pids from the system" << std::endl;
- exit(EXIT_FAILURE);
- }
-
- procs.emplace_back(std::move(proc));
- }
-
- 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 << "<empty>" << 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);
-
- // 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;
}
diff --git a/tools/showmap.cpp b/tools/showmap.cpp
index 452e895..198dd7b 100644
--- a/tools/showmap.cpp
+++ b/tools/showmap.cpp
@@ -23,231 +23,35 @@
#include <sys/types.h>
#include <unistd.h>
+#include <iomanip>
+#include <iostream>
+#include <map>
#include <memory>
#include <string>
-#include <vector>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <meminfo/procmeminfo.h>
-using ::android::meminfo::Vma;
+#include <processrecord.h>
+#include <smapinfo.h>
-struct VmaInfo {
- Vma vma;
- bool is_bss;
- uint32_t count;
+using ::android::meminfo::Format;
+using ::android::meminfo::GetFormat;
- VmaInfo() = default;
- VmaInfo(const Vma& v) : vma(v), is_bss(false), count(1) {}
- VmaInfo(const Vma& v, bool bss) : vma(v), is_bss(bss), count(1) {}
- VmaInfo(const Vma& v, const std::string& name, bool bss) : vma(v), is_bss(bss), count(1) {
- vma.name = name;
- }
-};
-
-// Global options
-static std::string g_filename = "";
-static bool g_merge_by_names = false;
-static bool g_terse = false;
-static bool g_verbose = false;
-static bool g_show_addr = false;
-static bool g_quiet = false;
-static pid_t g_pid = -1;
-
-static VmaInfo g_total;
-static std::vector<VmaInfo> g_vmas;
-
-[[noreturn]] static void usage(const char* progname, int exit_status) {
- fprintf(stderr,
- "%s [-aqtv] [-f FILE] PID\n"
- "-a\taddresses (show virtual memory map)\n"
- "-q\tquiet (don't show error if map could not be read)\n"
- "-t\tterse (show only items with private pages)\n"
- "-v\tverbose (don't coalesce maps with the same name)\n"
- "-f\tFILE (read from input from FILE instead of PID)\n",
- progname);
+[[noreturn]] static void usage(int exit_status) {
+ std::cerr << "showmap [-aqtv] [-f FILE] PID\n"
+ << "-a\taddresses (show virtual memory map)\n"
+ << "-q\tquiet (don't show error if map could not be read)\n"
+ << "-t\tterse (show only items with private pages)\n"
+ << "-v\tverbose (don't coalesce maps with the same name)\n"
+ << "-f\tFILE (read from input from FILE instead of PID)\n"
+ << "-o\t[raw][json][csv] Print output in the specified format.\n"
+ << " \tDefault output format is raw text.)\n";
exit(exit_status);
}
-static bool is_library(const std::string& name) {
- return (name.size() > 4) && (name[0] == '/') && ::android::base::EndsWith(name, ".so");
-}
-
-static bool insert_before(const VmaInfo& a, const VmaInfo& b) {
- if (g_show_addr) {
- return (a.vma.start < b.vma.start || (a.vma.start == b.vma.start && a.vma.end < b.vma.end));
- }
-
- return strcmp(a.vma.name.c_str(), b.vma.name.c_str()) < 0;
-}
-
-static void collect_vma(const Vma& vma) {
- if (g_vmas.empty()) {
- g_vmas.emplace_back(vma);
- return;
- }
-
- VmaInfo current(vma);
- VmaInfo& last = g_vmas.back();
- // determine if this is bss;
- if (vma.name.empty()) {
- if (last.vma.end == current.vma.start && is_library(last.vma.name)) {
- current.vma.name = last.vma.name;
- current.is_bss = true;
- } else {
- current.vma.name = "[anon]";
- }
- }
-
- std::vector<VmaInfo>::iterator it;
- for (it = g_vmas.begin(); it != g_vmas.end(); it++) {
- if (g_merge_by_names && (it->vma.name == current.vma.name)) {
- it->vma.usage.vss += current.vma.usage.vss;
- it->vma.usage.rss += current.vma.usage.rss;
- it->vma.usage.pss += current.vma.usage.pss;
-
- it->vma.usage.shared_clean += current.vma.usage.shared_clean;
- it->vma.usage.shared_dirty += current.vma.usage.shared_dirty;
- it->vma.usage.private_clean += current.vma.usage.private_clean;
- it->vma.usage.private_dirty += current.vma.usage.private_dirty;
- it->vma.usage.swap += current.vma.usage.swap;
- it->vma.usage.swap_pss += current.vma.usage.swap_pss;
-
- it->vma.usage.anon_huge_pages += current.vma.usage.anon_huge_pages;
- it->vma.usage.shmem_pmd_mapped += current.vma.usage.shmem_pmd_mapped;
- it->vma.usage.file_pmd_mapped += current.vma.usage.file_pmd_mapped;
- it->vma.usage.shared_hugetlb += current.vma.usage.shared_hugetlb;
- it->vma.usage.private_hugetlb += current.vma.usage.private_hugetlb;
-
- it->is_bss &= current.is_bss;
- it->count++;
-
- break;
- }
-
- if (insert_before(current, *it)) {
- g_vmas.insert(it, current);
- break;
- }
- }
-
- if (it == g_vmas.end()) {
- g_vmas.emplace_back(current);
- }
-}
-
-static void print_header() {
- const char* addr1 = g_show_addr ? " start end " : "";
- const char* addr2 = g_show_addr ? " addr addr " : "";
-
- printf("%s virtual shared shared private private "
- "Anon Shmem File Shared Private\n",
- addr1);
- printf("%s size RSS PSS clean dirty clean dirty swap swapPSS "
- "HugePages PmdMapped PmdMapped Hugetlb Hugetlb",
- addr2);
- if (!g_verbose && !g_show_addr) {
- printf(" # ");
- }
- if (g_verbose) {
- printf(" flags ");
- }
- printf(" object\n");
-}
-
-static void print_divider() {
- if (g_show_addr) {
- printf("-------- -------- ");
- }
- printf("-------- -------- -------- -------- -------- -------- -------- -------- -------- "
- "--------- --------- --------- -------- -------- ");
- if (!g_verbose && !g_show_addr) {
- printf("---- ");
- }
- if (g_verbose) {
- printf("------ ");
- }
- printf("------------------------------\n");
-}
-
-static void print_vmainfo(const VmaInfo& v, bool total) {
- if (g_show_addr) {
- if (total) {
- printf(" ");
- } else {
- printf("%16" PRIx64 " %16" PRIx64 " ", v.vma.start, v.vma.end);
- }
- }
- printf("%8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64
- " %8" PRIu64 " %8" PRIu64 " %9" PRIu64 " %9" PRIu64 " %9" PRIu64 " %8" PRIu64
- " %8" PRIu64 " ",
- v.vma.usage.vss, v.vma.usage.rss, v.vma.usage.pss, v.vma.usage.shared_clean,
- v.vma.usage.shared_dirty, v.vma.usage.private_clean, v.vma.usage.private_dirty,
- v.vma.usage.swap, v.vma.usage.swap_pss, v.vma.usage.anon_huge_pages,
- v.vma.usage.shmem_pmd_mapped, v.vma.usage.file_pmd_mapped, v.vma.usage.shared_hugetlb,
- v.vma.usage.private_hugetlb);
- if (!g_verbose && !g_show_addr) {
- printf("%4" PRIu32 " ", v.count);
- }
- if (g_verbose) {
- if (total) {
- printf(" ");
- } else {
- std::string flags_str("---");
- if (v.vma.flags & PROT_READ) flags_str[0] = 'r';
- if (v.vma.flags & PROT_WRITE) flags_str[1] = 'w';
- if (v.vma.flags & PROT_EXEC) flags_str[2] = 'x';
-
- printf("%6s ", flags_str.c_str());
- }
- }
-}
-
-static int showmap(void) {
- if (!::android::meminfo::ForEachVmaFromFile(g_filename, collect_vma)) {
- if (!g_quiet) {
- fprintf(stderr, "Failed to parse file %s\n", g_filename.c_str());
- }
- return 1;
- }
-
- print_header();
- print_divider();
-
- for (const auto& v : g_vmas) {
- g_total.vma.usage.vss += v.vma.usage.vss;
- g_total.vma.usage.rss += v.vma.usage.rss;
- g_total.vma.usage.pss += v.vma.usage.pss;
-
- g_total.vma.usage.private_clean += v.vma.usage.private_clean;
- g_total.vma.usage.private_dirty += v.vma.usage.private_dirty;
- g_total.vma.usage.shared_clean += v.vma.usage.shared_clean;
- g_total.vma.usage.shared_dirty += v.vma.usage.shared_dirty;
-
- g_total.vma.usage.swap += v.vma.usage.swap;
- g_total.vma.usage.swap_pss += v.vma.usage.swap_pss;
- g_total.count += v.count;
-
- if (g_terse && !(v.vma.usage.private_dirty || v.vma.usage.private_clean)) {
- continue;
- }
-
- print_vmainfo(v, false);
- printf("%s%s\n", v.vma.name.c_str(), v.is_bss ? " [bss]" : "");
- }
-
- print_divider();
- print_header();
- print_divider();
-
- print_vmainfo(g_total, true);
- printf("TOTAL\n");
-
- return 0;
-}
-
int main(int argc, char* argv[]) {
signal(SIGPIPE, SIG_IGN);
struct option longopts[] = {
@@ -255,46 +59,70 @@
{0, 0, nullptr, 0},
};
+ // Printing options.
+ bool terse = false;
+ bool quiet = false;
+
+ // Output options.
+ bool show_addr = false;
+ bool verbose = false;
+ Format format = Format::RAW;
+
+ // 'pid' will be ignored if a file is specified.
+ std::string filename;
+ pid_t pid = 0;
+
int opt;
- while ((opt = getopt_long(argc, argv, "tvaqf:h", longopts, nullptr)) != -1) {
+ while ((opt = getopt_long(argc, argv, "tvaqf:o:h", longopts, nullptr)) != -1) {
switch (opt) {
case 't':
- g_terse = true;
+ terse = true;
break;
case 'a':
- g_show_addr = true;
+ show_addr = true;
break;
case 'v':
- g_verbose = true;
+ verbose = true;
break;
case 'q':
- g_quiet = true;
+ quiet = true;
break;
case 'f':
- g_filename = optarg;
+ filename = optarg;
+ break;
+ case 'o':
+ format = GetFormat(optarg);
+ if (format == Format::INVALID) {
+ std::cerr << "Invalid format.\n";
+ usage(EXIT_FAILURE);
+ }
break;
case 'h':
- usage(argv[0], EXIT_SUCCESS);
+ usage(EXIT_SUCCESS);
default:
- usage(argv[0], EXIT_FAILURE);
+ usage(EXIT_FAILURE);
}
}
- if (g_filename.empty()) {
+ if (filename.empty()) {
if ((argc - 1) < optind) {
- fprintf(stderr, "Invalid arguments: Must provide <pid> at the end\n");
- usage(argv[0], EXIT_FAILURE);
+ std::cerr << "Invalid arguments: Must provide <pid> at the end\n";
+ usage(EXIT_FAILURE);
}
- g_pid = atoi(argv[optind]);
- if (g_pid <= 0) {
- fprintf(stderr, "Invalid process id %s\n", argv[optind]);
- usage(argv[0], EXIT_FAILURE);
+ pid = atoi(argv[optind]);
+ if (pid <= 0) {
+ std::cerr << "Invalid process id " << argv[optind] << "\n";
+ usage(EXIT_FAILURE);
}
-
- g_filename = ::android::base::StringPrintf("/proc/%d/smaps", g_pid);
+ // run_showmap will read directly from this file and ignore the pid argument.
+ filename = ::android::base::StringPrintf("/proc/%d/smaps", pid);
}
- g_merge_by_names = !g_verbose && !g_show_addr;
- return showmap();
+ bool success = ::android::smapinfo::run_showmap(pid, filename, terse, verbose, show_addr, quiet,
+ format, nullptr, std::cout, std::cerr);
+ if (!success) {
+ exit(EXIT_FAILURE);
+ }
+ return 0;
}
diff --git a/tools/wsstop.cpp b/tools/wsstop.cpp
index 368d04e..c0c6a86 100644
--- a/tools/wsstop.cpp
+++ b/tools/wsstop.cpp
@@ -69,9 +69,8 @@
printf("%16" PRIx64 " %16" PRIx64 " ", v.start, v.end);
printf("%8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64
"K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K ",
- v.usage.vss / 1024, v.usage.rss / 1024, v.usage.pss / 1024, v.usage.shared_clean / 1024,
- v.usage.shared_dirty / 1024, v.usage.private_clean / 1024, v.usage.private_dirty / 1024,
- v.usage.swap / 1024, v.usage.swap_pss / 1024);
+ v.usage.vss, v.usage.rss, v.usage.pss, v.usage.shared_clean, v.usage.shared_dirty,
+ v.usage.private_clean, v.usage.private_dirty, v.usage.swap, v.usage.swap_pss);
printf("%s\n", v.name.c_str());
}
diff --git a/vts/vts_meminfo_test.cpp b/vts/vts_meminfo_test.cpp
index cec46d5..a3f3652 100644
--- a/vts/vts_meminfo_test.cpp
+++ b/vts/vts_meminfo_test.cpp
@@ -89,7 +89,7 @@
}
}
-// /sys/fs/bpf/map_gpu_mem_gpu_mem_total_map support is required for devices launching with
+// /sys/fs/bpf/map_gpuMem_gpu_mem_total_map support is required for devices launching with
// Android S and having 5.4 or higher kernel version.
TEST(SysMemInfo, TestGpuTotalUsageKb) {
uint64_t size;