182 lines
5.3 KiB
Python
182 lines
5.3 KiB
Python
# 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 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)
|
|
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
|
|
else:
|
|
raise Exception("Could not find PID=%s in xrestop output" % pid)
|
|
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:
|
|
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:
|
|
print("WARNING: problem updating child PID's")
|