Skip to content

Instantly share code, notes, and snippets.

@zvrba
Created April 18, 2015 07:31
Show Gist options
  • Save zvrba/33893e14b4536a8d55d1 to your computer and use it in GitHub Desktop.
Save zvrba/33893e14b4536a8d55d1 to your computer and use it in GitHub Desktop.
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;
}
@yugr
Copy link

yugr commented Feb 11, 2024

Have you considered making this a proper repo?

@zvrba
Copy link
Author

zvrba commented Feb 11, 2024

No, it was a one-off utility and I even don't remember anymore why I wrote it.

@yugr
Copy link

yugr commented Feb 11, 2024

It's a helper for https://zvrba.net/articles/solib-memory-savings.html article and seems to be the only source code on Internet capable of precisely estimating shared libraries RAM savings. Thanks for the useful tool!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment