Source code

Revision control

Copy as Markdown

Other Tools

# 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:
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")