libmeminfo: Add libmeminfo to gather global and per-process memory stats
The library is expected to be a unified place for all components to read both global and per-process memory accounting form kernel including getting the working set. This change adds the PageInfo, MemInfo and ProcMemInfo classes and verifies the implementation against libpagemap for correctness. Adds a procmem2 tool show the usage. TODO: Plumbing in os_debug, add vmastats, zoneinfo etc parsing. Test: libmeminfo_test 1 Test: procmem2 1 Test: procmem2 -h -W 1 Test: procmem2 -h -w 1 Test: libmeminfo_benchmark Bug: 111694435 Bug: 114325007 Change-Id: I280440b1dc26a498170686d10fcf63f953a0dcbd Signed-off-by: Sandeep Patil <sspatil@google.com>
This commit is contained in:
parent
e0c3a8d97b
commit
54d8721374
14 changed files with 1572 additions and 0 deletions
1
libmeminfo/.clang-format
Symbolic link
1
libmeminfo/.clang-format
Symbolic link
|
@ -0,0 +1 @@
|
|||
../.clang-format-4
|
70
libmeminfo/Android.bp
Normal file
70
libmeminfo/Android.bp
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Copyright (C) 2018 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.
|
||||
//
|
||||
|
||||
cc_defaults {
|
||||
name: "libmeminfo_defaults",
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
],
|
||||
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"liblog",
|
||||
"libprocinfo",
|
||||
],
|
||||
}
|
||||
|
||||
cc_library {
|
||||
name: "libmeminfo",
|
||||
defaults: ["libmeminfo_defaults"],
|
||||
export_include_dirs: ["include"],
|
||||
export_shared_lib_headers: ["libbase"],
|
||||
srcs: [
|
||||
"pageacct.cpp",
|
||||
"procmeminfo.cpp",
|
||||
"sysmeminfo.cpp",
|
||||
],
|
||||
}
|
||||
|
||||
cc_test {
|
||||
name: "libmeminfo_test",
|
||||
defaults: ["libmeminfo_defaults"],
|
||||
|
||||
static_libs: [
|
||||
"libmeminfo",
|
||||
"libpagemap",
|
||||
"libbase",
|
||||
"liblog",
|
||||
],
|
||||
|
||||
srcs: [
|
||||
"libmeminfo_test.cpp"
|
||||
],
|
||||
}
|
||||
|
||||
cc_benchmark {
|
||||
name: "libmeminfo_benchmark",
|
||||
srcs: [
|
||||
"libmeminfo_benchmark.cpp",
|
||||
],
|
||||
static_libs : [
|
||||
"libbase",
|
||||
"liblog",
|
||||
"libmeminfo",
|
||||
"libprocinfo",
|
||||
],
|
||||
}
|
75
libmeminfo/include/meminfo/meminfo.h
Normal file
75
libmeminfo/include/meminfo/meminfo.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <unistd.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
namespace meminfo {
|
||||
|
||||
struct MemUsage {
|
||||
uint64_t vss;
|
||||
uint64_t rss;
|
||||
uint64_t pss;
|
||||
uint64_t uss;
|
||||
|
||||
uint64_t private_clean;
|
||||
uint64_t private_dirty;
|
||||
uint64_t shared_clean;
|
||||
uint64_t shared_dirty;
|
||||
|
||||
MemUsage()
|
||||
: vss(0),
|
||||
rss(0),
|
||||
pss(0),
|
||||
uss(0),
|
||||
private_clean(0),
|
||||
private_dirty(0),
|
||||
shared_clean(0),
|
||||
shared_dirty(0) {}
|
||||
|
||||
~MemUsage() = default;
|
||||
|
||||
void clear() {
|
||||
vss = rss = pss = uss = 0;
|
||||
private_clean = private_dirty = shared_clean = shared_dirty = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct Vma {
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
uint64_t offset;
|
||||
uint16_t flags;
|
||||
std::string 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;
|
||||
|
||||
// Memory usage of this mapping.
|
||||
MemUsage usage;
|
||||
// Working set within this mapping.
|
||||
MemUsage wss;
|
||||
};
|
||||
|
||||
} // namespace meminfo
|
||||
} // namespace android
|
69
libmeminfo/include/meminfo/pageacct.h
Normal file
69
libmeminfo/include/meminfo/pageacct.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <unistd.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
namespace android {
|
||||
namespace meminfo {
|
||||
|
||||
class PageAcct final {
|
||||
// Class for per-page accounting by using kernel provided interfaces like
|
||||
// kpagecount, kpageflags etc.
|
||||
public:
|
||||
static bool KernelHasPageIdle() {
|
||||
return (access("/sys/kernel/mm/page_idle/bitmap", R_OK | W_OK) == 0);
|
||||
}
|
||||
|
||||
bool InitPageAcct(bool pageidle_enable = false);
|
||||
bool PageFlags(uint64_t pfn, uint64_t* flags);
|
||||
bool PageMapCount(uint64_t pfn, uint64_t* mapcount);
|
||||
|
||||
int IsPageIdle(uint64_t pfn);
|
||||
|
||||
// The only way to create PageAcct object
|
||||
static PageAcct& Instance() {
|
||||
static PageAcct instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
~PageAcct() = default;
|
||||
|
||||
private:
|
||||
PageAcct() : kpagecount_fd_(-1), kpageflags_fd_(-1), pageidle_fd_(-1) {}
|
||||
int MarkPageIdle(uint64_t pfn) const;
|
||||
int GetPageIdle(uint64_t pfn) const;
|
||||
|
||||
// Non-copyable & Non-movable
|
||||
PageAcct(const PageAcct&) = delete;
|
||||
PageAcct& operator=(const PageAcct&) = delete;
|
||||
PageAcct& operator=(PageAcct&&) = delete;
|
||||
PageAcct(PageAcct&&) = delete;
|
||||
|
||||
::android::base::unique_fd kpagecount_fd_;
|
||||
::android::base::unique_fd kpageflags_fd_;
|
||||
::android::base::unique_fd pageidle_fd_;
|
||||
};
|
||||
|
||||
} // namespace meminfo
|
||||
} // namespace android
|
55
libmeminfo/include/meminfo/procmeminfo.h
Normal file
55
libmeminfo/include/meminfo/procmeminfo.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <string>
|
||||
#include <vector>
|
||||
|
||||
#include "meminfo.h"
|
||||
|
||||
namespace android {
|
||||
namespace meminfo {
|
||||
|
||||
class ProcMemInfo final {
|
||||
// Per-process memory accounting
|
||||
public:
|
||||
ProcMemInfo(pid_t pid, bool get_wss = false);
|
||||
|
||||
const std::vector<Vma>& Maps();
|
||||
const MemUsage& Usage();
|
||||
const MemUsage& Wss();
|
||||
|
||||
bool WssReset();
|
||||
~ProcMemInfo() = default;
|
||||
|
||||
private:
|
||||
bool ReadMaps(bool get_wss);
|
||||
bool ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss);
|
||||
|
||||
pid_t pid_;
|
||||
bool get_wss_;
|
||||
|
||||
std::vector<Vma> maps_;
|
||||
|
||||
MemUsage usage_;
|
||||
MemUsage wss_;
|
||||
};
|
||||
|
||||
} // namespace meminfo
|
||||
} // namespace android
|
62
libmeminfo/include/meminfo/sysmeminfo.h
Normal file
62
libmeminfo/include/meminfo/sysmeminfo.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
namespace meminfo {
|
||||
|
||||
class SysMemInfo final {
|
||||
// System or Global memory accounting
|
||||
public:
|
||||
static const std::vector<std::string> kDefaultSysMemInfoTags;
|
||||
|
||||
SysMemInfo() = default;
|
||||
|
||||
// Parse /proc/meminfo and read values that are needed
|
||||
bool ReadMemInfo(const std::string& path = "/proc/meminfo");
|
||||
bool ReadMemInfo(const std::vector<std::string>& tags,
|
||||
const std::string& path = "/proc/meminfo");
|
||||
|
||||
// getters
|
||||
uint64_t mem_total_kb() { return mem_in_kb_["MemTotal:"]; }
|
||||
uint64_t mem_free_kb() { return mem_in_kb_["MemFree:"]; }
|
||||
uint64_t mem_buffers_kb() { return mem_in_kb_["Buffers:"]; }
|
||||
uint64_t mem_cached_kb() { return mem_in_kb_["Cached:"]; }
|
||||
uint64_t mem_shmem_kb() { return mem_in_kb_["Shmem:"]; }
|
||||
uint64_t mem_slab_kb() { return mem_in_kb_["Slab:"]; }
|
||||
uint64_t mem_slab_reclailmable_kb() { return mem_in_kb_["SReclaimable:"]; }
|
||||
uint64_t mem_slab_unreclaimable_kb() { return mem_in_kb_["SUnreclaim:"]; }
|
||||
uint64_t mem_swap_kb() { return mem_in_kb_["SwapTotal:"]; }
|
||||
uint64_t mem_free_swap_kb() { return mem_in_kb_["SwapFree:"]; }
|
||||
uint64_t mem_zram_kb() { return mem_in_kb_["Zram:"]; }
|
||||
uint64_t mem_mapped_kb() { return mem_in_kb_["Mapped:"]; }
|
||||
uint64_t mem_vmalloc_used_kb() { return mem_in_kb_["VmallocUsed:"]; }
|
||||
uint64_t mem_page_tables_kb() { return mem_in_kb_["PageTables:"]; }
|
||||
uint64_t mem_kernel_stack_kb() { return mem_in_kb_["KernelStack:"]; }
|
||||
|
||||
private:
|
||||
std::map<std::string, uint64_t> mem_in_kb_;
|
||||
};
|
||||
|
||||
} // namespace meminfo
|
||||
} // namespace android
|
218
libmeminfo/libmeminfo_benchmark.cpp
Normal file
218
libmeminfo/libmeminfo_benchmark.cpp
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <meminfo/sysmeminfo.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/test_utils.h>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
enum {
|
||||
MEMINFO_TOTAL,
|
||||
MEMINFO_FREE,
|
||||
MEMINFO_BUFFERS,
|
||||
MEMINFO_CACHED,
|
||||
MEMINFO_SHMEM,
|
||||
MEMINFO_SLAB,
|
||||
MEMINFO_SLAB_RECLAIMABLE,
|
||||
MEMINFO_SLAB_UNRECLAIMABLE,
|
||||
MEMINFO_SWAP_TOTAL,
|
||||
MEMINFO_SWAP_FREE,
|
||||
MEMINFO_ZRAM_TOTAL,
|
||||
MEMINFO_MAPPED,
|
||||
MEMINFO_VMALLOC_USED,
|
||||
MEMINFO_PAGE_TABLES,
|
||||
MEMINFO_KERNEL_STACK,
|
||||
MEMINFO_COUNT
|
||||
};
|
||||
|
||||
void get_mem_info(uint64_t mem[], const char* file) {
|
||||
char buffer[4096];
|
||||
unsigned int numFound = 0;
|
||||
|
||||
int fd = open(file, O_RDONLY);
|
||||
|
||||
if (fd < 0) {
|
||||
printf("Unable to open %s: %s\n", file, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
const int len = read(fd, buffer, sizeof(buffer) - 1);
|
||||
close(fd);
|
||||
|
||||
if (len < 0) {
|
||||
printf("Empty %s\n", file);
|
||||
return;
|
||||
}
|
||||
buffer[len] = 0;
|
||||
|
||||
static const char* const tags[] = {
|
||||
"MemTotal:", "MemFree:", "Buffers:", "Cached:", "Shmem:", "Slab:",
|
||||
"SReclaimable:", "SUnreclaim:", "SwapTotal:", "SwapFree:", "ZRam:", "Mapped:",
|
||||
"VmallocUsed:", "PageTables:", "KernelStack:", NULL};
|
||||
|
||||
static const int tagsLen[] = {9, 8, 8, 7, 6, 5, 13, 11, 10, 9, 5, 7, 12, 11, 12, 0};
|
||||
|
||||
memset(mem, 0, sizeof(uint64_t) * 15);
|
||||
char* p = buffer;
|
||||
while (*p && (numFound < (sizeof(tagsLen) / sizeof(tagsLen[0])))) {
|
||||
int i = 0;
|
||||
while (tags[i]) {
|
||||
//std::cout << "tag =" << tags[i] << " p = " << std::string(p, tagsLen[i]) << std::endl;
|
||||
if (strncmp(p, tags[i], tagsLen[i]) == 0) {
|
||||
p += tagsLen[i];
|
||||
while (*p == ' ') p++;
|
||||
char* num = p;
|
||||
while (*p >= '0' && *p <= '9') p++;
|
||||
if (*p != 0) {
|
||||
*p = 0;
|
||||
p++;
|
||||
}
|
||||
mem[i] = atoll(num);
|
||||
numFound++;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
while (*p && *p != '\n') {
|
||||
p++;
|
||||
}
|
||||
if (*p) p++;
|
||||
}
|
||||
}
|
||||
|
||||
static void BM_ParseSysMemInfo(benchmark::State& state) {
|
||||
std::string meminfo = R"meminfo(MemTotal: 3019740 kB
|
||||
MemFree: 1809728 kB
|
||||
MemAvailable: 2546560 kB
|
||||
Buffers: 54736 kB
|
||||
Cached: 776052 kB
|
||||
SwapCached: 0 kB
|
||||
Active: 445856 kB
|
||||
Inactive: 459092 kB
|
||||
Active(anon): 78492 kB
|
||||
Inactive(anon): 2240 kB
|
||||
Active(file): 367364 kB
|
||||
Inactive(file): 456852 kB
|
||||
Unevictable: 3096 kB
|
||||
Mlocked: 3096 kB
|
||||
SwapTotal: 0 kB
|
||||
SwapFree: 0 kB
|
||||
Dirty: 32 kB
|
||||
Writeback: 0 kB
|
||||
AnonPages: 74988 kB
|
||||
Mapped: 62624 kB
|
||||
Shmem: 4020 kB
|
||||
Slab: 86464 kB
|
||||
SReclaimable: 44432 kB
|
||||
SUnreclaim: 42032 kB
|
||||
KernelStack: 4880 kB
|
||||
PageTables: 2900 kB
|
||||
NFS_Unstable: 0 kB
|
||||
Bounce: 0 kB
|
||||
WritebackTmp: 0 kB
|
||||
CommitLimit: 1509868 kB
|
||||
Committed_AS: 80296 kB
|
||||
VmallocTotal: 263061440 kB
|
||||
VmallocUsed: 0 kB
|
||||
VmallocChunk: 0 kB
|
||||
AnonHugePages: 6144 kB
|
||||
ShmemHugePages: 0 kB
|
||||
ShmemPmdMapped: 0 kB
|
||||
CmaTotal: 131072 kB
|
||||
CmaFree: 130380 kB
|
||||
HugePages_Total: 0
|
||||
HugePages_Free: 0
|
||||
HugePages_Rsvd: 0
|
||||
HugePages_Surp: 0
|
||||
Hugepagesize: 2048 kB)meminfo";
|
||||
|
||||
TemporaryFile tf;
|
||||
::android::base::WriteStringToFd(meminfo, tf.fd);
|
||||
|
||||
uint64_t mem[MEMINFO_COUNT];
|
||||
for (auto _ : state) {
|
||||
get_mem_info(mem, tf.path);
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_ParseSysMemInfo);
|
||||
|
||||
static void BM_ReadMemInfo(benchmark::State& state) {
|
||||
std::string meminfo = R"meminfo(MemTotal: 3019740 kB
|
||||
MemFree: 1809728 kB
|
||||
MemAvailable: 2546560 kB
|
||||
Buffers: 54736 kB
|
||||
Cached: 776052 kB
|
||||
SwapCached: 0 kB
|
||||
Active: 445856 kB
|
||||
Inactive: 459092 kB
|
||||
Active(anon): 78492 kB
|
||||
Inactive(anon): 2240 kB
|
||||
Active(file): 367364 kB
|
||||
Inactive(file): 456852 kB
|
||||
Unevictable: 3096 kB
|
||||
Mlocked: 3096 kB
|
||||
SwapTotal: 0 kB
|
||||
SwapFree: 0 kB
|
||||
Dirty: 32 kB
|
||||
Writeback: 0 kB
|
||||
AnonPages: 74988 kB
|
||||
Mapped: 62624 kB
|
||||
Shmem: 4020 kB
|
||||
Slab: 86464 kB
|
||||
SReclaimable: 44432 kB
|
||||
SUnreclaim: 42032 kB
|
||||
KernelStack: 4880 kB
|
||||
PageTables: 2900 kB
|
||||
NFS_Unstable: 0 kB
|
||||
Bounce: 0 kB
|
||||
WritebackTmp: 0 kB
|
||||
CommitLimit: 1509868 kB
|
||||
Committed_AS: 80296 kB
|
||||
VmallocTotal: 263061440 kB
|
||||
VmallocUsed: 0 kB
|
||||
VmallocChunk: 0 kB
|
||||
AnonHugePages: 6144 kB
|
||||
ShmemHugePages: 0 kB
|
||||
ShmemPmdMapped: 0 kB
|
||||
CmaTotal: 131072 kB
|
||||
CmaFree: 130380 kB
|
||||
HugePages_Total: 0
|
||||
HugePages_Free: 0
|
||||
HugePages_Rsvd: 0
|
||||
HugePages_Surp: 0
|
||||
Hugepagesize: 2048 kB)meminfo";
|
||||
|
||||
TemporaryFile tf;
|
||||
android::base::WriteStringToFd(meminfo, tf.fd);
|
||||
|
||||
std::string file = std::string(tf.path);
|
||||
::android::meminfo::SysMemInfo mi;
|
||||
for (auto _ : state) {
|
||||
mi.ReadMemInfo(file);
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_ReadMemInfo);
|
||||
|
||||
BENCHMARK_MAIN();
|
301
libmeminfo/libmeminfo_test.cpp
Normal file
301
libmeminfo/libmeminfo_test.cpp
Normal file
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <meminfo/pageacct.h>
|
||||
#include <meminfo/procmeminfo.h>
|
||||
#include <meminfo/sysmeminfo.h>
|
||||
#include <pagemap/pagemap.h>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/test_utils.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace android::meminfo;
|
||||
|
||||
pid_t pid = -1;
|
||||
|
||||
class ValidateProcMemInfo : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ASSERT_EQ(0, pm_kernel_create(&ker));
|
||||
ASSERT_EQ(0, pm_process_create(ker, pid, &proc));
|
||||
proc_mem = new ProcMemInfo(pid);
|
||||
ASSERT_NE(proc_mem, nullptr);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
delete proc_mem;
|
||||
pm_process_destroy(proc);
|
||||
pm_kernel_destroy(ker);
|
||||
}
|
||||
|
||||
pm_kernel_t* ker;
|
||||
pm_process_t* proc;
|
||||
ProcMemInfo* proc_mem;
|
||||
};
|
||||
|
||||
TEST_F(ValidateProcMemInfo, TestMapsSize) {
|
||||
const std::vector<Vma>& maps = proc_mem->Maps();
|
||||
ASSERT_FALSE(maps.empty()) << "Process " << getpid() << " maps are empty";
|
||||
}
|
||||
|
||||
TEST_F(ValidateProcMemInfo, TestMapsEquality) {
|
||||
const std::vector<Vma>& maps = proc_mem->Maps();
|
||||
ASSERT_EQ(proc->num_maps, maps.size());
|
||||
|
||||
for (size_t i = 0; i < maps.size(); ++i) {
|
||||
EXPECT_EQ(proc->maps[i]->start, maps[i].start);
|
||||
EXPECT_EQ(proc->maps[i]->end, maps[i].end);
|
||||
EXPECT_EQ(proc->maps[i]->offset, maps[i].offset);
|
||||
EXPECT_EQ(std::string(proc->maps[i]->name), maps[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ValidateProcMemInfo, TestMapsUsage) {
|
||||
const std::vector<Vma>& maps = proc_mem->Maps();
|
||||
ASSERT_FALSE(maps.empty());
|
||||
ASSERT_EQ(proc->num_maps, maps.size());
|
||||
|
||||
pm_memusage_t map_usage, proc_usage;
|
||||
pm_memusage_zero(&map_usage);
|
||||
pm_memusage_zero(&proc_usage);
|
||||
for (size_t i = 0; i < maps.size(); i++) {
|
||||
ASSERT_EQ(0, pm_map_usage(proc->maps[i], &map_usage));
|
||||
EXPECT_EQ(map_usage.vss, maps[i].usage.vss) << "VSS mismatch for map: " << maps[i].name;
|
||||
EXPECT_EQ(map_usage.rss, maps[i].usage.rss) << "RSS mismatch for map: " << maps[i].name;
|
||||
EXPECT_EQ(map_usage.pss, maps[i].usage.pss) << "PSS mismatch for map: " << maps[i].name;
|
||||
EXPECT_EQ(map_usage.uss, maps[i].usage.uss) << "USS mismatch for map: " << maps[i].name;
|
||||
pm_memusage_add(&proc_usage, &map_usage);
|
||||
}
|
||||
|
||||
EXPECT_EQ(proc_usage.vss, proc_mem->Usage().vss);
|
||||
EXPECT_EQ(proc_usage.rss, proc_mem->Usage().rss);
|
||||
EXPECT_EQ(proc_usage.pss, proc_mem->Usage().pss);
|
||||
EXPECT_EQ(proc_usage.uss, proc_mem->Usage().uss);
|
||||
}
|
||||
|
||||
class ValidateProcMemInfoWss : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ASSERT_EQ(0, pm_kernel_create(&ker));
|
||||
ASSERT_EQ(0, pm_process_create(ker, pid, &proc));
|
||||
proc_mem = new ProcMemInfo(pid, true);
|
||||
ASSERT_NE(proc_mem, nullptr);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
delete proc_mem;
|
||||
pm_process_destroy(proc);
|
||||
pm_kernel_destroy(ker);
|
||||
}
|
||||
|
||||
pm_kernel_t* ker;
|
||||
pm_process_t* proc;
|
||||
ProcMemInfo* proc_mem;
|
||||
};
|
||||
|
||||
TEST_F(ValidateProcMemInfoWss, TestWorkingTestReset) {
|
||||
// Expect reset to succeed
|
||||
EXPECT_TRUE(proc_mem->WssReset());
|
||||
}
|
||||
|
||||
TEST_F(ValidateProcMemInfoWss, TestWssEquality) {
|
||||
// Read wss using libpagemap
|
||||
pm_memusage_t wss_pagemap;
|
||||
EXPECT_EQ(0, pm_process_workingset(proc, &wss_pagemap, 0));
|
||||
|
||||
// Read wss using libmeminfo
|
||||
MemUsage wss = proc_mem->Wss();
|
||||
|
||||
// compare
|
||||
EXPECT_EQ(wss_pagemap.rss, wss.rss);
|
||||
EXPECT_EQ(wss_pagemap.pss, wss.pss);
|
||||
EXPECT_EQ(wss_pagemap.uss, wss.uss);
|
||||
}
|
||||
|
||||
class ValidatePageAcct : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ASSERT_EQ(0, pm_kernel_create(&ker));
|
||||
ASSERT_EQ(0, pm_process_create(ker, pid, &proc));
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
pm_process_destroy(proc);
|
||||
pm_kernel_destroy(ker);
|
||||
}
|
||||
|
||||
pm_kernel_t* ker;
|
||||
pm_process_t* proc;
|
||||
};
|
||||
|
||||
TEST_F(ValidatePageAcct, TestPageFlags) {
|
||||
PageAcct& pi = PageAcct::Instance();
|
||||
pi.InitPageAcct(false);
|
||||
|
||||
uint64_t* pagemap;
|
||||
size_t num_pages;
|
||||
for (size_t i = 0; i < proc->num_maps; i++) {
|
||||
ASSERT_EQ(0, pm_map_pagemap(proc->maps[i], &pagemap, &num_pages));
|
||||
for (size_t j = 0; j < num_pages; j++) {
|
||||
if (!PM_PAGEMAP_PRESENT(pagemap[j])) continue;
|
||||
|
||||
uint64_t pfn = PM_PAGEMAP_PFN(pagemap[j]);
|
||||
uint64_t page_flags_pagemap, page_flags_meminfo;
|
||||
|
||||
ASSERT_EQ(0, pm_kernel_flags(ker, pfn, &page_flags_pagemap));
|
||||
ASSERT_TRUE(pi.PageFlags(pfn, &page_flags_meminfo));
|
||||
// check if page flags equal
|
||||
EXPECT_EQ(page_flags_pagemap, page_flags_meminfo);
|
||||
}
|
||||
free(pagemap);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ValidatePageAcct, TestPageCounts) {
|
||||
PageAcct& pi = PageAcct::Instance();
|
||||
pi.InitPageAcct(false);
|
||||
|
||||
uint64_t* pagemap;
|
||||
size_t num_pages;
|
||||
for (size_t i = 0; i < proc->num_maps; i++) {
|
||||
ASSERT_EQ(0, pm_map_pagemap(proc->maps[i], &pagemap, &num_pages));
|
||||
for (size_t j = 0; j < num_pages; j++) {
|
||||
uint64_t pfn = PM_PAGEMAP_PFN(pagemap[j]);
|
||||
uint64_t map_count_pagemap, map_count_meminfo;
|
||||
|
||||
ASSERT_EQ(0, pm_kernel_count(ker, pfn, &map_count_pagemap));
|
||||
ASSERT_TRUE(pi.PageMapCount(pfn, &map_count_meminfo));
|
||||
// check if map counts are equal
|
||||
EXPECT_EQ(map_count_pagemap, map_count_meminfo);
|
||||
}
|
||||
free(pagemap);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ValidatePageAcct, TestPageIdle) {
|
||||
// skip the test if idle page tracking isn't enabled
|
||||
if (pm_kernel_init_page_idle(ker) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
PageAcct& pi = PageAcct::Instance();
|
||||
ASSERT_TRUE(pi.InitPageAcct(true));
|
||||
|
||||
uint64_t* pagemap;
|
||||
size_t num_pages;
|
||||
for (size_t i = 0; i < proc->num_maps; i++) {
|
||||
ASSERT_EQ(0, pm_map_pagemap(proc->maps[i], &pagemap, &num_pages));
|
||||
for (size_t j = 0; j < num_pages; j++) {
|
||||
if (!PM_PAGEMAP_PRESENT(pagemap[j])) continue;
|
||||
uint64_t pfn = PM_PAGEMAP_PFN(pagemap[j]);
|
||||
|
||||
ASSERT_EQ(0, pm_kernel_mark_page_idle(ker, &pfn, 1));
|
||||
int idle_status_pagemap = pm_kernel_get_page_idle(ker, pfn);
|
||||
int idle_status_meminfo = pi.IsPageIdle(pfn);
|
||||
EXPECT_EQ(idle_status_pagemap, idle_status_meminfo);
|
||||
}
|
||||
free(pagemap);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SysMemInfoParser, TestSysMemInfoFile) {
|
||||
std::string meminfo = R"meminfo(MemTotal: 3019740 kB
|
||||
MemFree: 1809728 kB
|
||||
MemAvailable: 2546560 kB
|
||||
Buffers: 54736 kB
|
||||
Cached: 776052 kB
|
||||
SwapCached: 0 kB
|
||||
Active: 445856 kB
|
||||
Inactive: 459092 kB
|
||||
Active(anon): 78492 kB
|
||||
Inactive(anon): 2240 kB
|
||||
Active(file): 367364 kB
|
||||
Inactive(file): 456852 kB
|
||||
Unevictable: 3096 kB
|
||||
Mlocked: 3096 kB
|
||||
SwapTotal: 0 kB
|
||||
SwapFree: 0 kB
|
||||
Dirty: 32 kB
|
||||
Writeback: 0 kB
|
||||
AnonPages: 74988 kB
|
||||
Mapped: 62624 kB
|
||||
Shmem: 4020 kB
|
||||
Slab: 86464 kB
|
||||
SReclaimable: 44432 kB
|
||||
SUnreclaim: 42032 kB
|
||||
KernelStack: 4880 kB
|
||||
PageTables: 2900 kB
|
||||
NFS_Unstable: 0 kB
|
||||
Bounce: 0 kB
|
||||
WritebackTmp: 0 kB
|
||||
CommitLimit: 1509868 kB
|
||||
Committed_AS: 80296 kB
|
||||
VmallocTotal: 263061440 kB
|
||||
VmallocUsed: 0 kB
|
||||
VmallocChunk: 0 kB
|
||||
AnonHugePages: 6144 kB
|
||||
ShmemHugePages: 0 kB
|
||||
ShmemPmdMapped: 0 kB
|
||||
CmaTotal: 131072 kB
|
||||
CmaFree: 130380 kB
|
||||
HugePages_Total: 0
|
||||
HugePages_Free: 0
|
||||
HugePages_Rsvd: 0
|
||||
HugePages_Surp: 0
|
||||
Hugepagesize: 2048 kB)meminfo";
|
||||
|
||||
TemporaryFile tf;
|
||||
ASSERT_TRUE(tf.fd != -1);
|
||||
ASSERT_TRUE(::android::base::WriteStringToFd(meminfo, tf.fd));
|
||||
|
||||
SysMemInfo mi;
|
||||
ASSERT_TRUE(mi.ReadMemInfo(tf.path));
|
||||
EXPECT_EQ(mi.mem_total_kb(), 3019740);
|
||||
EXPECT_EQ(mi.mem_page_tables_kb(), 2900);
|
||||
}
|
||||
|
||||
TEST(SysMemInfoParser, TestEmptyFile) {
|
||||
TemporaryFile tf;
|
||||
std::string empty_string = "";
|
||||
ASSERT_TRUE(tf.fd != -1);
|
||||
ASSERT_TRUE(::android::base::WriteStringToFd(empty_string, tf.fd));
|
||||
|
||||
SysMemInfo mi;
|
||||
EXPECT_TRUE(mi.ReadMemInfo(tf.path));
|
||||
EXPECT_EQ(mi.mem_total_kb(), 0);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
if (argc <= 1) {
|
||||
cerr << "Pid of a permanently sleeping process must be provided." << endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
::android::base::InitLogging(argv, android::base::StderrLogger);
|
||||
pid = std::stoi(std::string(argv[1]));
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
34
libmeminfo/meminfo_private.h
Normal file
34
libmeminfo/meminfo_private.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <unistd.h>
|
||||
|
||||
#include <meminfo/meminfo.h>
|
||||
#include <meminfo/pageacct.h>
|
||||
#include <meminfo/procmeminfo.h>
|
||||
#include <meminfo/sysmeminfo.h>
|
||||
|
||||
// Macros to do per-page flag manipulation
|
||||
#define _BITS(x, offset, bits) (((x) >> (offset)) & ((1LL << (bits)) - 1))
|
||||
#define PAGE_PRESENT(x) (_BITS(x, 63, 1))
|
||||
#define PAGE_SWAPPED(x) (_BITS(x, 62, 1))
|
||||
#define PAGE_SHIFT(x) (_BITS(x, 55, 6))
|
||||
#define PAGE_PFN(x) (_BITS(x, 0, 55))
|
||||
#define PAGE_SWAP_OFFSET(x) (_BITS(x, 5, 50))
|
||||
#define PAGE_SWAP_TYPE(x) (_BITS(x, 0, 5))
|
142
libmeminfo/pageacct.cpp
Normal file
142
libmeminfo/pageacct.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
#include "meminfo_private.h"
|
||||
|
||||
using unique_fd = ::android::base::unique_fd;
|
||||
|
||||
namespace android {
|
||||
namespace meminfo {
|
||||
|
||||
static inline off64_t pfn_to_idle_bitmap_offset(uint64_t pfn) {
|
||||
return static_cast<off64_t>((pfn >> 6) << 3);
|
||||
}
|
||||
|
||||
uint64_t pagesize(void) {
|
||||
static uint64_t pagesize = sysconf(_SC_PAGE_SIZE);
|
||||
return pagesize;
|
||||
}
|
||||
|
||||
bool PageAcct::InitPageAcct(bool pageidle_enable) {
|
||||
if (pageidle_enable && !PageAcct::KernelHasPageIdle()) {
|
||||
LOG(ERROR) << "Idle page tracking is not supported by the kernel";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kpagecount_fd_ < 0) {
|
||||
unique_fd count_fd(TEMP_FAILURE_RETRY(open("/proc/kpagecount", O_RDONLY | O_CLOEXEC)));
|
||||
if (count_fd < 0) {
|
||||
PLOG(ERROR) << "Failed to open /proc/kpagecount";
|
||||
return false;
|
||||
}
|
||||
kpagecount_fd_ = std::move(count_fd);
|
||||
}
|
||||
|
||||
if (kpageflags_fd_ < 0) {
|
||||
unique_fd flags_fd(TEMP_FAILURE_RETRY(open("/proc/kpageflags", O_RDONLY | O_CLOEXEC)));
|
||||
if (flags_fd < 0) {
|
||||
PLOG(ERROR) << "Failed to open /proc/kpageflags";
|
||||
return false;
|
||||
}
|
||||
kpageflags_fd_ = std::move(flags_fd);
|
||||
}
|
||||
|
||||
if (pageidle_enable && pageidle_fd_ < 0) {
|
||||
unique_fd idle_fd(
|
||||
TEMP_FAILURE_RETRY(open("/sys/kernel/mm/page_idle/bitmap", O_RDWR | O_CLOEXEC)));
|
||||
if (idle_fd < 0) {
|
||||
PLOG(ERROR) << "Failed to open page idle bitmap";
|
||||
return false;
|
||||
}
|
||||
pageidle_fd_ = std::move(idle_fd);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PageAcct::PageFlags(uint64_t pfn, uint64_t* flags) {
|
||||
if (!flags) return false;
|
||||
|
||||
if (kpageflags_fd_ < 0) {
|
||||
if (!InitPageAcct()) return false;
|
||||
}
|
||||
|
||||
if (pread64(kpageflags_fd_, flags, sizeof(uint64_t), pfn * sizeof(uint64_t)) < 0) {
|
||||
PLOG(ERROR) << "Failed to read page flags for page " << pfn;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PageAcct::PageMapCount(uint64_t pfn, uint64_t* mapcount) {
|
||||
if (!mapcount) return false;
|
||||
|
||||
if (kpagecount_fd_ < 0) {
|
||||
if (!InitPageAcct()) return false;
|
||||
}
|
||||
|
||||
if (pread64(kpagecount_fd_, mapcount, sizeof(uint64_t), pfn * sizeof(uint64_t)) < 0) {
|
||||
PLOG(ERROR) << "Failed to read map count for page " << pfn;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int PageAcct::IsPageIdle(uint64_t pfn) {
|
||||
if (pageidle_fd_ < 0) {
|
||||
if (!InitPageAcct(true)) return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
int idle_status = MarkPageIdle(pfn);
|
||||
if (idle_status) return idle_status;
|
||||
|
||||
return GetPageIdle(pfn);
|
||||
}
|
||||
|
||||
int PageAcct::MarkPageIdle(uint64_t pfn) const {
|
||||
off64_t offset = pfn_to_idle_bitmap_offset(pfn);
|
||||
// set the bit corresponding to page frame
|
||||
uint64_t idle_bits = 1ULL << (pfn % 64);
|
||||
|
||||
if (pwrite64(pageidle_fd_, &idle_bits, sizeof(uint64_t), offset) < 0) {
|
||||
PLOG(ERROR) << "Failed to write page idle bitmap for page " << pfn;
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PageAcct::GetPageIdle(uint64_t pfn) const {
|
||||
off64_t offset = pfn_to_idle_bitmap_offset(pfn);
|
||||
uint64_t idle_bits;
|
||||
|
||||
if (pread64(pageidle_fd_, &idle_bits, sizeof(uint64_t), offset) < 0) {
|
||||
PLOG(ERROR) << "Failed to read page idle bitmap for page " << pfn;
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return !!(idle_bits & (1ULL << (pfn % 64)));
|
||||
}
|
||||
|
||||
} // namespace meminfo
|
||||
} // namespace android
|
218
libmeminfo/procmeminfo.cpp
Normal file
218
libmeminfo/procmeminfo.cpp
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <linux/kernel-page-flags.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <procinfo/process_map.h>
|
||||
|
||||
#include "meminfo_private.h"
|
||||
|
||||
namespace android {
|
||||
namespace meminfo {
|
||||
|
||||
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->private_clean += from.private_clean;
|
||||
to->private_dirty += from.private_dirty;
|
||||
|
||||
to->shared_clean += from.shared_clean;
|
||||
to->shared_dirty += from.shared_dirty;
|
||||
}
|
||||
|
||||
ProcMemInfo::ProcMemInfo(pid_t pid, bool get_wss) : pid_(pid), get_wss_(get_wss) {
|
||||
if (!ReadMaps(get_wss_)) {
|
||||
LOG(ERROR) << "Failed to read maps for Process " << pid_;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Vma>& ProcMemInfo::Maps() {
|
||||
return maps_;
|
||||
}
|
||||
|
||||
const MemUsage& ProcMemInfo::Usage() {
|
||||
if (get_wss_) {
|
||||
LOG(WARNING) << "Trying to read memory usage from working set object";
|
||||
}
|
||||
return usage_;
|
||||
}
|
||||
|
||||
const MemUsage& ProcMemInfo::Wss() {
|
||||
if (!get_wss_) {
|
||||
LOG(WARNING) << "Trying to read working set when there is none";
|
||||
}
|
||||
|
||||
return wss_;
|
||||
}
|
||||
|
||||
bool ProcMemInfo::WssReset() {
|
||||
if (!get_wss_) {
|
||||
LOG(ERROR) << "Trying to reset working set from a memory usage counting object";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string clear_refs_path = ::android::base::StringPrintf("/proc/%d/clear_refs", pid_);
|
||||
if (!::android::base::WriteStringToFile("1\n", clear_refs_path)) {
|
||||
PLOG(ERROR) << "Failed to write to " << clear_refs_path;
|
||||
return false;
|
||||
}
|
||||
|
||||
wss_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcMemInfo::ReadMaps(bool get_wss) {
|
||||
// parse and read /proc/<pid>/maps
|
||||
std::string maps_file = ::android::base::StringPrintf("/proc/%d/maps", pid_);
|
||||
if (!::android::procinfo::ReadMapFile(
|
||||
maps_file, [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff,
|
||||
const char* name) {
|
||||
maps_.emplace_back(Vma(start, end, pgoff, flags, name));
|
||||
})) {
|
||||
LOG(ERROR) << "Failed to parse " << maps_file;
|
||||
maps_.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string pagemap_file = ::android::base::StringPrintf("/proc/%d/pagemap", pid_);
|
||||
::android::base::unique_fd pagemap_fd(
|
||||
TEMP_FAILURE_RETRY(open(pagemap_file.c_str(), O_RDONLY | O_CLOEXEC)));
|
||||
if (pagemap_fd < 0) {
|
||||
PLOG(ERROR) << "Failed to open " << pagemap_file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& vma : maps_) {
|
||||
if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss)) {
|
||||
LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start
|
||||
<< "-" << vma.end << "]";
|
||||
maps_.clear();
|
||||
return false;
|
||||
}
|
||||
if (get_wss) {
|
||||
add_mem_usage(&wss_, vma.wss);
|
||||
} else {
|
||||
add_mem_usage(&usage_, vma.usage);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) {
|
||||
PageAcct& pinfo = PageAcct::Instance();
|
||||
uint64_t pagesz = getpagesize();
|
||||
uint64_t num_pages = (vma.end - vma.start) / pagesz;
|
||||
|
||||
std::unique_ptr<uint64_t[]> pg_frames(new uint64_t[num_pages]);
|
||||
uint64_t first = vma.start / pagesz;
|
||||
if (pread64(pagemap_fd, pg_frames.get(), num_pages * sizeof(uint64_t),
|
||||
first * sizeof(uint64_t)) < 0) {
|
||||
PLOG(ERROR) << "Failed to read page frames from page map for pid: " << pid_;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<uint64_t[]> pg_flags(new uint64_t[num_pages]);
|
||||
std::unique_ptr<uint64_t[]> pg_counts(new uint64_t[num_pages]);
|
||||
for (uint64_t i = 0; i < num_pages; ++i) {
|
||||
if (!get_wss) {
|
||||
vma.usage.vss += pagesz;
|
||||
}
|
||||
uint64_t p = pg_frames[i];
|
||||
if (!PAGE_PRESENT(p) && !PAGE_SWAPPED(p)) continue;
|
||||
|
||||
if (PAGE_SWAPPED(p)) {
|
||||
// TODO: do what's needed for swapped pages
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t page_frame = PAGE_PFN(p);
|
||||
if (!pinfo.PageFlags(page_frame, &pg_flags[i])) {
|
||||
LOG(ERROR) << "Failed to get page flags for " << page_frame << " in process " << pid_;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pinfo.PageMapCount(page_frame, &pg_counts[i])) {
|
||||
LOG(ERROR) << "Failed to get page count for " << page_frame << " in process " << pid_;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Page was unmapped between the presence check at the beginning of the loop and here.
|
||||
if (pg_counts[i] == 0) {
|
||||
pg_frames[i] = 0;
|
||||
pg_flags[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool is_dirty = !!(pg_flags[i] & (1 << KPF_DIRTY));
|
||||
bool is_private = (pg_counts[i] == 1);
|
||||
// Working set
|
||||
if (get_wss) {
|
||||
bool is_referenced = !!(pg_flags[i] & (1 << KPF_REFERENCED));
|
||||
if (!is_referenced) {
|
||||
continue;
|
||||
}
|
||||
// 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.wss.vss += pagesz;
|
||||
vma.wss.rss += pagesz;
|
||||
vma.wss.uss += is_private ? pagesz : 0;
|
||||
vma.wss.pss += pagesz / pg_counts[i];
|
||||
if (is_private) {
|
||||
vma.wss.private_dirty += is_dirty ? pagesz : 0;
|
||||
vma.wss.private_clean += is_dirty ? 0 : pagesz;
|
||||
} else {
|
||||
vma.wss.shared_dirty += is_dirty ? pagesz : 0;
|
||||
vma.wss.shared_clean += is_dirty ? 0 : pagesz;
|
||||
}
|
||||
} else {
|
||||
vma.usage.rss += pagesz;
|
||||
vma.usage.uss += is_private ? pagesz : 0;
|
||||
vma.usage.pss += pagesz / pg_counts[i];
|
||||
if (is_private) {
|
||||
vma.usage.private_dirty += is_dirty ? pagesz : 0;
|
||||
vma.usage.private_clean += is_dirty ? 0 : pagesz;
|
||||
} else {
|
||||
vma.usage.shared_dirty += is_dirty ? pagesz : 0;
|
||||
vma.usage.shared_clean += is_dirty ? 0 : pagesz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace meminfo
|
||||
} // namespace android
|
129
libmeminfo/sysmeminfo.cpp
Normal file
129
libmeminfo/sysmeminfo.cpp
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/parseint.h>
|
||||
#include <android-base/strings.h>
|
||||
|
||||
#include "meminfo_private.h"
|
||||
|
||||
namespace android {
|
||||
namespace meminfo {
|
||||
|
||||
const std::vector<std::string> SysMemInfo::kDefaultSysMemInfoTags = {
|
||||
"MemTotal:", "MemFree:", "Buffers:", "Cached:", "Shmem:",
|
||||
"Slab:", "SReclaimable:", "SUnreclaim:", "SwapTotal:", "SwapFree:",
|
||||
"ZRam:", "Mapped:", "VmallocUsed:", "PageTables:", "KernelStack:",
|
||||
};
|
||||
|
||||
bool SysMemInfo::ReadMemInfo(const std::string& path) {
|
||||
return ReadMemInfo(SysMemInfo::kDefaultSysMemInfoTags, path);
|
||||
}
|
||||
|
||||
// TODO: Delete this function if it can't match up with the c-like implementation below.
|
||||
// Currently, this added about 50 % extra overhead on hikey.
|
||||
#if 0
|
||||
bool SysMemInfo::ReadMemInfo(const std::vector<std::string>& tags, const std::string& path) {
|
||||
std::string buffer;
|
||||
if (!::android::base::ReadFileToString(path, &buffer)) {
|
||||
PLOG(ERROR) << "Failed to read : " << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t total_found = 0;
|
||||
for (auto s = buffer.begin(); s < buffer.end() && total_found < tags.size();) {
|
||||
for (auto& tag : tags) {
|
||||
if (tag == std::string(s, s + tag.size())) {
|
||||
s += tag.size();
|
||||
while (isspace(*s)) s++;
|
||||
auto num_start = s;
|
||||
while (std::isdigit(*s)) s++;
|
||||
|
||||
std::string number(num_start, num_start + (s - num_start));
|
||||
if (!::android::base::ParseUint(number, &mem_in_kb_[tag])) {
|
||||
LOG(ERROR) << "Failed to parse uint";
|
||||
return false;
|
||||
}
|
||||
total_found++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (s < buffer.end() && *s != '\n') s++;
|
||||
if (s < buffer.end()) s++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
bool SysMemInfo::ReadMemInfo(const std::vector<std::string>& tags, const std::string& path) {
|
||||
char buffer[4096];
|
||||
int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
PLOG(ERROR) << "Failed to open file :" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
const int len = read(fd, buffer, sizeof(buffer) - 1);
|
||||
close(fd);
|
||||
if (len < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer[len] = '\0';
|
||||
char* p = buffer;
|
||||
uint32_t found = 0;
|
||||
while (*p && found < tags.size()) {
|
||||
for (auto& tag : tags) {
|
||||
if (strncmp(p, tag.c_str(), tag.size()) == 0) {
|
||||
p += tag.size();
|
||||
while (*p == ' ') p++;
|
||||
char* endptr = nullptr;
|
||||
mem_in_kb_[tag] = strtoull(p, &endptr, 10);
|
||||
if (p == endptr) {
|
||||
PLOG(ERROR) << "Failed to parse line in file: " << path;
|
||||
return false;
|
||||
}
|
||||
p = endptr;
|
||||
found++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (*p && *p != '\n') {
|
||||
p++;
|
||||
}
|
||||
if (*p) p++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace meminfo
|
||||
} // namespace android
|
27
libmeminfo/tools/Android.bp
Normal file
27
libmeminfo/tools/Android.bp
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (C) 2018 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.
|
||||
|
||||
cc_binary {
|
||||
name: "procmem2",
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-Wno-unused-parameter",
|
||||
],
|
||||
|
||||
srcs: ["procmem.cpp"],
|
||||
shared_libs: [
|
||||
"libmeminfo",
|
||||
],
|
||||
}
|
171
libmeminfo/tools/procmem.cpp
Normal file
171
libmeminfo/tools/procmem.cpp
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <meminfo/procmeminfo.h>
|
||||
|
||||
using ProcMemInfo = ::android::meminfo::ProcMemInfo;
|
||||
using MemUsage = ::android::meminfo::MemUsage;
|
||||
|
||||
static void usage(const char* cmd) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s [-i] [ -w | -W ] [ -p | -m ] [ -h ] pid\n"
|
||||
" -i Uses idle page tracking for working set statistics.\n"
|
||||
" -w Displays statistics for the working set only.\n"
|
||||
" -W Resets the working set of the process.\n"
|
||||
" -p Sort by PSS.\n"
|
||||
" -u Sort by USS.\n"
|
||||
" -m Sort by mapping order (as read from /proc).\n"
|
||||
" -h Hide maps with no RSS.\n",
|
||||
cmd);
|
||||
}
|
||||
|
||||
static void show_footer(uint32_t nelem, const std::string& padding) {
|
||||
std::string elem(7, '-');
|
||||
|
||||
for (uint32_t i = 0; i < nelem; ++i) {
|
||||
std::cout << std::setw(7) << elem << padding;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
static void show_header(const std::vector<std::string>& header, const std::string& padding) {
|
||||
if (header.empty()) return;
|
||||
|
||||
for (size_t i = 0; i < header.size() - 1; ++i) {
|
||||
std::cout << std::setw(7) << header[i] << padding;
|
||||
}
|
||||
std::cout << header.back() << std::endl;
|
||||
show_footer(header.size() - 1, padding);
|
||||
}
|
||||
|
||||
static void scan_usage(std::stringstream& ss, const MemUsage& usage, const std::string& padding,
|
||||
bool show_wss) {
|
||||
// clear string stream first.
|
||||
ss.str("");
|
||||
// TODO: use ::android::base::StringPrintf instead of <iomanip> here.
|
||||
if (!show_wss)
|
||||
ss << std::setw(6) << usage.vss/1024 << padding;
|
||||
ss << std::setw(6) << usage.rss/1024 << padding << std::setw(6)
|
||||
<< usage.pss/1024 << padding << std::setw(6) << usage.uss/1024 << padding
|
||||
<< std::setw(6) << usage.shared_clean/1024 << padding << std::setw(6)
|
||||
<< usage.shared_dirty/1024 << padding << std::setw(6)
|
||||
<< usage.private_clean/1024 << padding << std::setw(6)
|
||||
<< usage.private_dirty/1024 << padding;
|
||||
}
|
||||
|
||||
static int show(ProcMemInfo& proc, bool hide_zeroes, bool show_wss) {
|
||||
const std::vector<std::string> main_header = {"Vss", "Rss", "Pss", "Uss", "ShCl",
|
||||
"ShDi", "PrCl", "PrDi", "Name"};
|
||||
const std::vector<std::string> wss_header = {"WRss", "WPss", "WUss", "WShCl",
|
||||
"WShDi", "WPrCl", "WPrDi", "Name"};
|
||||
const std::vector<std::string>& header = show_wss ? wss_header : main_header;
|
||||
|
||||
// Read process memory stats
|
||||
const MemUsage& stats = show_wss ? proc.Wss() : proc.Usage();
|
||||
const std::vector<::android::meminfo::Vma>& maps = proc.Maps();
|
||||
|
||||
// following retains 'procmem' output so as to not break any scripts
|
||||
// that rely on it.
|
||||
std::string spaces = " ";
|
||||
show_header(header, spaces);
|
||||
const std::string padding = "K ";
|
||||
std::stringstream ss;
|
||||
for (auto& vma : maps) {
|
||||
const MemUsage& vma_stats = show_wss ? vma.wss : vma.usage;
|
||||
if (hide_zeroes && vma_stats.rss == 0) {
|
||||
continue;
|
||||
}
|
||||
scan_usage(ss, vma_stats, padding, show_wss);
|
||||
ss << vma.name << std::endl;
|
||||
std::cout << ss.str();
|
||||
}
|
||||
show_footer(header.size() - 1, spaces);
|
||||
scan_usage(ss, stats, padding, show_wss);
|
||||
ss << "TOTAL" << std::endl;
|
||||
std::cout << ss.str();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
bool use_pageidle = false;
|
||||
bool hide_zeroes = false;
|
||||
bool wss_reset = false;
|
||||
bool show_wss = false;
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt(argc, argv, "himpuWw")) != -1) {
|
||||
switch (opt) {
|
||||
case 'h':
|
||||
hide_zeroes = true;
|
||||
break;
|
||||
case 'i':
|
||||
use_pageidle = true;
|
||||
break;
|
||||
case 'm':
|
||||
break;
|
||||
case 'p':
|
||||
break;
|
||||
case 'u':
|
||||
break;
|
||||
case 'W':
|
||||
wss_reset = true;
|
||||
break;
|
||||
case 'w':
|
||||
show_wss = true;
|
||||
break;
|
||||
case '?':
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != (argc - 1)) {
|
||||
fprintf(stderr, "Need exactly one pid at the end\n");
|
||||
usage(argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
pid_t pid = atoi(argv[optind]);
|
||||
if (pid == 0) {
|
||||
std::cerr << "Invalid process id" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
bool need_wss = wss_reset || show_wss;
|
||||
ProcMemInfo proc(pid, need_wss);
|
||||
if (wss_reset) {
|
||||
if (!proc.WssReset()) {
|
||||
std::cerr << "Failed to reset working set of pid : " << pid << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return show(proc, hide_zeroes, show_wss);
|
||||
}
|
Loading…
Reference in a new issue