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>
This commit is contained in:
Sandeep Patil 2019-01-01 16:04:04 -08:00
parent fa2d8d5541
commit 82a48b160a
8 changed files with 819 additions and 1 deletions

View file

@ -16,6 +16,7 @@
#pragma once
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
@ -66,10 +67,16 @@ struct Vma {
uint16_t flags;
std::string name;
Vma() : start(0), end(0), offset(0), flags(0), name("") {}
Vma(uint64_t s, uint64_t e, uint64_t off, uint16_t f, const char* n)
: start(s), end(e), offset(off), flags(f), name(n) {}
~Vma() = default;
void clear() {
memset(&usage, 0, sizeof(usage));
memset(&wss, 0, sizeof(wss));
}
// Memory usage of this mapping.
MemUsage usage;
// Working set within this mapping.

View file

@ -26,6 +26,8 @@
namespace android {
namespace meminfo {
using VmaCallback = std::function<void(const Vma&)>;
class ProcMemInfo final {
// Per-process memory accounting
public:
@ -38,6 +40,18 @@ class ProcMemInfo final {
const MemUsage& Usage();
const MemUsage& Wss();
// Collect all 'vma' or 'maps' from /proc/<pid>/smaps and store them in 'maps_'. Returns a
// constant reference to the vma vector after the collection is done.
//
// Each 'struct Vma' is *fully* populated by this method (unlike SmapsOrRollup).
const std::vector<Vma>& Smaps(const std::string& path = "");
// This method reads /proc/<pid>/smaps and calls the callback() for each
// vma or map that it finds. The map is converted to 'struct Vma' object which is then
// passed to the callback.
// Returns 'false' if the file is malformed.
bool ForEachVma(const VmaCallback& callback);
// Used to parse either of /proc/<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.
@ -49,7 +63,6 @@ class ProcMemInfo final {
// private_clean
// private_dirty
// SwapPss
//
// All other fields of MemUsage are zeroed.
bool SmapsOrRollup(bool use_rollup, MemUsage* stats) const;
@ -73,6 +86,10 @@ class ProcMemInfo final {
std::vector<uint16_t> swap_offsets_;
};
// Makes callback for each 'vma' or 'map' found in file provided. The file is expected to be in the
// same format as /proc/<pid>/smaps. Returns 'false' if the file is malformed.
bool ForEachVmaFromFile(const std::string& path, const VmaCallback& callback);
// Same as ProcMemInfo::SmapsOrRollup but reads the statistics directly
// from a file. The file MUST be in the same format as /proc/<pid>/smaps
// or /proc/<pid>/smaps_rollup

View file

@ -365,6 +365,191 @@ VmFlags: rd wr mr mw me ac
EXPECT_EQ(stats.swap_pss, 70);
}
TEST(TestProcMemInfo, ForEachVmaFromFileTest) {
std::string exec_dir = ::android::base::GetExecutableDirectory();
std::string path = ::android::base::StringPrintf("%s/testdata1/smaps_short", exec_dir.c_str());
ProcMemInfo proc_mem(pid);
std::vector<Vma> vmas;
auto collect_vmas = [&](const Vma& v) { vmas.push_back(v); };
ASSERT_TRUE(ForEachVmaFromFile(path, collect_vmas));
// Expect values to be equal to what we have in testdata1/smaps_short
// Check for sizes first
ASSERT_EQ(vmas[0].usage.vss, 32768);
EXPECT_EQ(vmas[1].usage.vss, 11204);
EXPECT_EQ(vmas[2].usage.vss, 16896);
EXPECT_EQ(vmas[3].usage.vss, 260);
EXPECT_EQ(vmas[4].usage.vss, 6060);
EXPECT_EQ(vmas[5].usage.vss, 4);
// Check for names
EXPECT_EQ(vmas[0].name, "[anon:dalvik-zygote-jit-code-cache]");
EXPECT_EQ(vmas[1].name, "/system/framework/x86_64/boot-framework.art");
EXPECT_EQ(vmas[2].name, "[anon:libc_malloc]");
EXPECT_EQ(vmas[3].name, "/system/priv-app/SettingsProvider/oat/x86_64/SettingsProvider.odex");
EXPECT_EQ(vmas[4].name, "/system/lib64/libhwui.so");
EXPECT_EQ(vmas[5].name, "[vsyscall]");
EXPECT_EQ(vmas[0].usage.rss, 2048);
EXPECT_EQ(vmas[1].usage.rss, 11188);
EXPECT_EQ(vmas[2].usage.rss, 15272);
EXPECT_EQ(vmas[3].usage.rss, 260);
EXPECT_EQ(vmas[4].usage.rss, 4132);
EXPECT_EQ(vmas[5].usage.rss, 0);
EXPECT_EQ(vmas[0].usage.pss, 113);
EXPECT_EQ(vmas[1].usage.pss, 2200);
EXPECT_EQ(vmas[2].usage.pss, 15272);
EXPECT_EQ(vmas[3].usage.pss, 260);
EXPECT_EQ(vmas[4].usage.pss, 1274);
EXPECT_EQ(vmas[5].usage.pss, 0);
EXPECT_EQ(vmas[0].usage.uss, 0);
EXPECT_EQ(vmas[1].usage.uss, 1660);
EXPECT_EQ(vmas[2].usage.uss, 15272);
EXPECT_EQ(vmas[3].usage.uss, 260);
EXPECT_EQ(vmas[4].usage.uss, 0);
EXPECT_EQ(vmas[5].usage.uss, 0);
EXPECT_EQ(vmas[0].usage.private_clean, 0);
EXPECT_EQ(vmas[1].usage.private_clean, 0);
EXPECT_EQ(vmas[2].usage.private_clean, 0);
EXPECT_EQ(vmas[3].usage.private_clean, 260);
EXPECT_EQ(vmas[4].usage.private_clean, 0);
EXPECT_EQ(vmas[5].usage.private_clean, 0);
EXPECT_EQ(vmas[0].usage.private_dirty, 0);
EXPECT_EQ(vmas[1].usage.private_dirty, 1660);
EXPECT_EQ(vmas[2].usage.private_dirty, 15272);
EXPECT_EQ(vmas[3].usage.private_dirty, 0);
EXPECT_EQ(vmas[4].usage.private_dirty, 0);
EXPECT_EQ(vmas[5].usage.private_dirty, 0);
EXPECT_EQ(vmas[0].usage.shared_clean, 0);
EXPECT_EQ(vmas[1].usage.shared_clean, 80);
EXPECT_EQ(vmas[2].usage.shared_clean, 0);
EXPECT_EQ(vmas[3].usage.shared_clean, 0);
EXPECT_EQ(vmas[4].usage.shared_clean, 4132);
EXPECT_EQ(vmas[5].usage.shared_clean, 0);
EXPECT_EQ(vmas[0].usage.shared_dirty, 2048);
EXPECT_EQ(vmas[1].usage.shared_dirty, 9448);
EXPECT_EQ(vmas[2].usage.shared_dirty, 0);
EXPECT_EQ(vmas[3].usage.shared_dirty, 0);
EXPECT_EQ(vmas[4].usage.shared_dirty, 0);
EXPECT_EQ(vmas[5].usage.shared_dirty, 0);
EXPECT_EQ(vmas[0].usage.swap, 0);
EXPECT_EQ(vmas[1].usage.swap, 0);
EXPECT_EQ(vmas[2].usage.swap, 0);
EXPECT_EQ(vmas[3].usage.swap, 0);
EXPECT_EQ(vmas[4].usage.swap, 0);
EXPECT_EQ(vmas[5].usage.swap, 0);
EXPECT_EQ(vmas[0].usage.swap_pss, 0);
EXPECT_EQ(vmas[1].usage.swap_pss, 0);
EXPECT_EQ(vmas[2].usage.swap_pss, 0);
EXPECT_EQ(vmas[3].usage.swap_pss, 0);
EXPECT_EQ(vmas[4].usage.swap_pss, 0);
EXPECT_EQ(vmas[5].usage.swap_pss, 0);
}
TEST(TestProcMemInfo, SmapsReturnTest) {
ProcMemInfo proc_mem(pid);
auto vmas = proc_mem.Smaps();
EXPECT_FALSE(vmas.empty());
}
TEST(TestProcMemInfo, SmapsTest) {
std::string exec_dir = ::android::base::GetExecutableDirectory();
std::string path = ::android::base::StringPrintf("%s/testdata1/smaps_short", exec_dir.c_str());
ProcMemInfo proc_mem(pid);
auto vmas = proc_mem.Smaps(path);
ASSERT_FALSE(vmas.empty());
// Expect values to be equal to what we have in testdata1/smaps_short
// Check for sizes first
ASSERT_EQ(vmas[0].usage.vss, 32768);
EXPECT_EQ(vmas[1].usage.vss, 11204);
EXPECT_EQ(vmas[2].usage.vss, 16896);
EXPECT_EQ(vmas[3].usage.vss, 260);
EXPECT_EQ(vmas[4].usage.vss, 6060);
EXPECT_EQ(vmas[5].usage.vss, 4);
// Check for names
EXPECT_EQ(vmas[0].name, "[anon:dalvik-zygote-jit-code-cache]");
EXPECT_EQ(vmas[1].name, "/system/framework/x86_64/boot-framework.art");
EXPECT_EQ(vmas[2].name, "[anon:libc_malloc]");
EXPECT_EQ(vmas[3].name, "/system/priv-app/SettingsProvider/oat/x86_64/SettingsProvider.odex");
EXPECT_EQ(vmas[4].name, "/system/lib64/libhwui.so");
EXPECT_EQ(vmas[5].name, "[vsyscall]");
EXPECT_EQ(vmas[0].usage.rss, 2048);
EXPECT_EQ(vmas[1].usage.rss, 11188);
EXPECT_EQ(vmas[2].usage.rss, 15272);
EXPECT_EQ(vmas[3].usage.rss, 260);
EXPECT_EQ(vmas[4].usage.rss, 4132);
EXPECT_EQ(vmas[5].usage.rss, 0);
EXPECT_EQ(vmas[0].usage.pss, 113);
EXPECT_EQ(vmas[1].usage.pss, 2200);
EXPECT_EQ(vmas[2].usage.pss, 15272);
EXPECT_EQ(vmas[3].usage.pss, 260);
EXPECT_EQ(vmas[4].usage.pss, 1274);
EXPECT_EQ(vmas[5].usage.pss, 0);
EXPECT_EQ(vmas[0].usage.uss, 0);
EXPECT_EQ(vmas[1].usage.uss, 1660);
EXPECT_EQ(vmas[2].usage.uss, 15272);
EXPECT_EQ(vmas[3].usage.uss, 260);
EXPECT_EQ(vmas[4].usage.uss, 0);
EXPECT_EQ(vmas[5].usage.uss, 0);
EXPECT_EQ(vmas[0].usage.private_clean, 0);
EXPECT_EQ(vmas[1].usage.private_clean, 0);
EXPECT_EQ(vmas[2].usage.private_clean, 0);
EXPECT_EQ(vmas[3].usage.private_clean, 260);
EXPECT_EQ(vmas[4].usage.private_clean, 0);
EXPECT_EQ(vmas[5].usage.private_clean, 0);
EXPECT_EQ(vmas[0].usage.private_dirty, 0);
EXPECT_EQ(vmas[1].usage.private_dirty, 1660);
EXPECT_EQ(vmas[2].usage.private_dirty, 15272);
EXPECT_EQ(vmas[3].usage.private_dirty, 0);
EXPECT_EQ(vmas[4].usage.private_dirty, 0);
EXPECT_EQ(vmas[5].usage.private_dirty, 0);
EXPECT_EQ(vmas[0].usage.shared_clean, 0);
EXPECT_EQ(vmas[1].usage.shared_clean, 80);
EXPECT_EQ(vmas[2].usage.shared_clean, 0);
EXPECT_EQ(vmas[3].usage.shared_clean, 0);
EXPECT_EQ(vmas[4].usage.shared_clean, 4132);
EXPECT_EQ(vmas[5].usage.shared_clean, 0);
EXPECT_EQ(vmas[0].usage.shared_dirty, 2048);
EXPECT_EQ(vmas[1].usage.shared_dirty, 9448);
EXPECT_EQ(vmas[2].usage.shared_dirty, 0);
EXPECT_EQ(vmas[3].usage.shared_dirty, 0);
EXPECT_EQ(vmas[4].usage.shared_dirty, 0);
EXPECT_EQ(vmas[5].usage.shared_dirty, 0);
EXPECT_EQ(vmas[0].usage.swap, 0);
EXPECT_EQ(vmas[1].usage.swap, 0);
EXPECT_EQ(vmas[2].usage.swap, 0);
EXPECT_EQ(vmas[3].usage.swap, 0);
EXPECT_EQ(vmas[4].usage.swap, 0);
EXPECT_EQ(vmas[5].usage.swap, 0);
EXPECT_EQ(vmas[0].usage.swap_pss, 0);
EXPECT_EQ(vmas[1].usage.swap_pss, 0);
EXPECT_EQ(vmas[2].usage.swap_pss, 0);
EXPECT_EQ(vmas[3].usage.swap_pss, 0);
EXPECT_EQ(vmas[4].usage.swap_pss, 0);
EXPECT_EQ(vmas[5].usage.swap_pss, 0);
}
TEST(ValidateProcMemInfoFlags, TestPageFlags1) {
// Create proc object using libpagemap
pm_kernel_t* ker;

View file

@ -54,6 +54,51 @@ static void add_mem_usage(MemUsage* to, const MemUsage& from) {
to->shared_dirty += from.shared_dirty;
}
// Returns true if the line was valid smaps stats line false otherwise.
static bool parse_smaps_field(const char* line, MemUsage* stats) {
char field[64];
int len;
if (sscanf(line, "%63s %n", field, &len) == 1 && *field && field[strlen(field) - 1] == ':') {
const char* c = line + len;
switch (field[0]) {
case 'P':
if (strncmp(field, "Pss:", 4) == 0) {
stats->pss = strtoull(c, nullptr, 10);
} else if (strncmp(field, "Private_Clean:", 14) == 0) {
uint64_t prcl = strtoull(c, nullptr, 10);
stats->private_clean = prcl;
stats->uss += prcl;
} else if (strncmp(field, "Private_Dirty:", 14) == 0) {
uint64_t prdi = strtoull(c, nullptr, 10);
stats->private_dirty = prdi;
stats->uss += prdi;
}
break;
case 'S':
if (strncmp(field, "Size:", 5) == 0) {
stats->vss = strtoull(c, nullptr, 10);
} else if (strncmp(field, "Shared_Clean:", 13) == 0) {
stats->shared_clean = strtoull(c, nullptr, 10);
} else if (strncmp(field, "Shared_Dirty:", 13) == 0) {
stats->shared_dirty = strtoull(c, nullptr, 10);
} else if (strncmp(field, "Swap:", 5) == 0) {
stats->swap = strtoull(c, nullptr, 10);
} else if (strncmp(field, "SwapPss:", 8) == 0) {
stats->swap_pss = strtoull(c, nullptr, 10);
}
break;
case 'R':
if (strncmp(field, "Rss:", 4) == 0) {
stats->rss = strtoull(c, nullptr, 10);
}
break;
}
return true;
}
return false;
}
bool ProcMemInfo::ResetWorkingSet(pid_t pid) {
std::string clear_refs_path = ::android::base::StringPrintf("/proc/%d/clear_refs", pid);
if (!::android::base::WriteStringToFile("1\n", clear_refs_path)) {
@ -75,6 +120,25 @@ const std::vector<Vma>& ProcMemInfo::Maps() {
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 @@ const MemUsage& ProcMemInfo::Wss() {
return wss_;
}
bool ProcMemInfo::ForEachVma(const VmaCallback& callback) {
std::string path = ::android::base::StringPrintf("/proc/%d/smaps", pid_);
return ForEachVmaFromFile(path, callback);
}
bool ProcMemInfo::SmapsOrRollup(bool use_rollup, MemUsage* stats) const {
std::string path = ::android::base::StringPrintf("/proc/%d/%s", pid_,
use_rollup ? "smaps_rollup" : "smaps");
@ -260,6 +329,59 @@ bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) {
}
// Public APIs
bool ForEachVmaFromFile(const std::string& path, const VmaCallback& callback) {
auto fp = std::unique_ptr<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) {

View file

@ -0,0 +1,86 @@
#! /system/bin/sh
TESTDATA_PATH=/data/nativetest64/libmeminfo_test/testdata1
SMAPS=$TESTDATA_PATH/smaps
OUT1=$TMPDIR/1.txt
OUT2=$TMPDIR/2.txt
showmap -f $SMAPS > $OUT1
showmap2 -f $SMAPS > $OUT2
diff $OUT1 $OUT2 > /dev/null
ret=$?
if [[ $ret != 0 ]]; then
echo "fail: showmap -f <smaps>";
else
echo "pass: showmap -f <smaps>"
fi
showmap -q -f $SMAPS > $OUT1
showmap2 -q -f $SMAPS > $OUT2
diff $OUT1 $OUT2 > /dev/null
ret=$?
if [[ $ret != 0 ]]; then
echo "fail: showmap -q -f <smaps>";
else
echo "pass: showmap -q -f <smaps>"
fi
showmap -v -f $SMAPS > $OUT1
showmap2 -v -f $SMAPS > $OUT2
diff $OUT1 $OUT2 > /dev/null
ret=$?
if [[ $ret != 0 ]]; then
echo "fail: showmap -v -f <smaps>";
else
echo "pass: showmap -v -f <smaps>"
fi
showmap -a -f $SMAPS > $OUT1
showmap2 -a -f $SMAPS > $OUT2
diff $OUT1 $OUT2 > /dev/null
ret=$?
if [[ $ret != 0 ]]; then
echo "fail: showmap -a -f <smaps>";
else
echo "pass: showmap -a -f <smaps>"
fi
# Note that all tests from here down that have the option
# '-a' added to the command are expected to fail as
# 'showmap2' actually fixes the 64-bit address truncating
# that was already happening with showmap
showmap -a -t -f $SMAPS > $OUT1
showmap2 -a -t -f $SMAPS > $OUT2
diff $OUT1 $OUT2 > /dev/null
ret=$?
if [[ $ret != 0 ]]; then
echo "fail: showmap -a -t -f <smaps>";
else
echo "pass: showmap -a -t -f <smaps>"
fi
showmap -a -t -v -f $SMAPS > $OUT1
showmap2 -a -t -v -f $SMAPS > $OUT2
diff $OUT1 $OUT2 > /dev/null
ret=$?
if [[ $ret != 0 ]]; then
echo "fail: showmap -a -t -v -f <smaps>";
else
echo "pass: showmap -a -t -v -f <smaps>"
fi
# Note: This test again is expected to fail as the new
# showmap fixes an issue with -t where the tool was only
# showing maps with private dirty pages. The '-t' option was however
# supposed to show all maps that have 'private' pages, clean or dirty.
showmap -t -f $SMAPS > $OUT1
showmap2 -t -f $SMAPS > $OUT2
diff $OUT1 $OUT2 > /dev/null
ret=$?
if [[ $ret != 0 ]]; then
echo "fail: showmap -t -f <smaps>";
else
echo "pass: showmap -t -f <smaps>"
fi

View file

@ -0,0 +1,122 @@
54c00000-56c00000 r-xp 00000000 00:00 0 [anon:dalvik-zygote-jit-code-cache]
Name: [anon:dalvik-zygote-jit-code-cache]
Size: 32768 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 2048 kB
Pss: 113 kB
Shared_Clean: 0 kB
Shared_Dirty: 2048 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 2048 kB
Anonymous: 2048 kB
AnonHugePages: 2048 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 113 kB
VmFlags: rd ex mr mw me ac
701ea000-70cdb000 rw-p 00000000 fe:00 3165 /system/framework/x86_64/boot-framework.art
Size: 11204 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 11188 kB
Pss: 2200 kB
Shared_Clean: 80 kB
Shared_Dirty: 9448 kB
Private_Clean: 0 kB
Private_Dirty: 1660 kB
Referenced: 9892 kB
Anonymous: 11108 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 2200 kB
VmFlags: rd wr mr mw me ac
70074dd8d000-70074ee0d000 rw-p 00000000 00:00 0 [anon:libc_malloc]
Name: [anon:libc_malloc]
Size: 16896 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 15272 kB
Pss: 15272 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 15272 kB
Referenced: 11156 kB
Anonymous: 15272 kB
AnonHugePages: 6144 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 15272 kB
VmFlags: rd wr mr mw me ac
700755a2d000-700755a6e000 r-xp 00016000 fe:00 1947 /system/priv-app/SettingsProvider/oat/x86_64/SettingsProvider.odex
Size: 260 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 260 kB
Pss: 260 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 260 kB
Private_Dirty: 0 kB
Referenced: 260 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 260 kB
VmFlags: rd ex mr mw me
7007f85b0000-7007f8b9b000 r-xp 001ee000 fe:00 1537 /system/lib64/libhwui.so
Size: 6060 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 4132 kB
Pss: 1274 kB
Shared_Clean: 4132 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 4132 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 1274 kB
VmFlags: rd ex mr mw me
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Size: 4 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 0 kB
Pss: 0 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 0 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
VmFlags: rd ex

View file

@ -53,3 +53,17 @@ cc_binary {
"libmeminfo",
],
}
cc_binary {
name: "showmap2",
cflags: [
"-Wall",
"-Werror",
],
srcs: ["showmap.cpp"],
shared_libs: [
"libbase",
"libmeminfo",
],
}

View file

@ -0,0 +1,265 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <getopt.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <string>
#include <vector>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <meminfo/procmeminfo.h>
using ::android::meminfo::Vma;
struct VmaInfo {
Vma vma;
bool is_bss;
uint32_t count;
VmaInfo() = default;
VmaInfo(const Vma& v) : vma(v), is_bss(false), count(1) {}
VmaInfo(const Vma& v, bool bss) : vma(v), is_bss(bss), count(1) {}
VmaInfo(const Vma& v, const std::string& name, bool bss) : vma(v), is_bss(bss), count(1) {
vma.name = name;
}
};
// Global options
static std::string g_filename = "";
static bool g_merge_by_names = false;
static bool g_terse = false;
static bool g_verbose = false;
static bool g_show_addr = false;
static bool g_quiet = false;
static pid_t g_pid = -1;
static VmaInfo g_total;
static std::vector<VmaInfo> g_vmas;
[[noreturn]] static void usage(int exit_status) {
fprintf(stderr,
"%s [-aqtv] [-f FILE] PID\n"
"-a\taddresses (show virtual memory map)\n"
"-q\tquiet (don't show error if map could not be read)\n"
"-t\tterse (show only items with private pages)\n"
"-v\tverbose (don't coalesce maps with the same name)\n"
"-f\tFILE (read from input from FILE instead of PID)\n",
getprogname());
exit(exit_status);
}
static bool is_library(const std::string& name) {
return (name.size() > 4) && (name[0] == '/') && ::android::base::EndsWith(name, ".so");
}
static bool insert_before(const VmaInfo& a, const VmaInfo& b) {
if (g_show_addr) {
return (a.vma.start < b.vma.start || (a.vma.start == b.vma.start && a.vma.end < b.vma.end));
}
return strcmp(a.vma.name.c_str(), b.vma.name.c_str()) < 0;
}
static void collect_vma(const Vma& vma) {
if (g_vmas.empty()) {
g_vmas.emplace_back(vma);
return;
}
VmaInfo current(vma);
VmaInfo& last = g_vmas.back();
// determine if this is bss;
if (vma.name.empty()) {
if (last.vma.end == current.vma.start && is_library(last.vma.name)) {
current.vma.name = last.vma.name;
current.is_bss = true;
} else {
current.vma.name = "[anon]";
}
}
std::vector<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->is_bss &= current.is_bss;
it->count++;
break;
}
if (insert_before(current, *it)) {
g_vmas.insert(it, current);
break;
}
}
if (it == g_vmas.end()) {
g_vmas.emplace_back(current);
}
}
static void print_header() {
const char* addr1 = g_show_addr ? " start end " : "";
const char* addr2 = g_show_addr ? " addr addr " : "";
printf("%s virtual shared shared private private\n", addr1);
printf("%s size RSS PSS clean dirty clean dirty swap swapPSS",
addr2);
if (!g_verbose && !g_show_addr) {
printf(" # ");
}
printf(" object\n");
}
static void print_divider() {
if (g_show_addr) {
printf("-------- -------- ");
}
printf("-------- -------- -------- -------- -------- -------- -------- -------- -------- ");
if (!g_verbose && !g_show_addr) {
printf("---- ");
}
printf("------------------------------\n");
}
static void print_vmainfo(const VmaInfo& v, bool total) {
if (g_show_addr) {
if (total) {
printf(" ");
} else {
printf("%16" PRIx64 " %16" PRIx64 " ", v.vma.start, v.vma.end);
}
}
printf("%8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64
" %8" PRIu64 " %8" PRIu64 " ",
v.vma.usage.vss, v.vma.usage.rss, v.vma.usage.pss, v.vma.usage.shared_clean,
v.vma.usage.shared_dirty, v.vma.usage.private_clean, v.vma.usage.private_dirty,
v.vma.usage.swap, v.vma.usage.swap_pss);
if (!g_verbose && !g_show_addr) {
printf("%4" PRIu32 " ", v.count);
}
}
static int showmap(void) {
if (!::android::meminfo::ForEachVmaFromFile(g_filename, collect_vma)) {
if (!g_quiet) {
fprintf(stderr, "Failed to parse file %s\n", g_filename.c_str());
}
return 1;
}
print_header();
print_divider();
for (const auto& v : g_vmas) {
g_total.vma.usage.vss += v.vma.usage.vss;
g_total.vma.usage.rss += v.vma.usage.rss;
g_total.vma.usage.pss += v.vma.usage.pss;
g_total.vma.usage.private_clean += v.vma.usage.private_clean;
g_total.vma.usage.private_dirty += v.vma.usage.private_dirty;
g_total.vma.usage.shared_clean += v.vma.usage.shared_clean;
g_total.vma.usage.shared_dirty += v.vma.usage.shared_dirty;
g_total.vma.usage.swap += v.vma.usage.swap;
g_total.vma.usage.swap_pss += v.vma.usage.swap_pss;
g_total.count += v.count;
if (g_terse && !(v.vma.usage.private_dirty || v.vma.usage.private_clean)) {
continue;
}
print_vmainfo(v, false);
printf("%s%s\n", v.vma.name.c_str(), v.is_bss ? " [bss]" : "");
}
print_divider();
print_header();
print_divider();
print_vmainfo(g_total, true);
printf("TOTAL\n");
return 0;
}
int main(int argc, char* argv[]) {
signal(SIGPIPE, SIG_IGN);
struct option longopts[] = {
{"help", no_argument, nullptr, 'h'},
{0, 0, nullptr, 0},
};
int opt;
while ((opt = getopt_long(argc, argv, "tvaqf:h", longopts, nullptr)) != -1) {
switch (opt) {
case 't':
g_terse = true;
break;
case 'a':
g_show_addr = true;
break;
case 'v':
g_verbose = true;
break;
case 'q':
g_quiet = true;
break;
case 'f':
g_filename = optarg;
break;
case 'h':
usage(EXIT_SUCCESS);
default:
usage(EXIT_FAILURE);
}
}
if (g_filename.empty()) {
if ((argc - 1) < optind) {
fprintf(stderr, "Invalid arguments: Must provide <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(EXIT_FAILURE);
}
g_filename = ::android::base::StringPrintf("/proc/%d/smaps", g_pid);
}
g_merge_by_names = !g_verbose && !g_show_addr;
return showmap();
}