#!/usr/bin/python # # Compares virtual page difference between 2 running processes. # Helps with determining how much a forked process is really # taking in memory. Can also help with process that had their # pages merged by KSM. This doesn't support THP and other oddities. # Patches and compliments should be sent to smizrahi@redhat.com import sys import struct import ctypes libc = ctypes.CDLL("libc.so.6") PAGESIZE = libc.getpagesize() FrameInfo = struct.Struct("Q") PM_SWAPPED_BIT = 62 PM_PFN_LEN = 54 def getMemMaps(pid): with open("/proc/%d/maps" % pid, "r") as f: for line in f: line = line.split() address, perms, offset, dev, inode = line[:5] pathname = line[5] if len(line) == 6 else "" yield tuple(int(item, 16) for item in address.split("-")), perms, offset, dev, inode, pathname def getPages(pid, heapOnly=True, verbose=False): """ Gets the pfn or type+offset (if swapped) of a page. """ with open("/proc/%d/pagemap" % pid, "rb") as f: print "Scanning memory maps for pid %d" % pid for (mBegin, mEnd), _, _, _, inode, pathname in getMemMaps(pid): if heapOnly and (pathname != "[heap]" and inode != 0): if verbose: print "Skipping range %d - %d, not heap (%s)" % (mBegin, mEnd, pathname) continue if verbose: print "Checking range %d - %d" % (mBegin, mEnd) lpfn = 0 for frame in range(mBegin, mEnd): vpage = divmod(frame, PAGESIZE)[0] f.seek(vpage * 8) l = FrameInfo.unpack(f.read(8))[0] swapped = bool((l >> PM_SWAPPED_BIT) & 1) pfn = l & ((2 ** PM_PFN_LEN) - 1) if pfn == lpfn: continue lpfn = pfn yield swapped, pfn try: argi = 1 verbose = False if sys.argv[argi] == "-v": verbose = True argi += 1 pidA = int(sys.argv[argi]) argi += 1 pidB = int(sys.argv[argi]) except Exception: exit("Usage vmemcmp [-v] ") pages = [] swappedPages = [] for swapped, page in getPages(pidA, verbose=verbose): if swapped: swappedPages.append(page) else: pages.append(page) shared = 0 new = 0 for swapped, page in getPages(pidB, verbose=verbose): try: if swapped: swappedPages.remove(page) else: pages.remove(page) shared += 1 except ValueError: new += 1 pass page2kb = lambda pnum : ((pnum * PAGESIZE) / 1024) statString = lambda pageNum : "%d (%d KB)" % (pageNum, page2kb(pageNum)) print statString(len(pages) + len(swappedPages)), "only in A,", statString(shared), "shared,", statString(new), "only in B"