meminfo: Add Smaps(), showmap and friends.
Needed by showmap and also android_s_Debug to classify each allocation
into multiple heaps.
The APIs added are:
ForEachVmaFromFile - Global API to parse a file expected to be in the
same format as /proc/<pid>/smaps and make a callback for each VMA found.
ProcMemInfo::ForEachVma - Same as 'ForEachVmaFromFile' but for a
ProcMemInfo object corresponding to a process(pid).
ProcMemInfo::Smaps - Wrapper to ProcMemInfo::ForEachVma, except the
function collects 'struct Vma' in a member vector and returns the
reference for the same.
Added showmap2 using the APIs and the corresponding tests the same time.
Bug: 111694435
Test: showmap_test.sh
Test: libmeminfo_test 1
Change-Id: I3065809cf94ecf3da88529809701035c47a8ce34
Signed-off-by: Sandeep Patil <sspatil@google.com>
diff --git a/libmeminfo/procmeminfo.cpp b/libmeminfo/procmeminfo.cpp
index 1daff1e..1f8db1a 100644
--- a/libmeminfo/procmeminfo.cpp
+++ b/libmeminfo/procmeminfo.cpp
@@ -54,6 +54,51 @@
to->shared_dirty += from.shared_dirty;
}
+// Returns true if the line was valid smaps stats line false otherwise.
+static bool parse_smaps_field(const char* line, MemUsage* stats) {
+ char field[64];
+ int len;
+ if (sscanf(line, "%63s %n", field, &len) == 1 && *field && field[strlen(field) - 1] == ':') {
+ const char* c = line + len;
+ switch (field[0]) {
+ case 'P':
+ if (strncmp(field, "Pss:", 4) == 0) {
+ stats->pss = strtoull(c, nullptr, 10);
+ } else if (strncmp(field, "Private_Clean:", 14) == 0) {
+ uint64_t prcl = strtoull(c, nullptr, 10);
+ stats->private_clean = prcl;
+ stats->uss += prcl;
+ } else if (strncmp(field, "Private_Dirty:", 14) == 0) {
+ uint64_t prdi = strtoull(c, nullptr, 10);
+ stats->private_dirty = prdi;
+ stats->uss += prdi;
+ }
+ break;
+ case 'S':
+ if (strncmp(field, "Size:", 5) == 0) {
+ stats->vss = strtoull(c, nullptr, 10);
+ } else if (strncmp(field, "Shared_Clean:", 13) == 0) {
+ stats->shared_clean = strtoull(c, nullptr, 10);
+ } else if (strncmp(field, "Shared_Dirty:", 13) == 0) {
+ stats->shared_dirty = strtoull(c, nullptr, 10);
+ } else if (strncmp(field, "Swap:", 5) == 0) {
+ stats->swap = strtoull(c, nullptr, 10);
+ } else if (strncmp(field, "SwapPss:", 8) == 0) {
+ stats->swap_pss = strtoull(c, nullptr, 10);
+ }
+ break;
+ case 'R':
+ if (strncmp(field, "Rss:", 4) == 0) {
+ stats->rss = strtoull(c, nullptr, 10);
+ }
+ break;
+ }
+ return true;
+ }
+
+ return false;
+}
+
bool ProcMemInfo::ResetWorkingSet(pid_t pid) {
std::string clear_refs_path = ::android::base::StringPrintf("/proc/%d/clear_refs", pid);
if (!::android::base::WriteStringToFile("1\n", clear_refs_path)) {
@@ -75,6 +120,25 @@
return maps_;
}
+const std::vector<Vma>& ProcMemInfo::Smaps(const std::string& path) {
+ if (!maps_.empty()) {
+ return maps_;
+ }
+
+ auto collect_vmas = [&](const Vma& vma) { maps_.emplace_back(vma); };
+ if (path.empty() && !ForEachVma(collect_vmas)) {
+ LOG(ERROR) << "Failed to read smaps for Process " << pid_;
+ maps_.clear();
+ }
+
+ if (!path.empty() && !ForEachVmaFromFile(path, collect_vmas)) {
+ LOG(ERROR) << "Failed to read smaps from file " << path;
+ maps_.clear();
+ }
+
+ return maps_;
+}
+
const MemUsage& ProcMemInfo::Usage() {
if (get_wss_) {
LOG(WARNING) << "Trying to read process memory usage for " << pid_
@@ -103,6 +167,11 @@
return wss_;
}
+bool ProcMemInfo::ForEachVma(const VmaCallback& callback) {
+ std::string path = ::android::base::StringPrintf("/proc/%d/smaps", pid_);
+ return ForEachVmaFromFile(path, callback);
+}
+
bool ProcMemInfo::SmapsOrRollup(bool use_rollup, MemUsage* stats) const {
std::string path = ::android::base::StringPrintf("/proc/%d/%s", pid_,
use_rollup ? "smaps_rollup" : "smaps");
@@ -260,6 +329,59 @@
}
// Public APIs
+bool ForEachVmaFromFile(const std::string& path, const VmaCallback& callback) {
+ auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
+ if (fp == nullptr) {
+ return false;
+ }
+
+ char* line = nullptr;
+ bool parsing_vma = false;
+ ssize_t line_len;
+ Vma vma;
+ while ((line_len = getline(&line, 0, fp.get())) > 0) {
+ // Make sure the line buffer terminates like a C string for ReadMapFile
+ line[line_len] = '\0';
+
+ if (parsing_vma) {
+ if (parse_smaps_field(line, &vma.usage)) {
+ // This was a stats field
+ continue;
+ }
+
+ // Done collecting stats, make the call back
+ callback(vma);
+ parsing_vma = false;
+ }
+
+ vma.clear();
+ // If it has, we are looking for the vma stats
+ // 00400000-00409000 r-xp 00000000 fc:00 426998 /usr/lib/gvfs/gvfsd-http
+ if (!::android::procinfo::ReadMapFileContent(
+ line, [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff,
+ const char* name) {
+ vma.start = start;
+ vma.end = end;
+ vma.flags = flags;
+ vma.offset = pgoff;
+ vma.name = name;
+ })) {
+ LOG(ERROR) << "Failed to parse " << path;
+ return false;
+ }
+ parsing_vma = true;
+ }
+
+ // free getline() managed buffer
+ free(line);
+
+ if (parsing_vma) {
+ callback(vma);
+ }
+
+ return true;
+}
+
bool SmapsOrRollupFromFile(const std::string& path, MemUsage* stats) {
auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
if (fp == nullptr) {