'''
Copyright (C) 2019 Vanessa Sochat.
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/.
'''
from subprocess import (
check_output,
CalledProcessError
)
from watchme.utils import get_watchme_env
from watchme.logger import bot
import psutil
[docs]def monitor_pid_task(**kwargs):
'''monitor a specific process. This function can be used as a task, or
is (most likely used) for the psutils.decorators. A pid parameter
is required.
Parameters
==========
skip: an optional list of (comma separated) fields to skip. Can be in
net_io_counters,net_connections,net_if_address,net_if_stats
pid: the process id or name (required)
'''
pid = kwargs.get('pid', None)
bot.debug(kwargs)
# Helper function to get list from one,two,three
def get_list(name):
csv_list = kwargs.get(name, '')
return [x for x in csv_list.split(',') if x]
# A comma separated list of parameters to skip
skip = get_list('skip')
include = get_list('include')
only = get_list('only')
# Only continue given that argument is provided
if pid is None:
bot.warning("A 'pid' parameter is required to use the monitor_pid_task function.")
return pid
# The user is allowed to provide a process name, or a number
try:
pid = int(pid)
except ValueError:
pid = _get_pid(pid)
# But if it's stil no good (None) we exit.
if pid is None:
bot.warning("'pid' must be a running process or process name.")
return pid
ps = psutil.Process(pid)
bot.debug(ps)
results = {}
for key, val in ps.as_dict().items():
# If val is None, don't include
if val is None:
bot.debug('skipping %s, None' % key)
continue
if key == "connections":
connections = []
for net in val:
entry = {'fd': net.fd,
'family': str(net.family),
'type': str(net.type),
'laddr_ip': net.laddr.ip,
'laddr_port': net.laddr.port,
'raddr': net.raddr,
'status': net.status}
connections.append(entry)
val = connections
# First priority goes to a custom set
if len(only) > 0:
if key in only:
results[key] = val
else:
bot.debug('skipping %s' % key)
continue
# The user requested to skip
elif key in skip or key in ['threads']:
continue
# Keep count of openfiles
elif key in ["open_files"]:
results[key] = len(val)
# Don't risk exposing sensitive information
elif key == "environ":
if key in include:
results[key] = val
# Skip over ones that are too detailed
elif key in ['memory_maps']:
continue
# I assume this included all that are in pmem too
elif isinstance(val, psutil._pslinux.pfullmem):
results[key] = {"rss": val.rss,
"vms": val.vms,
"shared": val.shared,
"text": val.text,
"lib": val.lib,
"data": val.data,
"dirty": val.dirty,
"uss": val.uss,
"pss": val.pss,
"swap": val.swap}
elif isinstance(val, psutil._common.pgids) or isinstance(val, psutil._common.puids):
results[key] = {"real": val.real,
"effetive":val.effective,
"saved": val.saved}
elif isinstance(val, psutil._common.pcputimes):
results[key] = {"user": val.user,
"system": val.system,
"children_user": val.children_user,
"children_system": val.children_system}
elif isinstance(val, psutil._common.pctxsw):
results[key] = {"voluntary": val.voluntary,
"involuntary":val.involuntary}
elif isinstance(val, psutil._common.pionice):
results[key] = {"value": val.value}
# Older Python version (2) doesn't have attribute
if hasattr(val.ioclass, 'name'):
results[key]["ioclass"] = val.ioclass.name
# pfullmem (first above) should cover this
elif isinstance(val, psutil._pslinux.pmem):
continue
elif isinstance(val, psutil._pslinux.pio):
results[key] = {"read_count": val.read_count,
"write_count": val.write_count,
"read_bytes": val.read_bytes,
"write_bytes": val.write_bytes,
"read_chars": val.read_chars,
"write_chars": val.write_chars}
else:
results[key] = val
# Add any environment variables prefixed wit WATCHMEENV_
environ = get_watchme_env()
results.update(environ)
return results
[docs]def cpu_task(**kwargs):
'''Get variables about the cpu of the host. No parameters are required.
Parameters
==========
skip: an optional list of (comma separated) fields to skip. Can be in
net_io_counters,net_connections,net_if_address,net_if_stats
'''
result = {}
# A comma separated list of parameters to not include
skip = kwargs.get('skip', '')
skip = skip.split(',')
result['cpu_freq'] = dict(psutil.cpu_freq()._asdict())
result['cpu_percent'] = psutil.cpu_percent()
result['cpu_count'] = psutil.cpu_count()
result['cpu_stats'] = dict(psutil.cpu_stats()._asdict())
result['cpu_times'] = dict(psutil.cpu_times()._asdict())
result['cpu_times_percent'] = dict(psutil.cpu_times_percent()._asdict())
return _filter_result(result, skip)
[docs]def python_task(**kwargs):
'''Get modules, version, etc. about currently running python
Parameters
==========
skip: an optional list of (comma separated) fields to skip. Can be in
net_io_counters,net_connections,net_if_address,net_if_stats
'''
# A comma separated list of parameters to not include
skip = kwargs.get('skip', '')
skip = skip.split(',')
result = {'modules': list(psutil.os.sys.modules),
'version': psutil.os.sys.version,
'path': psutil.os.sys.path,
'prefix': psutil.os.sys.prefix,
'executable': psutil.os.sys.executable,
'copyright': psutil.os.sys.copyright }
return _filter_result(result, skip)
[docs]def system_task(**kwargs):
'''Get basic system info, unlikely to change.
Parameters
==========
skip: an optional list of (comma separated) fields to skip. Can be in
net_io_counters,net_connections,net_if_address,net_if_stats
'''
# A comma separated list of parameters to not include
skip = kwargs.get('skip', '')
skip = skip.split(',')
result = {}
result['platform'] = psutil.os.sys.platform
result['api_version'] = psutil.os.sys.api_version
result['maxsize'] = psutil.os.sys.maxsize
# Python 2 doesn't have these fields
if hasattr(psutil.os.sys, 'int_info'):
result['bits_per_digit'] = psutil.os.sys.int_info.bits_per_digit
result['sizeof_digit'] = psutil.os.sys.int_info.sizeof_digit
vinfo = psutil.os.sys.version_info
result['version_info'] = {'major': vinfo.major,
'minor': vinfo.minor,
'micro': vinfo.micro,
'releaselevel': vinfo.releaselevel,
'serial': vinfo.serial}
result['sep'] = psutil.os.sep
uname = psutil.os.uname()
# Python 2 returns a tuple
if not hasattr(uname, 'sysname'):
result['uname'] = uname
else:
result['uname'] = {'sysname': uname.sysname,
'nodename': uname.nodename,
'version': uname.version,
'release': uname.release,
'machine': uname.machine}
return _filter_result(result, skip)
[docs]def memory_task(**kwargs):
'''Get values for memory of the host. No parameters are required.
'''
result = {}
# gives an object with many fields
result['virtual_memory'] = dict(psutil.virtual_memory()._asdict())
# Add any environment variables prefixed wit WATCHMEENV_
environ = get_watchme_env()
result.update(environ)
return result
[docs]def users_task(**kwargs):
'''Get values for current users
'''
result = {'users': []}
for user in psutil.users():
result['users'].append(dict(user._asdict()))
# Add any environment variables prefixed wit WATCHMEENV_
environ = get_watchme_env()
result.update(environ)
return result
[docs]def sensors_task(**kwargs):
'''Get values from system sensors like fans, temperature, battery
'''
# A comma separated list of parameters to not include
skip = kwargs.get('skip', '')
skip = skip.split(',')
result = {}
result['sensors_temperatures'] = {}
result['sensors_fans'] = {}
# battery
battery = psutil.sensors_battery()
if battery is not None:
result['sensors_battery'] = {'percent': battery.percent,
'secsleft': str(battery.secsleft),
'power_plugged': battery.power_plugged}
# fans
for name, entry in psutil.sensors_fans().items():
entries = []
for e in entry:
item = {'label': e.label,
'current': e.current}
entries.append(item)
result['sensors_fans'][name] = entries
# temperature
for name, entry in psutil.sensors_temperatures().items():
entries = []
for e in entry:
temp = {'label': e.label,
'current': e.current,
'high': e.high,
'critical': e.critical}
entries.append(temp)
result['sensors_temperatures'][name] = entries
return _filter_result(result, skip)
[docs]def net_task(**kwargs):
'''Get values for networking on the host
Parameters
==========
skip: an optional list of (comma separated) fields to skip. Can be in
net_io_counters,net_connections,net_if_address,net_if_stats
'''
result = {}
# A comma separated list of parameters to not include
skip = kwargs.get('skip', '')
skip = skip.split(',')
result['net_connections'] = []
result['net_if_address'] = {}
result['net_if_stats'] = {}
for net in psutil.net_connections():
net_result = {'fd': net.fd,
'family': str(net.family),
'type': str(net.type),
'laddr_ip': net.laddr.ip,
'laddr_port': net.laddr.port,
'raddr': net.raddr,
'status': net.status,
'pid': net.pid}
result['net_connections'].append(net_result)
for name, entry in psutil.net_if_addrs().items():
entries = []
for e in entry:
item = {'family': str(e.family),
'address': e.address,
'netmask': e.netmask,
'broadcast': e.broadcast,
'ptp': e.ptp}
entries.append(item)
result['net_if_address'][name] = entries
for name, entry in psutil.net_if_stats().items():
item = {'isup': entry.isup,
'duplex': str(entry.duplex),
'speed': entry.speed,
'mtu': entry.mtu}
result['net_if_stats'][name] = item
netio = psutil.net_io_counters()
result['net_io_counters'] = {'bytes_sent': netio.bytes_sent,
'bytes_recv': netio.bytes_recv,
'packets_sent': netio.packets_sent,
'packets_recv': netio.packets_recv,
'errin': netio.errin,
'errout': netio.errout,
'dropin': netio.dropin,
'dropout': netio.dropout}
# Remove those not wanted
return _filter_result(result, skip)
[docs]def disk_task(**kwargs):
'''Get values for disks on the host
Parameters
==========
disk_usage: an optional path to get disk usage for.
skip: an optional list of (comma separated) fields to skip. Can be in
net_io_counters,net_connections,net_if_address,net_if_stats
'''
# A comma separated list of parameters to not include
skip = kwargs.get('skip', '')
skip = skip.split(',')
path = kwargs.get('disk_usage', '/')
result = {}
result['disk_to_counters'] = dict(psutil.disk_io_counters()._asdict())
result['disk_partitions'] = []
for disk in psutil.disk_partitions():
disk_result = {'device': disk.device,
'mountpoint': disk.mountpoint,
'fstype': disk.fstype,
'opts': disk.opts}
result['disk_partitions'].append(disk_result)
result['disk_usage'] = dict(psutil.disk_usage(path)._asdict())
# Remove those not wanted
return _filter_result(result, skip)
def _filter_result(result, skip):
'''a helper function to filter a dictionary based on a list of keys to
skip. We also add variables from the environment.
Parameters
==========
result: a dictionary of results
skip: a list of keys to remove/filter from the result.
'''
# Add any environment variables prefixed wit WATCHMEENV_
environ = get_watchme_env()
result.update(environ)
for key in skip:
if key in result:
del result[key]
return result
def _get_pid(name):
'''used to get the pid of a process, by name
Parameters
==========
name: the name of the process to get.
'''
try:
pid = check_output(["pidof", name])
pid = pid.decode('utf-8').strip('\n').split(' ')
if len(pid) > 1:
bot.warning("More than one pid found for %s, using first." % name)
pid = int(pid[0])
except CalledProcessError:
bot.error("%s does not exist." % name)
pid = None
return pid