Create a gist now

Instantly share code, notes, and snippets.

Compute the amount of memory saved through code sharing of SOs
// 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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment