|
// See https://www.kernel.org/doc/Documentation/vm/pagemap.txt |
|
|
|
#include <unistd.h> |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
#include <fcntl.h> |
|
#include <dirent.h> |
|
#include <stdint.h> |
|
#include <errno.h> |
|
#include <string.h> |
|
#include <iostream> |
|
#include <iomanip> |
|
#include <fstream> |
|
#include <sstream> |
|
#include <vector> |
|
#include <string> |
|
#include <stdexcept> |
|
#include <utility> |
|
|
|
using std::vector; |
|
using std::string; |
|
using std::cout; |
|
using std::cerr; |
|
using std::endl; |
|
|
|
static uint64_t G_pagesize; |
|
static vector<uint16_t> G_refcounts; |
|
static int G_kpagecountfd; |
|
|
|
template<typename F, typename... Args> |
|
static long syscall(const char *what, F f, Args... args) |
|
{ |
|
long result = f(args...); |
|
|
|
if (result < 0) { |
|
std::ostringstream err; |
|
err << what << ": " << strerror(errno); |
|
throw std::runtime_error(err.str()); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
class PageMap |
|
{ |
|
std::ifstream _ifs; |
|
int _pmapfd; |
|
bool _verbose; |
|
|
|
void ProcessMaps(); |
|
void ProcessRange(const string &soname, uint64_t loaddr, uint64_t hiaddr); |
|
uint16_t ProcessPage(uint64_t pfn); |
|
|
|
public: |
|
PageMap(int pid); |
|
~PageMap() |
|
{ |
|
if (_pmapfd >= 0) close(_pmapfd); |
|
} |
|
void Process(bool verbose); |
|
}; |
|
|
|
PageMap::PageMap(int pid) : _pmapfd(-1) |
|
{ |
|
{ |
|
std::ostringstream oss; |
|
oss << "/proc/" << pid << "/maps"; |
|
_ifs.open(oss.str().c_str()); |
|
if (!_ifs.is_open() && errno != ENOENT) |
|
throw std::runtime_error("can't open maps file"); |
|
} |
|
|
|
{ |
|
std::ostringstream oss; |
|
oss << "/proc/" << pid << "/pagemap"; |
|
_pmapfd = syscall("open(pmapfd)", open, oss.str().c_str(), O_RDONLY); |
|
} |
|
} |
|
|
|
void PageMap::Process(bool verbose) |
|
{ |
|
string line, flags, dev, soname; |
|
uint64_t loaddr, hiaddr, offset, ino; |
|
char delim; |
|
|
|
_verbose = verbose; |
|
|
|
while (getline(_ifs, line)) { |
|
std::istringstream iss(line); |
|
iss >> std::hex >> loaddr >> delim >> hiaddr >> flags >> offset >> dev >> ino >> soname; |
|
if (!iss) |
|
continue; // Happens when too few fields (soname missing) |
|
if (iss && flags == "r-xp" && ino) |
|
ProcessRange(soname, loaddr, hiaddr); |
|
} |
|
} |
|
|
|
void PageMap::ProcessRange(const string &soname, uint64_t loaddr, uint64_t hiaddr) |
|
{ |
|
uint64_t pfn_flags; |
|
|
|
loaddr /= G_pagesize; |
|
hiaddr /= G_pagesize; |
|
|
|
if (_verbose) |
|
cout << soname << endl; |
|
|
|
for (uint64_t page = loaddr; page < hiaddr; ++page) { |
|
syscall("lseek(pmapfd)", lseek, _pmapfd, 8*page, SEEK_SET); |
|
syscall("read(pmapfd)", read, _pmapfd, &pfn_flags, 8); |
|
if ((pfn_flags >> 63) == 1) { // present? |
|
uint64_t pfn = pfn_flags & ((1UL << 55)-1); |
|
auto count = ProcessPage(pfn); |
|
|
|
if (_verbose) |
|
cout << std::hex << std::setw(12) << pfn << " " << count << endl; |
|
} |
|
|
|
} |
|
} |
|
|
|
uint16_t PageMap::ProcessPage(uint64_t pfn) |
|
{ |
|
if (!G_refcounts[pfn]) |
|
{ |
|
uint64_t pagecount; |
|
syscall("lseek(kpagecountfd)", lseek, G_kpagecountfd, 8*pfn, SEEK_SET); |
|
syscall("read(kpagecountfd)", read, G_kpagecountfd, &pagecount, 8); |
|
if (pagecount > 65535) |
|
throw std::runtime_error("pagecount too big"); |
|
G_refcounts[pfn] = pagecount; |
|
} |
|
return G_refcounts[pfn]; |
|
} |
|
|
|
static vector<int> CollectPids() |
|
{ |
|
vector<int> pids; |
|
DIR *proc = opendir("/proc"); |
|
struct dirent *de; |
|
int pid; |
|
|
|
while ((de = readdir(proc)) != 0) { |
|
std::istringstream iss(de->d_name); |
|
if (iss >> pid) |
|
pids.push_back(pid); |
|
} |
|
closedir(proc); |
|
return pids; |
|
} |
|
|
|
static void Report() |
|
{ |
|
uint64_t shared = 0; |
|
for (auto x : G_refcounts) { |
|
if (x > 0) |
|
shared += x-1; |
|
} |
|
cout << "Total SAVED by sharing: " << shared << endl; |
|
} |
|
|
|
int main(int argc, char **argv) try |
|
{ |
|
if ((G_pagesize = sysconf(_SC_PAGESIZE)) < 0) |
|
throw std::runtime_error(strerror(errno)); |
|
if ((G_kpagecountfd = open("/proc/kpagecount", O_RDONLY)) < 0) |
|
throw std::runtime_error(strerror(errno)); |
|
|
|
G_refcounts.resize(1 << 21, 0); // covers up to 8G physical pages |
|
|
|
if (argc == 2) { |
|
PageMap(atoi(argv[1])).Process(true); |
|
} else { |
|
auto pids = CollectPids(); |
|
for (int pid : pids) |
|
PageMap(pid).Process(false); |
|
} |
|
|
|
Report(); |
|
return 0; |
|
} |
|
catch (std::runtime_error &err) |
|
{ |
|
cerr << "ERROR: " << err.what() << endl; |
|
return 1; |
|
} |
Have you considered making this a proper repo?