218 lines
8.6 KiB
Python
218 lines
8.6 KiB
Python
"""Gather metrics on specific processes.
|
|
|
|
"""
|
|
import monasca_agent.collector.checks as checks
|
|
import monasca_agent.common.util as util
|
|
|
|
|
|
class ProcessCheck(checks.AgentCheck):
|
|
|
|
PROCESS_GAUGE = ('process.thread_count',
|
|
'process.cpu_perc',
|
|
'process.mem.rss_mbytes',
|
|
'process.mem.vsz_mbytes',
|
|
'process.mem.real_mbytes',
|
|
'process.open_file_descriptors',
|
|
'process.open_file_descriptors_perc',
|
|
'process.io.read_count',
|
|
'process.io.write_count',
|
|
'process.io.read_kbytes',
|
|
'process.io.write_kbytes',
|
|
'process.voluntary_ctx_switches',
|
|
'process.involuntary_ctx_switches')
|
|
|
|
@staticmethod
|
|
def is_psutil_version_later_than(v):
|
|
try:
|
|
import psutil
|
|
vers = psutil.version_info
|
|
return vers >= v
|
|
except Exception:
|
|
return False
|
|
|
|
def find_pids(self, search_string, psutil, exact_match=True):
|
|
"""Create a set of pids of selected processes.
|
|
|
|
Search for search_string
|
|
"""
|
|
found_process_list = []
|
|
for proc in psutil.process_iter():
|
|
found = False
|
|
for string in search_string:
|
|
if exact_match:
|
|
try:
|
|
if proc.name() == string:
|
|
found = True
|
|
except psutil.NoSuchProcess:
|
|
self.log.warning('Process %s disappeared while scanning'
|
|
% string)
|
|
pass
|
|
except psutil.AccessDenied as e:
|
|
self.log.error('Access denied to %s process' % string)
|
|
self.log.error('Error: %s' % e)
|
|
raise
|
|
else:
|
|
try:
|
|
cmdline = proc.cmdline()
|
|
|
|
if string in ' '.join(cmdline):
|
|
found = True
|
|
except psutil.NoSuchProcess:
|
|
self.warning('Process %s disappeared while scanning'
|
|
% string)
|
|
pass
|
|
except psutil.AccessDenied as e:
|
|
self.log.error('Access denied to %s process'
|
|
% string)
|
|
self.log.error('Error: %s' % e)
|
|
raise
|
|
|
|
if found or string == 'All':
|
|
found_process_list.append(proc.pid)
|
|
|
|
return set(found_process_list)
|
|
|
|
def get_process_metrics(self, pids, psutil, cpu_check_interval):
|
|
|
|
# initialize process metrics
|
|
# process metrics available for all versions of psutil
|
|
rss = 0
|
|
vms = 0
|
|
cpu = 0
|
|
thr = 0
|
|
|
|
# process metrics available for psutil versions 0.6.0 and later
|
|
extended_metrics_0_6_0 = (self.is_psutil_version_later_than((0, 6, 0))
|
|
and not util.Platform.is_win32())
|
|
# On Windows ext_memory_info returns different metrics
|
|
if extended_metrics_0_6_0:
|
|
real = 0
|
|
voluntary_ctx_switches = 0
|
|
involuntary_ctx_switches = 0
|
|
else:
|
|
real = None
|
|
voluntary_ctx_switches = None
|
|
involuntary_ctx_switches = None
|
|
|
|
# process metrics available for psutil versions 0.5.0 and later on UNIX
|
|
extended_metrics_0_5_0_unix = (self.is_psutil_version_later_than((0, 5, 0))
|
|
and util.Platform.is_unix())
|
|
if extended_metrics_0_5_0_unix:
|
|
open_file_descriptors = 0
|
|
open_file_descriptors_perc = 0
|
|
else:
|
|
open_file_descriptors = None
|
|
open_file_descriptors_perc = None
|
|
|
|
# process I/O counters (agent might not have permission to access)
|
|
read_count = 0
|
|
write_count = 0
|
|
read_kbytes = 0
|
|
write_kbytes = 0
|
|
|
|
got_denied = False
|
|
|
|
for pid in set(pids):
|
|
try:
|
|
p = psutil.Process(pid)
|
|
if extended_metrics_0_6_0:
|
|
mem = p.memory_info_ex()
|
|
real += float((mem.rss - mem.shared) / 1048576)
|
|
try:
|
|
ctx_switches = p.num_ctx_switches()
|
|
voluntary_ctx_switches += ctx_switches.voluntary
|
|
involuntary_ctx_switches += ctx_switches.involuntary
|
|
except NotImplementedError:
|
|
# Handle old Kernels which don't provide this info.
|
|
voluntary_ctx_switches = None
|
|
involuntary_ctx_switches = None
|
|
else:
|
|
mem = p.memory_info()
|
|
|
|
if extended_metrics_0_5_0_unix:
|
|
try:
|
|
open_file_descriptors = float(p.num_fds())
|
|
max_open_file_descriptors = float(p.rlimit(psutil.RLIMIT_NOFILE)[1])
|
|
if max_open_file_descriptors > 0.0:
|
|
open_file_descriptors_perc = open_file_descriptors / max_open_file_descriptors * 100
|
|
else:
|
|
open_file_descriptors_perc = 0
|
|
except psutil.AccessDenied:
|
|
got_denied = True
|
|
|
|
rss += float(mem.rss / 1048576)
|
|
vms += float(mem.vms / 1048576)
|
|
thr += p.num_threads()
|
|
cpu += p.cpu_percent(cpu_check_interval)
|
|
|
|
# user might not have permission to call io_counters()
|
|
if read_count is not None:
|
|
try:
|
|
io_counters = p.io_counters()
|
|
read_count += io_counters.read_count
|
|
write_count += io_counters.write_count
|
|
read_kbytes += float(io_counters.read_bytes / 1024)
|
|
write_kbytes += float(io_counters.write_bytes / 1024)
|
|
except psutil.AccessDenied:
|
|
self.log.debug('monasca-agent user does not have ' +
|
|
'access to I/O counters for process' +
|
|
' %d: %s'
|
|
% (pid, p.name))
|
|
read_count = None
|
|
write_count = None
|
|
read_kbytes = None
|
|
write_kbytes = None
|
|
|
|
# Skip processes dead in the meantime
|
|
except psutil.NoSuchProcess:
|
|
self.warning('Process %s disappeared while scanning' % pid)
|
|
pass
|
|
|
|
if got_denied:
|
|
self.warning("The Monitoring Agent was denied access " +
|
|
"when trying to get the number of file descriptors")
|
|
|
|
# Memory values are in Byte
|
|
return (thr, cpu, rss, vms, real, open_file_descriptors,
|
|
open_file_descriptors_perc, read_count, write_count,
|
|
read_kbytes, write_kbytes, voluntary_ctx_switches,
|
|
involuntary_ctx_switches)
|
|
|
|
def check(self, instance):
|
|
try:
|
|
import psutil
|
|
except ImportError:
|
|
raise Exception('You need the "psutil" package to run this check')
|
|
|
|
name = instance.get('name', None)
|
|
exact_match = instance.get('exact_match', True)
|
|
search_string = instance.get('search_string', None)
|
|
cpu_check_interval = instance.get('cpu_check_interval', 0.1)
|
|
|
|
if name is None:
|
|
raise KeyError('The "name" of process groups is mandatory')
|
|
|
|
if search_string is None:
|
|
raise KeyError('The "search_string" is mandatory')
|
|
|
|
if not isinstance(cpu_check_interval, (int, long, float)):
|
|
self.warning("cpu_check_interval not a number; defaulting to 0.1")
|
|
cpu_check_interval = 0.1
|
|
|
|
pids = self.find_pids(search_string, psutil, exact_match=exact_match)
|
|
dimensions = self._set_dimensions({'process_name': name}, instance)
|
|
|
|
self.log.debug('ProcessCheck: process %s analysed' % name)
|
|
|
|
self.gauge('process.pid_count', len(pids), dimensions=dimensions)
|
|
|
|
if instance.get('detailed', False):
|
|
metrics = dict(zip(ProcessCheck.PROCESS_GAUGE,
|
|
self.get_process_metrics(pids,
|
|
psutil,
|
|
cpu_check_interval)))
|
|
|
|
for metric, value in metrics.iteritems():
|
|
if value is not None:
|
|
self.gauge(metric, value, dimensions=dimensions)
|