libmeminfo: Add SmapsRollup

This adds the tests and SmapsRollup() parsing function in
ProcMemInfo. Adds tests to check the return value as well as
the correctness.

Bug: 111694435
Test: libmeminfo_test 1 --gtest_filter=TestProcMemInfo.*
Test: libmeminfo_benchmark --benchmark_filter=BM_SmapsRollup_
Result:
----------------------------------------------------------
Benchmark                   Time           CPU Iterations
----------------------------------------------------------
BM_SmapsRollup_old       4751 ns       4730 ns     149458
BM_SmapsRollup_new       4858 ns       4837 ns     144636
----------------------------------------------------------

Change-Id: Ia051fe53a7622e3091502ff7166efafae35e7935
Signed-off-by: Sandeep Patil <sspatil@google.com>
This commit is contained in:
Sandeep Patil 2018-12-29 21:05:38 -08:00
parent c24f1e3c63
commit fa2d8d5541
6 changed files with 56543 additions and 5 deletions

View file

@ -32,6 +32,7 @@ struct MemUsage {
uint64_t uss;
uint64_t swap;
uint64_t swap_pss;
uint64_t private_clean;
uint64_t private_dirty;
@ -44,6 +45,7 @@ struct MemUsage {
pss(0),
uss(0),
swap(0),
swap_pss(0),
private_clean(0),
private_dirty(0),
shared_clean(0),
@ -52,7 +54,7 @@ struct MemUsage {
~MemUsage() = default;
void clear() {
vss = rss = pss = uss = swap = 0;
vss = rss = pss = uss = swap = swap_pss = 0;
private_clean = private_dirty = shared_clean = shared_dirty = 0;
}
};

View file

@ -37,6 +37,22 @@ class ProcMemInfo final {
const std::vector<Vma>& Maps();
const MemUsage& Usage();
const MemUsage& Wss();
// 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.
//
// The method populates the following statistics in order to be fast an efficient.
// Pss
// Rss
// Uss
// private_clean
// private_dirty
// SwapPss
//
// All other fields of MemUsage are zeroed.
bool SmapsOrRollup(bool use_rollup, MemUsage* stats) const;
const std::vector<uint16_t>& SwapOffsets();
~ProcMemInfo() = default;
@ -57,5 +73,10 @@ class ProcMemInfo final {
std::vector<uint16_t> swap_offsets_;
};
// 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
bool SmapsOrRollupFromFile(const std::string& path, MemUsage* stats);
} // namespace meminfo
} // namespace android

View file

@ -14,6 +14,7 @@
* limitations under the License.
*/
#include <meminfo/procmeminfo.h>
#include <meminfo/sysmeminfo.h>
#include <fcntl.h>
@ -31,6 +32,9 @@
#include <benchmark/benchmark.h>
using ::android::meminfo::MemUsage;
using ::android::meminfo::ProcMemInfo;
using ::android::meminfo::SmapsOrRollupFromFile;
using ::android::meminfo::SysMemInfo;
enum {
@ -457,4 +461,85 @@ static void BM_VmallocInfo_new(benchmark::State& state) {
}
BENCHMARK(BM_VmallocInfo_new);
// This implementation is picked up as-is from frameworks/base/core/jni/android_os_Debug.cpp
// and only slightly modified to use std:unique_ptr.
static bool get_smaps_rollup(const std::string path, MemUsage* rollup) {
char lineBuffer[1024];
auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
if (fp != nullptr) {
char* line;
while (true) {
if (fgets(lineBuffer, sizeof(lineBuffer), fp.get()) == NULL) {
break;
}
line = lineBuffer;
switch (line[0]) {
case 'P':
if (strncmp(line, "Pss:", 4) == 0) {
char* c = line + 4;
while (*c != 0 && (*c < '0' || *c > '9')) {
c++;
}
rollup->pss += atoi(c);
} else if (strncmp(line, "Private_Clean:", 14) == 0 ||
strncmp(line, "Private_Dirty:", 14) == 0) {
char* c = line + 14;
while (*c != 0 && (*c < '0' || *c > '9')) {
c++;
}
rollup->uss += atoi(c);
}
break;
case 'R':
if (strncmp(line, "Rss:", 4) == 0) {
char* c = line + 4;
while (*c != 0 && (*c < '0' || *c > '9')) {
c++;
}
rollup->rss += atoi(c);
}
break;
case 'S':
if (strncmp(line, "SwapPss:", 8) == 0) {
char* c = line + 8;
long lSwapPss;
while (*c != 0 && (*c < '0' || *c > '9')) {
c++;
}
lSwapPss = atoi(c);
rollup->swap_pss += lSwapPss;
}
break;
}
}
} else {
return false;
}
return true;
}
static void BM_SmapsRollup_old(benchmark::State& state) {
std::string exec_dir = ::android::base::GetExecutableDirectory();
std::string path = ::android::base::StringPrintf("%s/testdata1/smaps", exec_dir.c_str());
for (auto _ : state) {
MemUsage stats;
CHECK_EQ(get_smaps_rollup(path, &stats), true);
CHECK_EQ(stats.pss, 108384);
}
}
BENCHMARK(BM_SmapsRollup_old);
static void BM_SmapsRollup_new(benchmark::State& state) {
std::string exec_dir = ::android::base::GetExecutableDirectory();
std::string path = ::android::base::StringPrintf("%s/testdata1/smaps", exec_dir.c_str());
for (auto _ : state) {
MemUsage stats;
CHECK_EQ(SmapsOrRollupFromFile(path, &stats), true);
CHECK_EQ(stats.pss, 108384);
}
}
BENCHMARK(BM_SmapsRollup_new);
BENCHMARK_MAIN();

View file

@ -246,13 +246,13 @@ TEST_F(ValidatePageAcct, TestPageIdle) {
}
}
TEST(TestProcMemInfo, TestMapsEmpty) {
TEST(TestProcMemInfo, MapsEmpty) {
ProcMemInfo proc_mem(pid);
const std::vector<Vma>& maps = proc_mem.Maps();
EXPECT_GT(maps.size(), 0);
}
TEST(TestProcMemInfo, TestUsageEmpty) {
TEST(TestProcMemInfo, UsageEmpty) {
// If we created the object for getting working set,
// the usage must be empty
ProcMemInfo proc_mem(pid, true);
@ -264,7 +264,7 @@ TEST(TestProcMemInfo, TestUsageEmpty) {
EXPECT_EQ(usage.swap, 0);
}
TEST(TestProcMemInfoWssReset, TestWssEmpty) {
TEST(TestProcMemInfo, WssEmpty) {
// If we created the object for getting usage,
// the working set must be empty
ProcMemInfo proc_mem(pid, false);
@ -276,7 +276,7 @@ TEST(TestProcMemInfoWssReset, TestWssEmpty) {
EXPECT_EQ(wss.swap, 0);
}
TEST(TestProcMemInfoWssReset, TestSwapOffsetsEmpty) {
TEST(TestProcMemInfo, SwapOffsetsEmpty) {
// If we created the object for getting working set,
// the swap offsets must be empty
ProcMemInfo proc_mem(pid, true);
@ -284,6 +284,87 @@ TEST(TestProcMemInfoWssReset, TestSwapOffsetsEmpty) {
EXPECT_EQ(swap_offsets.size(), 0);
}
TEST(TestProcMemInfo, SmapsOrRollupReturn) {
// if /proc/<pid>/smaps_rollup file exists, .SmapsRollup() must return true;
// false otherwise
std::string path = ::android::base::StringPrintf("/proc/%d/smaps_rollup", pid);
ProcMemInfo proc_mem(pid);
MemUsage stats;
EXPECT_EQ(!access(path.c_str(), F_OK), proc_mem.SmapsOrRollup(true, &stats));
}
TEST(TestProcMemInfo, SmapsOrRollupTest) {
std::string rollup =
R"rollup(12c00000-7fe859e000 ---p 00000000 00:00 0 [rollup]
Rss: 331908 kB
Pss: 202052 kB
Shared_Clean: 158492 kB
Shared_Dirty: 18928 kB
Private_Clean: 90472 kB
Private_Dirty: 64016 kB
Referenced: 318700 kB
Anonymous: 81984 kB
AnonHugePages: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 5344 kB
SwapPss: 442 kB
Locked: 1523537 kB)rollup";
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
ASSERT_TRUE(::android::base::WriteStringToFd(rollup, tf.fd));
MemUsage stats;
ASSERT_EQ(SmapsOrRollupFromFile(tf.path, &stats), true);
EXPECT_EQ(stats.rss, 331908);
EXPECT_EQ(stats.pss, 202052);
EXPECT_EQ(stats.uss, 154488);
EXPECT_EQ(stats.private_clean, 90472);
EXPECT_EQ(stats.private_dirty, 64016);
EXPECT_EQ(stats.swap_pss, 442);
}
TEST(TestProcMemInfo, SmapsOrRollupSmapsTest) {
// This is a made up smaps for the test
std::string smaps =
R"smaps(12c00000-13440000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
Name: [anon:dalvik-main space (region space)]
Size: 8448 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 2652 kB
Pss: 2652 kB
Shared_Clean: 840 kB
Shared_Dirty: 40 kB
Private_Clean: 84 kB
Private_Dirty: 2652 kB
Referenced: 2652 kB
Anonymous: 2652 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 102 kB
SwapPss: 70 kB
Locked: 2652 kB
VmFlags: rd wr mr mw me ac
)smaps";
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
ASSERT_TRUE(::android::base::WriteStringToFd(smaps, tf.fd));
MemUsage stats;
ASSERT_EQ(SmapsOrRollupFromFile(tf.path, &stats), true);
EXPECT_EQ(stats.rss, 2652);
EXPECT_EQ(stats.pss, 2652);
EXPECT_EQ(stats.uss, 2736);
EXPECT_EQ(stats.private_clean, 84);
EXPECT_EQ(stats.private_dirty, 2652);
EXPECT_EQ(stats.swap_pss, 70);
}
TEST(ValidateProcMemInfoFlags, TestPageFlags1) {
// Create proc object using libpagemap
pm_kernel_t* ker;

View file

@ -30,6 +30,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <procinfo/process_map.h>
@ -102,6 +103,12 @@ const MemUsage& ProcMemInfo::Wss() {
return wss_;
}
bool ProcMemInfo::SmapsOrRollup(bool use_rollup, MemUsage* stats) const {
std::string path = ::android::base::StringPrintf("/proc/%d/%s", pid_,
use_rollup ? "smaps_rollup" : "smaps");
return SmapsOrRollupFromFile(path, stats);
};
const std::vector<uint16_t>& ProcMemInfo::SwapOffsets() {
if (get_wss_) {
LOG(WARNING) << "Trying to read process swap offsets for " << pid_
@ -252,5 +259,50 @@ bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) {
return true;
}
// Public APIs
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) {
return false;
}
char line[1024];
stats->clear();
while (fgets(line, sizeof(line), fp.get()) != nullptr) {
switch (line[0]) {
case 'P':
if (strncmp(line, "Pss:", 4) == 0) {
char* c = line + 4;
stats->pss += strtoull(c, nullptr, 10);
} else if (strncmp(line, "Private_Clean:", 14) == 0) {
char* c = line + 14;
uint64_t prcl = strtoull(c, nullptr, 10);
stats->private_clean += prcl;
stats->uss += prcl;
} else if (strncmp(line, "Private_Dirty:", 14) == 0) {
char* c = line + 14;
uint64_t prdi = strtoull(c, nullptr, 10);
stats->private_dirty += prdi;
stats->uss += prdi;
}
break;
case 'R':
if (strncmp(line, "Rss:", 4) == 0) {
char* c = line + 4;
stats->rss += strtoull(c, nullptr, 10);
}
break;
case 'S':
if (strncmp(line, "SwapPss:", 8) == 0) {
char* c = line + 8;
stats->swap_pss += strtoull(c, nullptr, 10);
}
break;
}
}
return true;
}
} // namespace meminfo
} // namespace android

56297
libmeminfo/testdata1/smaps Normal file

File diff suppressed because it is too large Load diff