meminfo: Add support to track working set with idle page tracking.
Also adds a tool to make use of this if it exists call 'wsstop' Bug: 111694435 Test: wsstop -d 3 -n 100 1 Change-Id: I50415f0bdc09c09b5b414cf0e4fff8f2907c5823 Signed-off-by: Sandeep Patil <sspatil@google.com>
This commit is contained in:
parent
a5e0d695fa
commit
061b71339c
4 changed files with 260 additions and 6 deletions
|
@ -40,6 +40,11 @@ class ProcMemInfo final {
|
|||
const MemUsage& Usage();
|
||||
const MemUsage& Wss();
|
||||
|
||||
// Same as Maps() except, only valid for reading working set using CONFIG_IDLE_PAGE_TRACKING
|
||||
// support in kernel. If the kernel support doesn't exist, the function will return an empty
|
||||
// vector.
|
||||
const std::vector<Vma>& MapsWithPageIdle();
|
||||
|
||||
// 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.
|
||||
//
|
||||
|
@ -83,8 +88,8 @@ class ProcMemInfo final {
|
|||
~ProcMemInfo() = default;
|
||||
|
||||
private:
|
||||
bool ReadMaps(bool get_wss);
|
||||
bool ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss);
|
||||
bool ReadMaps(bool get_wss, bool use_pageidle = false);
|
||||
bool ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss, bool use_pageidle);
|
||||
|
||||
pid_t pid_;
|
||||
bool get_wss_;
|
||||
|
|
|
@ -121,6 +121,14 @@ const std::vector<Vma>& ProcMemInfo::Maps() {
|
|||
return maps_;
|
||||
}
|
||||
|
||||
const std::vector<Vma>& ProcMemInfo::MapsWithPageIdle() {
|
||||
if (maps_.empty() && !ReadMaps(get_wss_, true)) {
|
||||
LOG(ERROR) << "Failed to read maps with page idle for Process " << pid_;
|
||||
}
|
||||
|
||||
return maps_;
|
||||
}
|
||||
|
||||
const std::vector<Vma>& ProcMemInfo::Smaps(const std::string& path) {
|
||||
if (!maps_.empty()) {
|
||||
return maps_;
|
||||
|
@ -226,7 +234,7 @@ bool ProcMemInfo::PageMap(const Vma& vma, std::vector<uint64_t>* pagemap) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ProcMemInfo::ReadMaps(bool get_wss) {
|
||||
bool ProcMemInfo::ReadMaps(bool get_wss, bool use_pageidle) {
|
||||
// Each object reads /proc/<pid>/maps only once. This is done to make sure programs that are
|
||||
// running for the lifetime of the system can recycle the objects and don't have to
|
||||
// unnecessarily retain and update this object in memory (which can get significantly large).
|
||||
|
@ -256,7 +264,7 @@ bool ProcMemInfo::ReadMaps(bool get_wss) {
|
|||
}
|
||||
|
||||
for (auto& vma : maps_) {
|
||||
if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss)) {
|
||||
if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss, use_pageidle)) {
|
||||
LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start << "-"
|
||||
<< vma.end << "]";
|
||||
maps_.clear();
|
||||
|
@ -268,7 +276,7 @@ bool ProcMemInfo::ReadMaps(bool get_wss) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) {
|
||||
bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss, bool use_pageidle) {
|
||||
PageAcct& pinfo = PageAcct::Instance();
|
||||
uint64_t pagesz = getpagesize();
|
||||
uint64_t num_pages = (vma.end - vma.start) / pagesz;
|
||||
|
@ -281,6 +289,13 @@ bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (get_wss && use_pageidle) {
|
||||
if (!pinfo.InitPageAcct(true)) {
|
||||
LOG(ERROR) << "Failed to init idle page accounting";
|
||||
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) {
|
||||
|
@ -323,7 +338,8 @@ bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) {
|
|||
bool is_private = (pg_counts[i] == 1);
|
||||
// Working set
|
||||
if (get_wss) {
|
||||
bool is_referenced = !!(pg_flags[i] & (1 << KPF_REFERENCED));
|
||||
bool is_referenced = use_pageidle ? (pinfo.IsPageIdle(page_frame) == 1)
|
||||
: !!(pg_flags[i] & (1 << KPF_REFERENCED));
|
||||
if (!is_referenced) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -67,3 +67,17 @@ cc_binary {
|
|||
"libmeminfo",
|
||||
],
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "wsstop",
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
],
|
||||
srcs: ["wsstop.cpp"],
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"liblog",
|
||||
"libmeminfo",
|
||||
],
|
||||
}
|
||||
|
|
219
libmeminfo/tools/wsstop.cpp
Normal file
219
libmeminfo/tools/wsstop.cpp
Normal file
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* 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 <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <meminfo/pageacct.h>
|
||||
#include <meminfo/procmeminfo.h>
|
||||
|
||||
using ::android::meminfo::ProcMemInfo;
|
||||
using ::android::meminfo::Vma;
|
||||
|
||||
// Global options
|
||||
static int32_t g_delay = 0;
|
||||
static int32_t g_total = 2;
|
||||
static pid_t g_pid = -1;
|
||||
|
||||
[[noreturn]] static void usage(int exit_status) {
|
||||
fprintf(stderr,
|
||||
"%s [-d DELAY_BETWEEN_EACH_SAMPLE] [-n REFRESH_TOTAL] PID\n"
|
||||
"-d\tdelay between each working set sample (default 0)\n"
|
||||
"-n\ttotal number of refreshes before we exit (default 2)\n",
|
||||
getprogname());
|
||||
|
||||
exit(exit_status);
|
||||
}
|
||||
|
||||
static void print_header() {
|
||||
const char* addr1 = " start end ";
|
||||
const char* addr2 = " addr addr ";
|
||||
|
||||
printf("%s virtual shared shared private private\n", addr1);
|
||||
printf("%s size RSS PSS clean dirty clean dirty swap "
|
||||
"swapPSS",
|
||||
addr2);
|
||||
printf(" object\n");
|
||||
}
|
||||
|
||||
static void print_divider() {
|
||||
printf("---------------- ---------------- ");
|
||||
printf("--------- --------- --------- --------- --------- --------- --------- --------- "
|
||||
"--------- ");
|
||||
printf("------------------------------\n");
|
||||
}
|
||||
|
||||
static void print_vma(const Vma& v) {
|
||||
printf("%16" PRIx64 " %16" PRIx64 " ", v.start, v.end);
|
||||
printf("%8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64
|
||||
"K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K ",
|
||||
v.usage.vss / 1024, v.usage.rss / 1024, v.usage.pss / 1024, v.usage.shared_clean / 1024,
|
||||
v.usage.shared_dirty / 1024, v.usage.private_clean / 1024, v.usage.private_dirty / 1024,
|
||||
v.usage.swap / 1024, v.usage.swap_pss / 1024);
|
||||
printf("%s\n", v.name.c_str());
|
||||
}
|
||||
|
||||
static bool same_vma(const Vma& cur, const Vma& last) {
|
||||
return (cur.start == last.start && cur.end == last.end && cur.name == last.name &&
|
||||
cur.flags == last.flags && cur.offset == last.offset);
|
||||
}
|
||||
|
||||
static Vma diff_vma_params(const Vma& cur, const Vma& last) {
|
||||
Vma res;
|
||||
res.usage.shared_clean = cur.usage.shared_clean > last.usage.shared_clean
|
||||
? cur.usage.shared_clean - last.usage.shared_clean
|
||||
: 0;
|
||||
res.usage.shared_dirty = cur.usage.shared_dirty > last.usage.shared_dirty
|
||||
? cur.usage.shared_dirty - last.usage.shared_dirty
|
||||
: 0;
|
||||
res.usage.private_clean = cur.usage.private_clean > last.usage.private_clean
|
||||
? cur.usage.private_clean - last.usage.private_clean
|
||||
: 0;
|
||||
res.usage.private_dirty = cur.usage.private_dirty > last.usage.private_dirty
|
||||
? cur.usage.private_dirty - last.usage.private_dirty
|
||||
: 0;
|
||||
|
||||
res.usage.rss = cur.usage.rss > last.usage.rss ? cur.usage.rss - last.usage.rss : 0;
|
||||
res.usage.pss = cur.usage.pss > last.usage.pss ? cur.usage.pss - last.usage.pss : 0;
|
||||
res.usage.uss = cur.usage.uss > last.usage.uss ? cur.usage.uss - last.usage.uss : 0;
|
||||
res.usage.swap = cur.usage.swap > last.usage.swap ? cur.usage.swap - last.usage.swap : 0;
|
||||
res.usage.swap_pss =
|
||||
cur.usage.swap_pss > last.usage.swap_pss ? cur.usage.swap_pss - last.usage.swap_pss : 0;
|
||||
|
||||
// set vma properties to the same as the current one.
|
||||
res.start = cur.start;
|
||||
res.end = cur.end;
|
||||
res.offset = cur.offset;
|
||||
res.flags = cur.flags;
|
||||
res.name = cur.name;
|
||||
return res;
|
||||
}
|
||||
|
||||
static void diff_workingset(std::vector<Vma>& wss, std::vector<Vma>& old, std::vector<Vma>* res) {
|
||||
res->clear();
|
||||
auto vma_sorter = [](const Vma& a, const Vma& b) { return a.start < b.start; };
|
||||
std::sort(wss.begin(), wss.end(), vma_sorter);
|
||||
std::sort(old.begin(), old.end(), vma_sorter);
|
||||
if (old.empty()) {
|
||||
*res = wss;
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& i : wss) {
|
||||
bool found_same_vma = false;
|
||||
// TODO: This is highly inefficient, fix it if it takes
|
||||
// too long. Worst case will be system_server
|
||||
for (auto& j : old) {
|
||||
if (same_vma(i, j)) {
|
||||
res->emplace_back(diff_vma_params(i, j));
|
||||
found_same_vma = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_same_vma) {
|
||||
res->emplace_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(res->begin(), res->end(), vma_sorter);
|
||||
return;
|
||||
}
|
||||
|
||||
static int workingset() {
|
||||
std::vector<Vma> last_wss = {};
|
||||
std::vector<Vma> diff_wss = {};
|
||||
uint32_t nr_refresh = 0;
|
||||
|
||||
while (true) {
|
||||
std::unique_ptr<ProcMemInfo> proc_mem = std::make_unique<ProcMemInfo>(g_pid, true);
|
||||
std::vector<Vma> wss = proc_mem->MapsWithPageIdle();
|
||||
|
||||
diff_workingset(wss, last_wss, &diff_wss);
|
||||
diff_wss.erase(std::remove_if(diff_wss.begin(), diff_wss.end(),
|
||||
[](const auto& v) { return v.usage.rss == 0; }),
|
||||
diff_wss.end());
|
||||
if ((nr_refresh % 5) == 0) {
|
||||
print_header();
|
||||
print_divider();
|
||||
}
|
||||
|
||||
for (const auto& v : diff_wss) {
|
||||
print_vma(v);
|
||||
}
|
||||
|
||||
nr_refresh++;
|
||||
if (nr_refresh == g_total) {
|
||||
break;
|
||||
}
|
||||
|
||||
last_wss = wss;
|
||||
sleep(g_delay);
|
||||
print_divider();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
struct option longopts[] = {
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{0, 0, nullptr, 0},
|
||||
};
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt_long(argc, argv, "d:n:h", longopts, nullptr)) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
g_delay = atoi(optarg);
|
||||
break;
|
||||
case 'n':
|
||||
g_total = atoi(optarg);
|
||||
break;
|
||||
case 'h':
|
||||
usage(EXIT_SUCCESS);
|
||||
default:
|
||||
usage(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (!::android::meminfo::PageAcct::KernelHasPageIdle()) {
|
||||
fprintf(stderr, "Missing support for Idle page tracking in the kernel\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return workingset();
|
||||
}
|
Loading…
Reference in a new issue