platform_system_core/libmeminfo/tools/procrank.cpp
Sandeep Patil 56c414e872 meminfo: Remove unnecessary working set stats
The Vma and ProcMemInfo objects do not need separate stats
objects for storing working set. The Vma either has working set
or memory usage information and never both.

Bug: 111694435
Test: libmeminfo_test 1

Change-Id: I2df05f7e750bbba4325474633e705d6d68dd2ccb
Merged-In: I2df05f7e750bbba4325474633e705d6d68dd2ccb
Signed-off-by: Sandeep Patil <sspatil@google.com>
2019-01-14 09:42:50 -08:00

530 lines
19 KiB
C++

/*
* 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 <dirent.h>
#include <errno.h>
#include <inttypes.h>
#include <linux/kernel-page-flags.h>
#include <linux/oom.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
#include <memory>
#include <sstream>
#include <vector>
#include <android-base/file.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <meminfo/procmeminfo.h>
#include <meminfo/sysmeminfo.h>
using ::android::meminfo::MemUsage;
using ::android::meminfo::ProcMemInfo;
struct ProcessRecord {
public:
ProcessRecord(pid_t pid, bool get_wss = false, uint64_t pgflags = 0, uint64_t pgflags_mask = 0)
: pid_(-1),
procmem_(nullptr),
oomadj_(OOM_SCORE_ADJ_MAX + 1),
cmdline_(""),
proportional_swap_(0),
unique_swap_(0),
zswap_(0) {
std::unique_ptr<ProcMemInfo> procmem =
std::make_unique<ProcMemInfo>(pid, get_wss, pgflags, pgflags_mask);
if (procmem == nullptr) {
std::cerr << "Failed to create ProcMemInfo for: " << pid << std::endl;
return;
}
std::string fname = ::android::base::StringPrintf("/proc/%d/oom_score_adj", pid);
auto oomscore_fp =
std::unique_ptr<FILE, decltype(&fclose)>{fopen(fname.c_str(), "re"), fclose};
if (oomscore_fp == nullptr) {
std::cerr << "Failed to open oom_score_adj file: " << fname << std::endl;
return;
}
if (fscanf(oomscore_fp.get(), "%d\n", &oomadj_) != 1) {
std::cerr << "Failed to read oomadj from: " << fname << std::endl;
return;
}
fname = ::android::base::StringPrintf("/proc/%d/cmdline", pid);
if (!::android::base::ReadFileToString(fname, &cmdline_)) {
std::cerr << "Failed to read cmdline from: " << fname << std::endl;
cmdline_ = "<unknown>";
}
// We deliberately don't read the proc/<pid>cmdline file directly into 'cmdline_'
// because of some processes showing up cmdlines that end with "0x00 0x0A 0x00"
// e.g. xtra-daemon, lowi-server
// The .c_str() assignment below then takes care of trimming the cmdline at the first
// 0x00. This is how original procrank worked (luckily)
cmdline_.resize(strlen(cmdline_.c_str()));
procmem_ = std::move(procmem);
pid_ = pid;
}
bool valid() const { return pid_ != -1; }
void CalculateSwap(const uint16_t* swap_offset_array, float zram_compression_ratio) {
const std::vector<uint16_t>& swp_offs = procmem_->SwapOffsets();
for (auto& off : swp_offs) {
proportional_swap_ += getpagesize() / swap_offset_array[off];
unique_swap_ += swap_offset_array[off] == 1 ? getpagesize() : 0;
zswap_ = proportional_swap_ * zram_compression_ratio;
}
}
// Getters
pid_t pid() const { return pid_; }
const std::string& cmdline() const { return cmdline_; }
int32_t oomadj() const { return oomadj_; }
uint64_t proportional_swap() const { return proportional_swap_; }
uint64_t unique_swap() const { return unique_swap_; }
uint64_t zswap() const { return zswap_; }
// Wrappers to ProcMemInfo
const std::vector<uint16_t>& SwapOffsets() const { return procmem_->SwapOffsets(); }
const MemUsage& Usage() const { return procmem_->Usage(); }
const MemUsage& Wss() const { return procmem_->Wss(); }
private:
pid_t pid_;
std::unique_ptr<ProcMemInfo> procmem_;
int32_t oomadj_;
std::string cmdline_;
uint64_t proportional_swap_;
uint64_t unique_swap_;
uint64_t zswap_;
};
// Show working set instead of memory consumption
bool show_wss = false;
// Reset working set of each process
bool reset_wss = false;
// Show per-process oom_score_adj column
bool show_oomadj = false;
// True if the device has swap enabled
bool has_swap = false;
// True, if device has zram enabled
bool has_zram = false;
// If zram is enabled, the compression ratio is zram used / swap used.
float zram_compression_ratio = 0.0;
// Sort process in reverse, default is descending
bool reverse_sort = false;
// Calculated total memory usage across all processes in the system
uint64_t total_pss = 0;
uint64_t total_uss = 0;
uint64_t total_swap = 0;
uint64_t total_pswap = 0;
uint64_t total_uswap = 0;
uint64_t total_zswap = 0;
[[noreturn]] static void usage(int exit_status) {
std::cerr << "Usage: " << getprogname() << " [ -W ] [ -v | -r | -p | -u | -s | -h ]"
<< std::endl
<< " -v Sort by VSS." << std::endl
<< " -r Sort by RSS." << std::endl
<< " -p Sort by PSS." << std::endl
<< " -u Sort by USS." << std::endl
<< " -s Sort by swap." << std::endl
<< " (Default sort order is PSS.)" << std::endl
<< " -R Reverse sort order (default is descending)." << std::endl
<< " -c Only show cached (storage backed) pages" << std::endl
<< " -C Only show non-cached (ram/swap backed) pages" << std::endl
<< " -k Only show pages collapsed by KSM" << std::endl
<< " -w Display statistics for working set only." << std::endl
<< " -W Reset working set of all processes." << std::endl
<< " -o Show and sort by oom score against lowmemorykiller thresholds."
<< std::endl
<< " -h Display this help screen." << std::endl;
exit(exit_status);
}
static bool read_all_pids(std::vector<pid_t>* pids, std::function<bool(pid_t pid)> for_each_pid) {
pids->clear();
std::unique_ptr<DIR, int (*)(DIR*)> procdir(opendir("/proc"), closedir);
if (!procdir) return false;
struct dirent* dir;
pid_t pid;
while ((dir = readdir(procdir.get()))) {
if (!::android::base::ParseInt(dir->d_name, &pid)) continue;
if (!for_each_pid(pid)) return false;
pids->push_back(pid);
}
return true;
}
static bool count_swap_offsets(const ProcessRecord& proc, uint16_t* swap_offset_array,
uint32_t size) {
const std::vector<uint16_t>& swp_offs = proc.SwapOffsets();
for (auto& off : swp_offs) {
if (off >= size) {
std::cerr << "swap offset " << off << " is out of bounds for process: " << proc.pid()
<< std::endl;
return false;
}
if (swap_offset_array[off] == USHRT_MAX) {
std::cerr << "swap offset " << off << " ref count overflow in process: " << proc.pid()
<< std::endl;
return false;
}
swap_offset_array[off]++;
}
return true;
}
static void print_header(std::stringstream& ss) {
ss.str("");
ss << ::android::base::StringPrintf("%5s ", "PID");
if (show_oomadj) {
ss << ::android::base::StringPrintf("%5s ", "oom");
}
if (show_wss) {
ss << ::android::base::StringPrintf("%7s %7s %7s ", "WRss", "WPss", "WUss");
// now swap statistics here, working set pages by definition shouldn't end up in swap.
} else {
ss << ::android::base::StringPrintf("%8s %7s %7s %7s ", "Vss", "Rss", "Pss", "Uss");
if (has_swap) {
ss << ::android::base::StringPrintf("%7s %7s %7s ", "Swap", "PSwap", "USwap");
if (has_zram) {
ss << ::android::base::StringPrintf("%7s ", "ZSwap");
}
}
}
ss << "cmdline";
}
static void print_process_record(std::stringstream& ss, ProcessRecord& proc) {
ss << ::android::base::StringPrintf("%5d ", proc.pid());
if (show_oomadj) {
ss << ::android::base::StringPrintf("%5d ", proc.oomadj());
}
if (show_wss) {
ss << ::android::base::StringPrintf("%6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ",
proc.Wss().rss / 1024, proc.Wss().pss / 1024,
proc.Wss().uss / 1024);
} else {
ss << ::android::base::StringPrintf("%7" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64
"K ",
proc.Usage().vss / 1024, proc.Usage().rss / 1024,
proc.Usage().pss / 1024, proc.Usage().uss / 1024);
if (has_swap) {
ss << ::android::base::StringPrintf("%6" PRIu64 "K ", proc.Usage().swap / 1024);
ss << ::android::base::StringPrintf("%6" PRIu64 "K ", proc.proportional_swap() / 1024);
ss << ::android::base::StringPrintf("%6" PRIu64 "K ", proc.unique_swap() / 1024);
if (has_zram) {
ss << ::android::base::StringPrintf("%6" PRIu64 "K ", (proc.zswap() / 1024));
}
}
}
}
static void print_processes(std::stringstream& ss, std::vector<ProcessRecord>& procs,
uint16_t* swap_offset_array) {
for (auto& proc : procs) {
total_pss += show_wss ? proc.Wss().pss : proc.Usage().pss;
total_uss += show_wss ? proc.Wss().uss : proc.Usage().uss;
if (!show_wss && has_swap) {
proc.CalculateSwap(swap_offset_array, zram_compression_ratio);
total_swap += proc.Usage().swap;
total_pswap += proc.proportional_swap();
total_uswap += proc.unique_swap();
if (has_zram) {
total_zswap += proc.zswap();
}
}
print_process_record(ss, proc);
ss << proc.cmdline() << std::endl;
}
}
static void print_separator(std::stringstream& ss) {
ss << ::android::base::StringPrintf("%5s ", "");
if (show_oomadj) {
ss << ::android::base::StringPrintf("%5s ", "");
}
if (show_wss) {
ss << ::android::base::StringPrintf("%7s %7s %7s ", "", "------", "------");
} else {
ss << ::android::base::StringPrintf("%8s %7s %7s %7s ", "", "", "------", "------");
if (has_swap) {
ss << ::android::base::StringPrintf("%7s %7s %7s ", "------", "------", "------");
if (has_zram) {
ss << ::android::base::StringPrintf("%7s ", "------");
}
}
}
ss << ::android::base::StringPrintf("%s", "------");
}
static void print_totals(std::stringstream& ss) {
ss << ::android::base::StringPrintf("%5s ", "");
if (show_oomadj) {
ss << ::android::base::StringPrintf("%5s ", "");
}
if (show_wss) {
ss << ::android::base::StringPrintf("%7s %6" PRIu64 "K %6" PRIu64 "K ", "",
total_pss / 1024, total_uss / 1024);
} else {
ss << ::android::base::StringPrintf("%8s %7s %6" PRIu64 "K %6" PRIu64 "K ", "", "",
total_pss / 1024, total_uss / 1024);
if (has_swap) {
ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_swap / 1024);
ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_pswap / 1024);
ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_uswap / 1024);
if (has_zram) {
ss << ::android::base::StringPrintf("%6" PRIu64 "K ", total_zswap / 1024);
}
}
}
ss << "TOTAL";
}
static void print_sysmeminfo(std::stringstream& ss, ::android::meminfo::SysMemInfo& smi) {
if (has_swap) {
ss << ::android::base::StringPrintf("ZRAM: %" PRIu64 "K physical used for %" PRIu64
"K in swap "
"(%" PRIu64 "K total swap)",
smi.mem_zram_kb(),
(smi.mem_swap_kb() - smi.mem_swap_free_kb()),
smi.mem_swap_kb())
<< std::endl;
}
ss << ::android::base::StringPrintf(" RAM: %" PRIu64 "K total, %" PRIu64 "K free, %" PRIu64
"K buffers, "
"%" PRIu64 "K cached, %" PRIu64 "K shmem, %" PRIu64
"K slab",
smi.mem_total_kb(), smi.mem_free_kb(), smi.mem_buffers_kb(),
smi.mem_cached_kb(), smi.mem_shmem_kb(), smi.mem_slab_kb());
}
int main(int argc, char* argv[]) {
auto pss_sort = [](ProcessRecord& a, ProcessRecord& b) {
MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
return reverse_sort ? stats_a.pss < stats_b.pss : stats_a.pss > stats_b.pss;
};
auto uss_sort = [](ProcessRecord& a, ProcessRecord& b) {
MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
return reverse_sort ? stats_a.uss < stats_b.uss : stats_a.uss > stats_b.uss;
};
auto rss_sort = [](ProcessRecord& a, ProcessRecord& b) {
MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
return reverse_sort ? stats_a.rss < stats_b.pss : stats_a.pss > stats_b.pss;
};
auto vss_sort = [](ProcessRecord& a, ProcessRecord& b) {
MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
return reverse_sort ? stats_a.vss < stats_b.vss : stats_a.vss > stats_b.vss;
};
auto swap_sort = [](ProcessRecord& a, ProcessRecord& b) {
MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
return reverse_sort ? stats_a.swap < stats_b.swap : stats_a.swap > stats_b.swap;
};
auto oomadj_sort = [](ProcessRecord& a, ProcessRecord& b) {
return reverse_sort ? a.oomadj() < b.oomadj() : a.oomadj() > b.oomadj();
};
// default PSS sort
std::function<bool(ProcessRecord & a, ProcessRecord & b)> proc_sort = pss_sort;
// count all pages by default
uint64_t pgflags = 0;
uint64_t pgflags_mask = 0;
int opt;
while ((opt = getopt(argc, argv, "cChkoprRsuvwW")) != -1) {
switch (opt) {
case 'c':
pgflags = 0;
pgflags_mask = (1 << KPF_SWAPBACKED);
break;
case 'C':
pgflags = (1 << KPF_SWAPBACKED);
pgflags_mask = (1 << KPF_SWAPBACKED);
break;
case 'h':
usage(EXIT_SUCCESS);
case 'k':
pgflags = (1 << KPF_KSM);
pgflags_mask = (1 << KPF_KSM);
break;
case 'o':
proc_sort = oomadj_sort;
show_oomadj = true;
break;
case 'p':
proc_sort = pss_sort;
break;
case 'r':
proc_sort = rss_sort;
break;
case 'R':
reverse_sort = true;
break;
case 's':
proc_sort = swap_sort;
break;
case 'u':
proc_sort = uss_sort;
break;
case 'v':
proc_sort = vss_sort;
break;
case 'w':
show_wss = true;
break;
case 'W':
reset_wss = true;
break;
default:
usage(EXIT_FAILURE);
}
}
std::vector<pid_t> pids;
std::vector<ProcessRecord> procs;
if (reset_wss) {
if (!read_all_pids(&pids,
[&](pid_t pid) -> bool { return ProcMemInfo::ResetWorkingSet(pid); })) {
std::cerr << "Failed to reset working set of all processes" << std::endl;
exit(EXIT_FAILURE);
}
// we are done, all other options passed to procrank are ignored in the presence of '-W'
return 0;
}
::android::meminfo::SysMemInfo smi;
if (!smi.ReadMemInfo()) {
std::cerr << "Failed to get system memory info" << std::endl;
exit(EXIT_FAILURE);
}
// Figure out swap and zram
uint64_t swap_total = smi.mem_swap_kb() * 1024;
has_swap = swap_total > 0;
// Allocate the swap array
auto swap_offset_array = std::make_unique<uint16_t[]>(swap_total / getpagesize());
if (has_swap) {
has_zram = smi.mem_zram_kb() > 0;
if (has_zram) {
zram_compression_ratio = static_cast<float>(smi.mem_zram_kb()) /
(smi.mem_swap_kb() - smi.mem_swap_free_kb());
}
}
auto mark_swap_usage = [&](pid_t pid) -> bool {
ProcessRecord proc(pid, show_wss, pgflags, pgflags_mask);
if (!proc.valid()) {
std::cerr << "Failed to create process record for: " << pid << std::endl;
return false;
}
// Skip processes with no memory mappings
uint64_t vss = proc.Usage().vss;
if (vss == 0) return true;
// collect swap_offset counts from all processes in 1st pass
if (!show_wss && has_swap &&
!count_swap_offsets(proc, swap_offset_array.get(), swap_total / getpagesize())) {
std::cerr << "Failed to count swap offsets for process: " << pid << std::endl;
return false;
}
procs.push_back(std::move(proc));
return true;
};
// Get a list of all pids currently running in the system in
// 1st pass through all processes. Mark each swap offset used by the process as we find them
// for calculating proportional swap usage later.
if (!read_all_pids(&pids, mark_swap_usage)) {
std::cerr << "Failed to read all pids from the system" << std::endl;
exit(EXIT_FAILURE);
}
std::stringstream ss;
if (procs.empty()) {
// This would happen in corner cases where procrank is being run to find KSM usage on a
// system with no KSM and combined with working set determination as follows
// procrank -w -u -k
// procrank -w -s -k
// procrank -w -o -k
ss << "<empty>" << std::endl << std::endl;
print_sysmeminfo(ss, smi);
ss << std::endl;
std::cout << ss.str();
return 0;
}
// Sort all process records, default is PSS descending
std::sort(procs.begin(), procs.end(), proc_sort);
// start dumping output in string stream
print_header(ss);
ss << std::endl;
// 2nd pass to calculate and get per process stats to add them up
print_processes(ss, procs, swap_offset_array.get());
// Add separator to output
print_separator(ss);
ss << std::endl;
// Add totals to output
print_totals(ss);
ss << std::endl << std::endl;
// Add system information at the end
print_sysmeminfo(ss, smi);
ss << std::endl;
// dump on the screen
std::cout << ss.str();
return 0;
}