# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import re import subprocess from cmanager_base import CounterManager def xrestop(binary="xrestop"): """ python front-end to running xrestop: http://www.freedesktop.org/wiki/Software/xrestop For each monitored process, `xrestop -m 1 -b` produces output like: 0 - Thunderbird ( PID: 2035 ): res_base : 0x1600000 res_mask : 0x1fffff windows : 69 GCs : 35 fonts : 1 pixmaps : 175 pictures : 272 glyphsets : 73 colormaps : 0 passive grabs : 0 cursors : 9 unknowns : 42 pixmap bytes : 4715737 other bytes : ~13024 total bytes : ~4728761 """ process_regex = re.compile(r"([0-9]+) - (.*) \( PID: *(.*) *\):") args = ["-m", "1", "-b"] command = [binary] + args process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) stdout, stderr = process.communicate() if process.returncode: raise Exception( "Unexpected error executing '%s':\n%s" % (subprocess.list2cmdline(command), stdout) ) # process output retval = {} index = name = pid = None for line in stdout.strip().splitlines(): line = line.rstrip() match = process_regex.match(line) if match: index, name, pid = match.groups() try: pid = int(pid) except ValueError: # ignore processes without PIDs index = name = pid = None continue index = int(index) retval[pid] = dict(index=index, name=name) else: if not pid: continue counter, value = line.split(":", 1) counter = counter.strip() value = value.strip() retval[pid][counter] = value return retval def GetPrivateBytes(pids): """Calculate the amount of private, writeable memory allocated to a process. This code was adapted from 'pmap.c', part of the procps project. """ privateBytes = 0 for pid in pids: mapfile = "/proc/%s/maps" % pid with open(mapfile) as maps: private = 0 for line in maps: # split up (range, line) = line.split(" ", 1) (start, end) = range.split("-") flags = line.split(" ", 1)[0] size = int(end, 16) - int(start, 16) if flags.find("p") >= 0: if flags.find("w") >= 0: private += size privateBytes += private return privateBytes def GetResidentSize(pids): """Retrieve the current resident memory for a given process""" # for some reason /proc/PID/stat doesn't give accurate information # so we use status instead RSS = 0 for pid in pids: file = "/proc/%s/status" % pid with open(file) as status: for line in status: if line.find("VmRSS") >= 0: RSS += int(line.split()[1]) * 1024 return RSS def GetXRes(pids): """Returns the total bytes used by X or raises an error if total bytes is not available""" XRes = 0 xres_output = xrestop() for pid in pids: if pid in xres_output: data = xres_output[pid]["total bytes"] data = data.lstrip("~") # total bytes is like '~4728761' try: data = float(data) XRes += data except ValueError: print("Invalid data, not a float") raise # Content processes generally won't use X11 after bug 1635451, # so failing to find the process isn't an error. return XRes class LinuxCounterManager(CounterManager): """This class manages the monitoring of a process with any number of counters. A counter can be any function that takes an argument of one pid and returns a piece of data about that process. Some examples are: CalcCPUTime, GetResidentSize, and GetPrivateBytes """ counterDict = { "Private Bytes": GetPrivateBytes, "RSS": GetResidentSize, "XRes": GetXRes, } def __init__(self, process_name, process, counters): """Args: counters: A list of counters to monitor. Any counters whose name does not match a key in 'counterDict' will be ignored. """ CounterManager.__init__(self) self.process = process self._loadCounters() self.registerCounters(counters) def getCounterValue(self, counterName): """Returns the last value of the counter 'counterName'""" try: return self.registeredCounters[counterName][0](self.pidList()) except Exception: return None def pidList(self): """Updates the list of PIDs we're interested in""" try: return [self.process.pid] + [child.pid for child in self.process.children()] except Exception: print("WARNING: problem updating child PID's")