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:
parent
fa2d8d5541
commit
82a48b160a
8 changed files with 819 additions and 1 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
86
libmeminfo/testdata1/showmap_test.sh
Executable file
86
libmeminfo/testdata1/showmap_test.sh
Executable 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
|
||||
|
||||
|
122
libmeminfo/testdata1/smaps_short
Normal file
122
libmeminfo/testdata1/smaps_short
Normal 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
|
|
@ -53,3 +53,17 @@ cc_binary {
|
|||
"libmeminfo",
|
||||
],
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "showmap2",
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
],
|
||||
|
||||
srcs: ["showmap.cpp"],
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libmeminfo",
|
||||
],
|
||||
}
|
||||
|
|
265
libmeminfo/tools/showmap.cpp
Normal file
265
libmeminfo/tools/showmap.cpp
Normal 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();
|
||||
}
|
Loading…
Reference in a new issue