├── pysysinfo ├── __init__.py ├── wanpipe.py ├── redisdb.py ├── varnish.py ├── phpfpm.py ├── rackspace.py ├── phpopc.py ├── apache.py ├── lighttpd.py ├── nginx.py ├── ntp.py ├── filesystem.py ├── netiface.py ├── phpapc.py ├── freeswitch.py ├── tomcat.py ├── system.py ├── mysql.py ├── memcached.py ├── squid.py └── process.py ├── pymunin └── plugins │ ├── __init__.py │ ├── ntpstats.py │ ├── fsstats.py │ ├── ntphostoffset_.py │ ├── phpfpmstats.py │ ├── procstats.py │ ├── ntphostoffsets.py │ ├── rackspacestats.py │ ├── apachestats.py │ ├── lighttpdstats.py │ ├── diskusagestats.py │ ├── netstats.py │ ├── netifacestats.py │ ├── phpopcstats.py │ ├── nginxstats.py │ ├── varnishstats.py │ ├── wanpipestats.py │ └── diskiostats.py ├── MANIFEST.in ├── download.txt ├── .gitignore ├── ext ├── apache │ └── status.conf ├── opcinfo.php └── apcinfo.php ├── setup.py └── README.md /pysysinfo/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pymunin/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include COPYING.txt 3 | include download.txt 4 | recursive-include ext * 5 | -------------------------------------------------------------------------------- /download.txt: -------------------------------------------------------------------------------- 1 | Check http://aouyar.github.com/PyMunin/ to get the most recent version of the 2 | PyMunin Multi graph Munin Plugins and documentation. 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | .project 3 | .pydevproject 4 | *~ 5 | *.db 6 | *.orig 7 | docs/_build/* 8 | dist/* 9 | build/* 10 | *.egg-info/* 11 | .coverage 12 | _site 13 | -------------------------------------------------------------------------------- /ext/apache/status.conf: -------------------------------------------------------------------------------- 1 | ExtendedStatus On 2 | 3 | 4 | SetHandler server-status 5 | Order Deny,Allow 6 | Deny from all 7 | Allow from 127.0.0.1 8 | 9 | 10 | -------------------------------------------------------------------------------- /ext/opcinfo.php: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /pysysinfo/wanpipe.py: -------------------------------------------------------------------------------- 1 | """Implements WanpipeInfo Class for gathering stats from Wanpipe 2 | Telephony Interfaces. 3 | 4 | """ 5 | 6 | import re 7 | import util 8 | import netiface 9 | 10 | __author__ = "Ali Onur Uyar" 11 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 12 | __credits__ = [] 13 | __license__ = "GPL" 14 | __version__ = "0.9" 15 | __maintainer__ = "Ali Onur Uyar" 16 | __email__ = "aouyar at gmail.com" 17 | __status__ = "Development" 18 | 19 | 20 | # Defaults 21 | wanpipemonCmd = '/usr/sbin/wanpipemon' 22 | 23 | 24 | class WanpipeInfo: 25 | """Class to retrieve stats for Wanpipe Interfaces.""" 26 | 27 | def getIfaceStats(self): 28 | """Return dictionary of Traffic Stats for each Wanpipe Interface. 29 | 30 | @return: Nested dictionary of statistics for each interface. 31 | 32 | """ 33 | ifInfo = netiface.NetIfaceInfo() 34 | ifStats = ifInfo.getIfStats() 35 | info_dict = {} 36 | for ifname in ifStats: 37 | if re.match('^w\d+g\d+$', ifname): 38 | info_dict[ifname] = ifStats[ifname] 39 | return info_dict 40 | 41 | def getPRIstats(self, iface): 42 | """Return RDSI Operational Stats for interface. 43 | 44 | @param iface: Interface name. (Ex. w1g1) 45 | @return: Nested dictionary of statistics for interface. 46 | 47 | """ 48 | info_dict = {} 49 | output = util.exec_command([wanpipemonCmd, '-i', iface, '-c', 'Ta']) 50 | for line in output.splitlines(): 51 | mobj = re.match('^\s*(Line Code Violation|Far End Block Errors|' 52 | 'CRC4 Errors|FAS Errors)\s*:\s*(\d+)\s*$', 53 | line, re.IGNORECASE) 54 | if mobj: 55 | info_dict[mobj.group(1).lower().replace(' ', '')] = int(mobj.group(2)) 56 | continue 57 | mobj = re.match('^\s*(Rx Level)\s*:\s*>{0,1}\s*([-\d\.]+)db\s*', 58 | line, re.IGNORECASE) 59 | if mobj: 60 | info_dict[mobj.group(1).lower().replace(' ', '')] = float(mobj.group(2)) 61 | continue 62 | return info_dict 63 | -------------------------------------------------------------------------------- /pysysinfo/redisdb.py: -------------------------------------------------------------------------------- 1 | """Implements RedisInfo Class for gathering stats from Redis. 2 | 3 | """ 4 | 5 | import time 6 | import redis 7 | import util 8 | 9 | __author__ = "Ali Onur Uyar" 10 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 11 | __credits__ = [] 12 | __license__ = "GPL" 13 | __version__ = "0.9" 14 | __maintainer__ = "Ali Onur Uyar" 15 | __email__ = "aouyar at gmail.com" 16 | __status__ = "Development" 17 | 18 | 19 | 20 | 21 | class RedisInfo: 22 | """Class that establishes connection to Memcached Instance 23 | to retrieve statistics on operation. 24 | 25 | """ 26 | 27 | def __init__(self, host=None, port=None, db=None, password=None, 28 | socket_timeout=None, unix_socket_path=None): 29 | """Initialize connection to Redis. 30 | 31 | @param host: Redis Host. (Default: localhost) 32 | @param port: Redis Port. (Default: Default Redis Port) 33 | @param db: Redis DB ID. (Default: 0) 34 | @param password: Redis Password (Optional) 35 | @param socket_timeout: Redis Socket Timeout (Default: OS Default.) 36 | @param unix_socket_path: Socket File Path for UNIX Socket connections. 37 | (Not required unless connection to Redis is 38 | through named socket.) 39 | 40 | """ 41 | params = locals() 42 | self._conn = None 43 | self._connParams = dict((k, params[k]) 44 | for k in ('host', 'port', 'db', 'password', 45 | 'socket_timeout', 'unix_socket_path') 46 | if params[k] is not None) 47 | self._conn = redis.Redis(**self._connParams) 48 | 49 | def ping(self): 50 | """Ping Redis Server and return Round-Trip-Time in seconds. 51 | 52 | @return: Round-trip-time in seconds as float. 53 | 54 | """ 55 | start = time.time() 56 | self._conn.ping() 57 | return (time.time() - start) 58 | 59 | def getStats(self): 60 | """Query Redis and return stats. 61 | 62 | @return: Dictionary of stats. 63 | 64 | """ 65 | try: 66 | return self._conn.info('all') 67 | except TypeError: 68 | return self._conn.info() 69 | 70 | -------------------------------------------------------------------------------- /pysysinfo/varnish.py: -------------------------------------------------------------------------------- 1 | """Implements VarnishInfo Class for gathering stats from Varnish Cache. 2 | 3 | The statistics are obtained by running the command varnishstats. 4 | 5 | """ 6 | 7 | import re 8 | import util 9 | 10 | __author__ = "Ali Onur Uyar" 11 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 12 | __credits__ = [] 13 | __license__ = "GPL" 14 | __version__ = "0.9.24" 15 | __maintainer__ = "Ali Onur Uyar" 16 | __email__ = "aouyar at gmail.com" 17 | __status__ = "Development" 18 | 19 | 20 | # Defaults 21 | varnishstatCmd = "varnishstat" 22 | 23 | 24 | 25 | class VarnishInfo: 26 | """Class to retrieve stats from Varnish Cache.""" 27 | 28 | _descDict = {} 29 | 30 | def __init__(self, instance=None): 31 | """Initialization for monitoring Varnish Cache instance. 32 | 33 | @param instance: Name of the Varnish Cache instance. 34 | (Defaults to hostname.) 35 | """ 36 | self._instance = instance 37 | 38 | 39 | def getStats(self): 40 | """Runs varnishstats command to get stats from Varnish Cache. 41 | 42 | @return: Dictionary of stats. 43 | 44 | """ 45 | info_dict = {} 46 | args = [varnishstatCmd, '-1'] 47 | if self._instance is not None: 48 | args.extend(['-n', self._instance]) 49 | output = util.exec_command(args) 50 | if self._descDict is None: 51 | self._descDict = {} 52 | for line in output.splitlines(): 53 | mobj = re.match('(\S+)\s+(\d+)\s+(\d+\.\d+|\.)\s+(\S.*\S)\s*$', 54 | line) 55 | if mobj: 56 | fname = mobj.group(1).replace('.', '_') 57 | info_dict[fname] = util.parse_value(mobj.group(2)) 58 | self._descDict[fname] = mobj.group(4) 59 | return info_dict 60 | 61 | def getDescDict(self): 62 | """Returns dictionary mapping stats entries to decriptions. 63 | 64 | @return: Dictionary. 65 | 66 | """ 67 | if len(self._descDict) == 0: 68 | self.getStats() 69 | return self._descDict 70 | 71 | def getDesc(self, entry): 72 | """Returns description for stat entry. 73 | 74 | @param entry: Entry name. 75 | @return: Description for entry. 76 | 77 | """ 78 | if len(self._descDict) == 0: 79 | self.getStats() 80 | return self._descDict.get(entry) 81 | 82 | -------------------------------------------------------------------------------- /pysysinfo/phpfpm.py: -------------------------------------------------------------------------------- 1 | """Implements PHPfpmInfo Class for gathering stats from PHP FastCGI Process 2 | Manager using the status page. 3 | 4 | The status interface of PHP FastCGI Process Manager must be enabled. 5 | 6 | """ 7 | 8 | import re 9 | import util 10 | 11 | __author__ = "Ali Onur Uyar" 12 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 13 | __credits__ = [] 14 | __license__ = "GPL" 15 | __version__ = "0.9.12" 16 | __maintainer__ = "Ali Onur Uyar" 17 | __email__ = "aouyar at gmail.com" 18 | __status__ = "Development" 19 | 20 | 21 | defaultHTTPport = 80 22 | defaultHTTPSport = 443 23 | 24 | 25 | class PHPfpmInfo: 26 | """Class to retrieve stats from APC from Web Server.""" 27 | 28 | def __init__(self, host=None, port=None, user=None, password=None, 29 | monpath=None, ssl=False): 30 | """Initialize URL for PHP FastCGI Process Manager status page. 31 | 32 | @param host: Web Server Host. (Default: 127.0.0.1) 33 | @param port: Web Server Port. (Default: 80, SSL: 443) 34 | @param user: Username. (Not needed unless authentication is required 35 | to access status page. 36 | @param password: Password. (Not needed unless authentication is required 37 | to access status page. 38 | @param monpath: PHP FPM path relative to Document Root. 39 | (Default: fpm_status.php) 40 | @param ssl: Use SSL if True. (Default: False) 41 | 42 | """ 43 | if host is not None: 44 | self._host = host 45 | else: 46 | self._host = '127.0.0.1' 47 | if port is not None: 48 | self._port = int(port) 49 | else: 50 | if ssl: 51 | self._port = defaultHTTPSport 52 | else: 53 | self._port = defaultHTTPport 54 | self._user = user 55 | self._password = password 56 | if ssl: 57 | self._proto = 'https' 58 | else: 59 | self._proto = 'http' 60 | if monpath: 61 | self._monpath = monpath 62 | else: 63 | self._monpath = 'fpm_status.php' 64 | 65 | def getStats(self): 66 | """Query and parse Web Server Status Page. 67 | 68 | """ 69 | url = "%s://%s:%d/%s" % (self._proto, self._host, self._port, 70 | self._monpath) 71 | response = util.get_url(url, self._user, self._password) 72 | stats = {} 73 | for line in response.splitlines(): 74 | mobj = re.match('([\w\s]+):\s+(\w+)$', line) 75 | if mobj: 76 | stats[mobj.group(1)] = util.parse_value(mobj.group(2)) 77 | return stats 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /pysysinfo/rackspace.py: -------------------------------------------------------------------------------- 1 | """Implements methods for gathering stats from Rackspace Cloud Service. 2 | 3 | """ 4 | 5 | import cloudfiles 6 | 7 | __author__ = "Ben Welsh" 8 | __copyright__ = "Copyright 2012, Ben Welsh" 9 | __credits__ = [] 10 | __license__ = "GPL" 11 | __version__ = "0.2" 12 | __maintainer__ = "Ali Onur Uyar" 13 | __email__ = "aouyar at gmail.com" 14 | __status__ = "Development" 15 | 16 | 17 | class CloudFilesInfo: 18 | """ 19 | Establishes connection to Rackspace Cloud to retrieve stats on Cloud Files. 20 | """ 21 | def __init__(self, username, api_key, 22 | region=None, servicenet=False, timeout=4): 23 | """Initialize connection to Rackspace Cloud Files. 24 | 25 | @param username: Rackspace Cloud username 26 | @param api_key: Rackspace Cloud api_key 27 | @param region: Try passing "us" for US Auth Service, and "uk" UK Auth 28 | Service; omit parameter to use library default. 29 | @servicenet: If True, Rackspace ServiceNet network will be used to 30 | access Cloud Files. 31 | @timeout: Connection timeout in seconds. (Default: 4) 32 | 33 | """ 34 | self._connParams = {} 35 | self._connParams['username'] = username 36 | self._connParams['api_key'] = api_key 37 | if region is not None: 38 | try: 39 | authurl = getattr(cloudfiles, '%s_authurl' % str(region)) 40 | self._connParams['authurl'] = authurl 41 | except: 42 | raise Exception("Invalid region code: %s" % str(region)) 43 | if servicenet: 44 | self._connParams['servicenet'] = True 45 | self._connParams['timeout'] = timeout 46 | self._conn = cloudfiles.get_connection(**self._connParams) 47 | 48 | def getContainerList(self, limit=None, marker=None): 49 | """Returns list of Rackspace Cloud Files containers names. 50 | 51 | @param limit: Number of containers to return. 52 | @param marker: Return only results whose name is greater than marker. 53 | @return: List of container names. 54 | 55 | """ 56 | return self._conn.list_containers(limit, marker) 57 | 58 | def getContainerStats(self, limit=None, marker=None): 59 | """Returns Rackspace Cloud Files usage stats for containers. 60 | 61 | @param limit: Number of containers to return. 62 | @param marker: Return only results whose name is greater than marker. 63 | @return: Dictionary of container stats indexed by container name. 64 | 65 | """ 66 | stats = {} 67 | for row in self._conn.list_containers_info(limit, marker): 68 | stats[row['name']] = {'count': row['count'], 'size': row['bytes']} 69 | return stats 70 | -------------------------------------------------------------------------------- /pysysinfo/phpopc.py: -------------------------------------------------------------------------------- 1 | """Implements OPCinfo Class for gathering stats from Zend Optimizor +. 2 | 3 | The statistics are obtained through a request to custom opcinfo.php script 4 | that must be placed in the Web Server Document Root Directory. 5 | 6 | """ 7 | 8 | import util 9 | import json 10 | 11 | __author__ = "Preston M." 12 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 13 | __credits__ = [] 14 | __license__ = "GPL" 15 | __version__ = "0.9.24" 16 | __maintainer__ = "Preston M." 17 | __email__ = "pentie at gmail.com" 18 | __status__ = "Development" 19 | 20 | 21 | defaultHTTPport = 80 22 | defaultHTTPSport = 443 23 | 24 | 25 | class OPCinfo: 26 | """Class to retrieve stats from APC from Web Server.""" 27 | 28 | def __init__(self, host=None, port=None, user=None, password=None, 29 | monpath=None, ssl=False, extras=False, autoInit=True): 30 | """Initialize URL for APC stats access. 31 | 32 | @param host: Web Server Host. (Default: 127.0.0.1) 33 | @param port: Web Server Port. (Default: 80, SSL: 443) 34 | @param user: Username. (Not needed unless authentication is required 35 | to access status page. 36 | @param password: Password. (Not needed unless authentication is required 37 | to access status page. 38 | @param monpath: APC status script path relative to Document Root. 39 | (Default: apcinfo.php) 40 | @param ssl: Use SSL if True. (Default: False) 41 | @param extras: Include extra metrics, which can be computationally more 42 | expensive. 43 | @param autoInit: If True connect to Web Server on instantiation. 44 | 45 | """ 46 | if host is not None: 47 | self._host = host 48 | else: 49 | self._host = '127.0.0.1' 50 | if port is not None: 51 | self._port = int(port) 52 | else: 53 | if ssl: 54 | self._port = defaultHTTPSport 55 | else: 56 | self._port = defaultHTTPport 57 | self._user = user 58 | self._password = password 59 | if ssl: 60 | self._proto = 'https' 61 | else: 62 | self._proto = 'http' 63 | if monpath: 64 | self._monpath = monpath 65 | else: 66 | self._monpath = 'opcinfo.php' 67 | self._extras = extras 68 | self._statusDict = None 69 | if autoInit: 70 | self.initStats() 71 | 72 | def initStats(self, extras=None): 73 | """Query and parse Web Server Status Page. 74 | 75 | @param extras: Include extra metrics, which can be computationally more 76 | expensive. 77 | 78 | """ 79 | url = "%s://%s:%d/%s" % (self._proto, self._host, self._port, self._monpath) 80 | response = util.get_url(url, self._user, self._password) 81 | #with open('/tmp/opcinfo.json') as f: 82 | # response = f.read() 83 | self._statusDict = json.loads(response) 84 | 85 | def getAllStats(self): 86 | """Return All Stats for APC. 87 | 88 | @return: Nested dictionary of stats. 89 | 90 | """ 91 | return self._statusDict; 92 | 93 | -------------------------------------------------------------------------------- /pysysinfo/apache.py: -------------------------------------------------------------------------------- 1 | """Implements ApacheInfo Class for gathering stats from Apache Web Server. 2 | 3 | The statistics are obtained by connecting to and querying the server-status 4 | page of local and/or remote Apache Web Servers. 5 | 6 | """ 7 | 8 | import re 9 | import util 10 | 11 | __author__ = "Ali Onur Uyar" 12 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 13 | __credits__ = [] 14 | __license__ = "GPL" 15 | __version__ = "0.9.12" 16 | __maintainer__ = "Ali Onur Uyar" 17 | __email__ = "aouyar at gmail.com" 18 | __status__ = "Development" 19 | 20 | 21 | defaultHTTPport = 80 22 | defaultHTTPSport = 443 23 | 24 | 25 | class ApacheInfo: 26 | """Class to retrieve stats for Apache Web Server.""" 27 | 28 | def __init__(self, host=None, port=None, user=None, password=None, 29 | statuspath = None, ssl=False, autoInit=True): 30 | """Initialize Apache server-status URL access. 31 | 32 | @param host: Apache Web Server Host. (Default: 127.0.0.1) 33 | @param port: Apache Web Server Port. (Default: 80, SSL: 443) 34 | @param user: Username. (Not needed unless authentication is required 35 | to access server-status page. 36 | @param password: Password. (Not needed unless authentication is required 37 | to access server-status page. 38 | @statuspath: Path of status page. (Default: server-status) 39 | @param ssl: Use SSL if True. (Default: False) 40 | @param autoInit: If True connect to Apache Web Server on instantiation. 41 | 42 | """ 43 | if host is not None: 44 | self._host = host 45 | else: 46 | self._host = '127.0.0.1' 47 | if port is not None: 48 | self._port = int(port) 49 | else: 50 | if ssl: 51 | self._port = defaultHTTPSport 52 | else: 53 | self._port = defaultHTTPport 54 | self._user = user 55 | self._password = password 56 | if statuspath is not None: 57 | self._statuspath = statuspath 58 | else: 59 | self._statuspath = 'server-status' 60 | if ssl: 61 | self._proto = 'https' 62 | else: 63 | self._proto = 'http' 64 | self._statusDict = None 65 | if autoInit: 66 | self.initStats() 67 | 68 | def initStats(self): 69 | """Query and parse Apache Web Server Status Page.""" 70 | url = "%s://%s:%d/%s?auto" % (self._proto, self._host, self._port, 71 | self._statuspath) 72 | response = util.get_url(url, self._user, self._password) 73 | self._statusDict = {} 74 | for line in response.splitlines(): 75 | mobj = re.match('(\S.*\S)\s*:\s*(\S+)\s*$', line) 76 | if mobj: 77 | self._statusDict[mobj.group(1)] = util.parse_value(mobj.group(2)) 78 | if self._statusDict.has_key('Scoreboard'): 79 | self._statusDict['MaxWorkers'] = len(self._statusDict['Scoreboard']) 80 | 81 | def getServerStats(self): 82 | """Return Stats for Apache Web Server. 83 | 84 | @return: Dictionary of server stats. 85 | 86 | """ 87 | return self._statusDict; 88 | 89 | -------------------------------------------------------------------------------- /pysysinfo/lighttpd.py: -------------------------------------------------------------------------------- 1 | """Implements LighttpdInfo Class for gathering stats from Lighttpd Web Server. 2 | 3 | The statistics are obtained by connecting to and querying the server-status 4 | page of local and/or remote Lighttpd Web Servers. 5 | 6 | """ 7 | 8 | import re 9 | import util 10 | 11 | __author__ = "Ali Onur Uyar" 12 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 13 | __credits__ = [] 14 | __license__ = "GPL" 15 | __version__ = "0.9.12" 16 | __maintainer__ = "Ali Onur Uyar" 17 | __email__ = "aouyar at gmail.com" 18 | __status__ = "Development" 19 | 20 | 21 | defaultHTTPport = 80 22 | defaultHTTPSport = 443 23 | 24 | 25 | class LighttpdInfo: 26 | """Class to retrieve stats for Lighttpd Web Server.""" 27 | 28 | def __init__(self, host=None, port=None, user=None, password=None, 29 | statuspath = None, ssl=False, autoInit=True): 30 | """Initialize Lighttpd server-status URL access. 31 | 32 | @param host: Lighttpd Web Server Host. (Default: 127.0.0.1) 33 | @param port: Lighttpd Web Server Port. (Default: 80, SSL: 443) 34 | @param user: Username. (Not needed unless authentication is required 35 | to access server-status page. 36 | @param password: Password. (Not needed unless authentication is required 37 | to access server-status page. 38 | @statuspath: Path of status page. (Default: server-status) 39 | @param ssl: Use SSL if True. (Default: False) 40 | @param autoInit: If True connect to Lighttpd Web Server on instantiation. 41 | 42 | """ 43 | if host is not None: 44 | self._host = host 45 | else: 46 | self._host = '127.0.0.1' 47 | if port is not None: 48 | self._port = int(port) 49 | else: 50 | if ssl: 51 | self._port = defaultHTTPSport 52 | else: 53 | self._port = defaultHTTPport 54 | self._user = user 55 | self._password = password 56 | if statuspath is not None: 57 | self._statuspath = statuspath 58 | else: 59 | self._statuspath = 'server-status' 60 | if ssl: 61 | self._proto = 'https' 62 | else: 63 | self._proto = 'http' 64 | self._statusDict = None 65 | if autoInit: 66 | self.initStats() 67 | 68 | def initStats(self): 69 | """Query and parse Lighttpd Web Server Status Page.""" 70 | url = "%s://%s:%d/%s?auto" % (self._proto, self._host, self._port, 71 | self._statuspath) 72 | response = util.get_url(url, self._user, self._password) 73 | self._statusDict = {} 74 | for line in response.splitlines(): 75 | mobj = re.match('(\S.*\S)\s*:\s*(\S+)\s*$', line) 76 | if mobj: 77 | self._statusDict[mobj.group(1)] = util.parse_value(mobj.group(2)) 78 | if self._statusDict.has_key('Scoreboard'): 79 | self._statusDict['MaxServers'] = len(self._statusDict['Scoreboard']) 80 | 81 | def getServerStats(self): 82 | """Return Stats for Lighttpd Web Server. 83 | 84 | @return: Dictionary of server stats. 85 | 86 | """ 87 | return self._statusDict; 88 | -------------------------------------------------------------------------------- /ext/apcinfo.php: -------------------------------------------------------------------------------- 1 | $val) { 47 | printf("%s:%s:%s\n",'cache_sys', $key, $val); 48 | } 49 | foreach ($cache_user as $key => $val) { 50 | printf("%s:%s:%s\n",'cache_user', $key, $val); 51 | } 52 | 53 | $num_seg = $mem['num_seg']; 54 | $seg_size = $mem['seg_size']; 55 | $avail_mem = $mem['avail_mem']; 56 | $total_mem = $num_seg * $seg_size; 57 | $util_ratio = (float) $avail_mem / $total_mem; 58 | $mem['total_mem'] = $total_mem; 59 | $mem['utilization_ratio'] = 1 - $util_ratio; 60 | 61 | if ($detail) { 62 | // Fragmentation: 1 - (Largest Block of Free Memory / Total Free Memory) 63 | $total_num_frag = 0; 64 | $total_frag = 0; 65 | $total_free = 0; 66 | $total_free_small = 0; 67 | for($i=0; $i < $num_seg; $i++) { 68 | $seg_free_max = 0; $seg_free_total = 0; $seg_num_frag = 0; 69 | $seg_free_small = 0; 70 | foreach($mem_detail['block_lists'][$i] as $block) { 71 | $seg_num_frag += 1; 72 | if ($block['size'] > $seg_free_max) { 73 | $seg_free_max = $block['size']; 74 | } 75 | if ($block['size'] < $MAX_FRAGMENT_SIZE) { 76 | $seg_free_small += $block['size']; 77 | } 78 | $seg_free_total += $block['size']; 79 | } 80 | if ($seg_num_frag > 1) { 81 | $total_num_frag += $seg_num_frag - 1; 82 | $total_frag += $seg_free_total - $seg_free_max; 83 | $total_free_small += $seg_free_small; 84 | } 85 | $total_free += $seg_free_total; 86 | } 87 | $frag_count = $total_num_frag; 88 | $frag_avg_size = ($frag_count > 0) ? (float )$total_frag / $frag_count: 0; 89 | switch ($algorithm) { 90 | case 1: 91 | $frag_ratio = ($total_free > 0) ? (float) $total_frag / $total_free : 0; 92 | break; 93 | default: 94 | $frag_ratio = ($total_free > 0) ? (float) $total_free_small / $total_free : 0; 95 | $algorithm = 0; 96 | break; 97 | } 98 | $mem['fragmentation_algorithm'] = $algorithm; 99 | $mem['fragmentation_ratio'] = $frag_ratio; 100 | $mem['fragment_count'] = $frag_count; 101 | $mem['fragment_avg_size'] = $frag_avg_size; 102 | } 103 | 104 | foreach ($mem as $key => $val) { 105 | printf("%s:%s:%s\n",'memory', $key, $val); 106 | } 107 | 108 | ?> 109 | -------------------------------------------------------------------------------- /pysysinfo/nginx.py: -------------------------------------------------------------------------------- 1 | """Implements NginxInfo Class for gathering stats from Nginx Web Server. 2 | 3 | The statistics are obtained by connecting to and querying the server-status 4 | page of local and/or remote Nginx Web Servers. 5 | 6 | """ 7 | 8 | import re 9 | import util 10 | 11 | __author__ = "Ali Onur Uyar" 12 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 13 | __credits__ = [] 14 | __license__ = "GPL" 15 | __version__ = "0.9.12" 16 | __maintainer__ = "Ali Onur Uyar" 17 | __email__ = "aouyar at gmail.com" 18 | __status__ = "Development" 19 | 20 | 21 | defaultHTTPport = 80 22 | defaultHTTPSport = 443 23 | 24 | 25 | class NginxInfo: 26 | """Class to retrieve stats for Nginx Web Server.""" 27 | 28 | def __init__(self, host=None, port=None, user=None, password=None, 29 | statuspath = None, ssl=False, autoInit=True): 30 | """Initialize Nginx server-status URL access. 31 | 32 | @param host: Nginx Web Server Host. (Default: 127.0.0.1) 33 | @param port: Nginx Web Server Port. (Default: 80, SSL: 443) 34 | @param user: Username. (Not needed unless authentication is required 35 | to access server-status page. 36 | @param password: Password. (Not needed unless authentication is required 37 | to access server-status page. 38 | @statuspath: Path of status page. (Default: nginx_status) 39 | @param ssl: Use SSL if True. (Default: False) 40 | @param autoInit: If True connect to Nginx Web Server on instantiation. 41 | 42 | """ 43 | if host is not None: 44 | self._host = host 45 | else: 46 | self._host = '127.0.0.1' 47 | if port is not None: 48 | self._port = int(port) 49 | else: 50 | if ssl: 51 | self._port = defaultHTTPSport 52 | else: 53 | self._port = defaultHTTPport 54 | self._user = user 55 | self._password = password 56 | if statuspath is not None: 57 | self._statuspath = statuspath 58 | else: 59 | self._statuspath = 'nginx_status' 60 | if ssl: 61 | self._proto = 'https' 62 | else: 63 | self._proto = 'http' 64 | self._statusDict = None 65 | if autoInit: 66 | self.initStats() 67 | 68 | def initStats(self): 69 | """Query and parse Nginx Web Server Status Page.""" 70 | url = "%s://%s:%d/%s" % (self._proto, self._host, self._port, 71 | self._statuspath) 72 | response = util.get_url(url, self._user, self._password) 73 | self._statusDict = {} 74 | for line in response.splitlines(): 75 | mobj = re.match('\s*(\d+)\s+(\d+)\s+(\d+)\s*$', line) 76 | if mobj: 77 | idx = 0 78 | for key in ('accepts','handled','requests'): 79 | idx += 1 80 | self._statusDict[key] = util.parse_value(mobj.group(idx)) 81 | else: 82 | for (key,val) in re.findall('(\w+):\s*(\d+)', line): 83 | self._statusDict[key.lower()] = util.parse_value(val) 84 | 85 | def getServerStats(self): 86 | """Return Stats for Nginx Web Server. 87 | 88 | @return: Dictionary of server stats. 89 | 90 | """ 91 | return self._statusDict; 92 | 93 | -------------------------------------------------------------------------------- /pysysinfo/ntp.py: -------------------------------------------------------------------------------- 1 | """Implements NTPinfo Class for gathering time synchronization stats from NTP. 2 | 3 | The statistics are obtained by connecting to and querying local and/or 4 | remote NTP servers. 5 | 6 | """ 7 | 8 | import re 9 | import util 10 | 11 | __author__ = "Ali Onur Uyar" 12 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 13 | __credits__ = [] 14 | __license__ = "GPL" 15 | __version__ = "0.9" 16 | __maintainer__ = "Ali Onur Uyar" 17 | __email__ = "aouyar at gmail.com" 18 | __status__ = "Development" 19 | 20 | 21 | # Defaults 22 | ntpqCmd = "ntpq" 23 | ntpdateCmd = "ntpdate" 24 | 25 | 26 | class NTPinfo: 27 | """Class to retrieve stats for Time Synchronization from NTP Service""" 28 | 29 | def getPeerStats(self): 30 | """Get NTP Peer Stats for localhost by querying local NTP Server. 31 | 32 | @return: Dictionary of NTP stats converted to seconds. 33 | 34 | """ 35 | info_dict = {} 36 | output = util.exec_command([ntpqCmd, '-n', '-c', 'peers']) 37 | for line in output.splitlines(): 38 | mobj = re.match('\*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+', line) 39 | if mobj: 40 | info_dict['ip'] = mobj.group(1) 41 | cols = line.split() 42 | info_dict['stratum'] = int(cols[2]) 43 | info_dict['delay'] = float(cols[7]) / 1000.0 44 | info_dict['offset'] = float(cols[8]) / 1000.0 45 | info_dict['jitter'] = float(cols[9]) / 1000.0 46 | return info_dict 47 | else: 48 | raise Exception("Execution of command failed: %s" % ntpqCmd) 49 | return info_dict 50 | 51 | def getHostOffset(self, host): 52 | """Get NTP Stats and offset of remote host relative to localhost 53 | by querying NTP Server on remote host. 54 | 55 | @param host: Remote Host IP. 56 | @return: Dictionary of NTP stats converted to seconds. 57 | 58 | """ 59 | info_dict = {} 60 | output = util.exec_command([ntpdateCmd, '-u', '-q', host]) 61 | for line in output.splitlines(): 62 | mobj = re.match('server.*,\s*stratum\s+(\d),.*' 63 | 'offset\s+([\d\.-]+),.*delay\s+([\d\.]+)\s*$', 64 | line) 65 | if mobj: 66 | info_dict['stratum'] = int(mobj.group(1)) 67 | info_dict['delay'] = float(mobj.group(3)) 68 | info_dict['offset'] = float(mobj.group(2)) 69 | return info_dict 70 | return info_dict 71 | 72 | def getHostOffsets(self, hosts): 73 | """Get NTP Stats and offset of multiple remote hosts relative to localhost 74 | by querying NTP Servers on remote hosts. 75 | 76 | @param host: List of Remote Host IPs. 77 | @return: Dictionary of NTP stats converted to seconds. 78 | 79 | """ 80 | info_dict = {} 81 | output = util.exec_command([ntpdateCmd, '-u', '-q'] + list(hosts)) 82 | for line in output.splitlines(): 83 | mobj = re.match('server\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}),' 84 | '\s*stratum\s+(\d),.*offset\s+([\d\.-]+),' 85 | '.*delay\s+([\d\.]+)\s*$', line) 86 | if mobj: 87 | host_dict = {} 88 | host = mobj.group(1) 89 | host_dict['stratum'] = int(mobj.group(2)) 90 | host_dict['delay'] = float(mobj.group(4)) 91 | host_dict['offset'] = float(mobj.group(3)) 92 | info_dict[host] = host_dict 93 | return info_dict 94 | -------------------------------------------------------------------------------- /pymunin/plugins/ntpstats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ntpstats - Munin Plugin to monitor stats of active synchronization peer. 3 | 4 | 5 | Requirements 6 | 7 | - Requires ntpd running on local host and ntpq utility. 8 | 9 | Wild Card Plugin - No 10 | 11 | 12 | Multigraph Plugin - Graph Structure 13 | 14 | - ntp_peer_stratum 15 | - ntp_peer_stats 16 | 17 | 18 | Environment Variables 19 | 20 | include_graphs: Comma separated list of enabled graphs. 21 | (All graphs enabled by default.) 22 | exclude_graphs: Comma separated list of disabled graphs. 23 | 24 | Example: 25 | [ntpstats] 26 | env.exclude_graphs ntp_peer_stratum 27 | 28 | """ 29 | # Munin - Magic Markers 30 | #%# family=auto 31 | #%# capabilities=autoconf nosuggest 32 | 33 | import sys 34 | from pymunin import MuninGraph, MuninPlugin, muninMain 35 | from pysysinfo.ntp import NTPinfo 36 | 37 | 38 | __author__ = "Ali Onur Uyar" 39 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 40 | __credits__ = [] 41 | __license__ = "GPL" 42 | __version__ = "0.9" 43 | __maintainer__ = "Ali Onur Uyar" 44 | __email__ = "aouyar at gmail.com" 45 | __status__ = "Development" 46 | 47 | 48 | class MuninNTPstatsPlugin(MuninPlugin): 49 | """Multigraph Munin Plugin for monitoring NTP Peer. 50 | 51 | """ 52 | plugin_name = 'ntpstats' 53 | isMultigraph = True 54 | 55 | def __init__(self, argv=(), env=None, debug=False): 56 | """Populate Munin Plugin with MuninGraph instances. 57 | 58 | @param argv: List of command line arguments. 59 | @param env: Dictionary of environment variables. 60 | @param debug: Print debugging messages if True. (Default: False) 61 | 62 | """ 63 | MuninPlugin.__init__(self, argv, env, debug) 64 | self._category = 'Time' 65 | 66 | if self.graphEnabled('ntp_peer_stratum'): 67 | graph = MuninGraph('NTP Stratum for System Peer', self._category, 68 | info='Stratum of the NTP Server the system is in sync with.', 69 | args='--base 1000 --lower-limit 0') 70 | graph.addField('stratum', 'stratum', type='GAUGE', draw='LINE2') 71 | self.appendGraph('ntp_peer_stratum', graph) 72 | 73 | if self.graphEnabled('ntp_peer_stats'): 74 | graph = MuninGraph('NTP Timing Stats for System Peer', self._category, 75 | info='Timing Stats for the NTP Server the system is in sync with.', 76 | args='--base 1000 --lower-limit 0', 77 | vlabel='seconds' 78 | ) 79 | graph.addField('offset', 'offset', type='GAUGE', draw='LINE2') 80 | graph.addField('delay', 'delay', type='GAUGE', draw='LINE2') 81 | graph.addField('jitter', 'jitter', type='GAUGE', draw='LINE2') 82 | self.appendGraph('ntp_peer_stats', graph) 83 | 84 | def retrieveVals(self): 85 | """Retrieve values for graphs.""" 86 | ntpinfo = NTPinfo() 87 | stats = ntpinfo.getPeerStats() 88 | if stats: 89 | if self.hasGraph('ntp_peer_stratum'): 90 | self.setGraphVal('ntp_peer_stratum', 'stratum', 91 | stats.get('stratum')) 92 | if self.hasGraph('ntp_peer_stats'): 93 | self.setGraphVal('ntp_peer_stats', 'offset', 94 | stats.get('offset')) 95 | self.setGraphVal('ntp_peer_stats', 'delay', 96 | stats.get('delay')) 97 | self.setGraphVal('ntp_peer_stats', 'jitter', 98 | stats.get('jitter')) 99 | 100 | def autoconf(self): 101 | """Implements Munin Plugin Auto-Configuration Option. 102 | 103 | @return: True if plugin can be auto-configured, False otherwise. 104 | 105 | """ 106 | ntpinfo = NTPinfo() 107 | stats = ntpinfo.getPeerStats() 108 | return len(stats) > 0 109 | 110 | 111 | def main(): 112 | sys.exit(muninMain(MuninNTPstatsPlugin)) 113 | 114 | 115 | if __name__ == "__main__": 116 | main() 117 | -------------------------------------------------------------------------------- /pysysinfo/filesystem.py: -------------------------------------------------------------------------------- 1 | """Implements FilesystemInfo Class for gathering disk usage stats. 2 | 3 | """ 4 | 5 | import subprocess 6 | 7 | __author__ = "Ali Onur Uyar" 8 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 9 | __credits__ = [] 10 | __license__ = "GPL" 11 | __version__ = "0.9.23" 12 | __maintainer__ = "Ali Onur Uyar" 13 | __email__ = "aouyar at gmail.com" 14 | __status__ = "Development" 15 | 16 | 17 | # Defaults 18 | dfCmd = '/bin/df' 19 | mountsFile = '/proc/mounts' 20 | 21 | 22 | 23 | class FilesystemInfo: 24 | """Class to retrieve stats for disk utilization.""" 25 | 26 | def __init__(self): 27 | """Read /proc/mounts to get filesystem types. 28 | 29 | """ 30 | self._fstypeDict = {} 31 | self._fs2devDict = {} 32 | try: 33 | fp = open(mountsFile, 'r') 34 | data = fp.read() 35 | fp.close() 36 | except: 37 | raise IOError('Reading of file %s failed.' % mountsFile) 38 | for line in data.splitlines(): 39 | cols = line.split() 40 | self._fstypeDict[cols[1]] = cols[2] 41 | self._fs2devDict[cols[1]] = cols[0] 42 | 43 | def getFSlist(self): 44 | """Returns list of filesystems. 45 | 46 | @return: List of filesystems. 47 | 48 | """ 49 | return self._fstypeDict.keys() 50 | 51 | def getFStype(self, fs): 52 | """Return the type of the filesystem fs. 53 | 54 | @return: Filesystem type. 55 | 56 | """ 57 | return self._fstypeDict.get(fs) 58 | 59 | def getFSdev(self, fs): 60 | """Return the device path forfilesystem fs. 61 | 62 | @return: Device path. 63 | 64 | """ 65 | return self._fs2devDict.get(fs) 66 | 67 | def getSpaceUse(self): 68 | """Get disk space usage. 69 | 70 | @return: Dictionary of filesystem space utilization stats for filesystems. 71 | 72 | """ 73 | stats = {} 74 | try: 75 | out = subprocess.Popen([dfCmd, "-Pk"], 76 | stdout=subprocess.PIPE).communicate()[0] 77 | except: 78 | raise Exception('Execution of command %s failed.' % dfCmd) 79 | lines = out.splitlines() 80 | if len(lines) > 1: 81 | for line in lines[1:]: 82 | fsstats = {} 83 | cols = line.split() 84 | fsstats['device'] = cols[0] 85 | fsstats['type'] = self._fstypeDict[cols[5]] 86 | fsstats['total'] = 1024 * int(cols[1]) 87 | fsstats['inuse'] = 1024 * int(cols[2]) 88 | fsstats['avail'] = 1024 * int(cols[3]) 89 | fsstats['inuse_pcent'] = int(cols[4][:-1]) 90 | stats[cols[5]] = fsstats 91 | return stats 92 | 93 | def getInodeUse(self): 94 | """Get disk space usage. 95 | 96 | @return: Dictionary of filesystem inode utilization stats for filesystems. 97 | 98 | """ 99 | stats = {} 100 | try: 101 | out = subprocess.Popen([dfCmd, "-i", "-Pk"], 102 | stdout=subprocess.PIPE).communicate()[0] 103 | except: 104 | raise Exception('Execution of command %s failed.' % dfCmd) 105 | lines = out.splitlines() 106 | if len(lines) > 1: 107 | for line in lines[1:]: 108 | fsstats = {} 109 | cols = line.split() 110 | try: 111 | pcent = int(cols[4][:-1]) 112 | except: 113 | pcent = None 114 | if pcent is not None: 115 | fsstats['device'] = cols[0] 116 | fsstats['type'] = self._fstypeDict[cols[5]] 117 | fsstats['total'] = int(cols[1]) 118 | fsstats['inuse'] = int(cols[2]) 119 | fsstats['avail'] = int(cols[3]) 120 | fsstats['inuse_pcent'] = pcent 121 | stats[cols[5]] = fsstats 122 | return stats 123 | -------------------------------------------------------------------------------- /pysysinfo/netiface.py: -------------------------------------------------------------------------------- 1 | """Implements IfaceInfo Class for gathering stats from Network Interfaces. 2 | 3 | """ 4 | 5 | import re 6 | import subprocess 7 | 8 | __author__ = "Ali Onur Uyar" 9 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 10 | __credits__ = [] 11 | __license__ = "GPL" 12 | __version__ = "0.9" 13 | __maintainer__ = "Ali Onur Uyar" 14 | __email__ = "aouyar at gmail.com" 15 | __status__ = "Development" 16 | 17 | 18 | # Defaults 19 | ifaceStatsFile = '/proc/net/dev' 20 | ipCmd = '/sbin/ip' 21 | routeCmd = '/sbin/route' 22 | 23 | 24 | class NetIfaceInfo: 25 | """Class to retrieve stats for Network Interfaces.""" 26 | 27 | def getIfStats(self): 28 | """Return dictionary of Traffic Stats for Network Interfaces. 29 | 30 | @return: Nested dictionary of statistics for each interface. 31 | 32 | """ 33 | info_dict = {} 34 | try: 35 | fp = open(ifaceStatsFile, 'r') 36 | data = fp.read() 37 | fp.close() 38 | except: 39 | raise IOError('Failed reading interface stats from file: %s' 40 | % ifaceStatsFile) 41 | for line in data.splitlines(): 42 | mobj = re.match('^\s*([\w\d:]+):\s*(.*\S)\s*$', line) 43 | if mobj: 44 | iface = mobj.group(1) 45 | statline = mobj.group(2) 46 | info_dict[iface] = dict(zip( 47 | ('rxbytes', 'rxpackets', 'rxerrs', 'rxdrop', 'rxfifo', 48 | 'rxframe', 'rxcompressed', 'rxmulticast', 49 | 'txbytes', 'txpackets', 'txerrs', 'txdrop', 'txfifo', 50 | 'txcolls', 'txcarrier', 'txcompressed'), 51 | [int(x) for x in statline.split()])) 52 | 53 | return info_dict 54 | 55 | def getIfConfig(self): 56 | """Return dictionary of Interface Configuration (ifconfig). 57 | 58 | @return: Dictionary of if configurations keyed by if name. 59 | 60 | """ 61 | conf = {} 62 | try: 63 | out = subprocess.Popen([ipCmd, "addr", "show"], 64 | stdout=subprocess.PIPE).communicate()[0] 65 | except: 66 | raise Exception('Execution of command %s failed.' % ipCmd) 67 | for line in out.splitlines(): 68 | mobj = re.match('^\d+: (\S+):\s+<(\S*)>\s+(\S.*\S)\s*$', line) 69 | if mobj: 70 | iface = mobj.group(1) 71 | conf[iface] = {} 72 | continue 73 | mobj = re.match('^\s{4}link\/(.*\S)\s*$', line) 74 | if mobj: 75 | arr = mobj.group(1).split() 76 | if len(arr) > 0: 77 | conf[iface]['type'] = arr[0] 78 | if len(arr) > 1: 79 | conf[iface]['hwaddr'] = arr[1] 80 | continue 81 | mobj = re.match('^\s+(inet|inet6)\s+([\d\.\:A-Za-z]+)\/(\d+)($|\s+.*\S)\s*$', line) 82 | if mobj: 83 | proto = mobj.group(1) 84 | if not conf[iface].has_key(proto): 85 | conf[iface][proto] = [] 86 | addrinfo = {} 87 | addrinfo['addr'] = mobj.group(2).lower() 88 | addrinfo['mask'] = int(mobj.group(3)) 89 | arr = mobj.group(4).split() 90 | if len(arr) > 0 and arr[0] == 'brd': 91 | addrinfo['brd'] = arr[1] 92 | conf[iface][proto].append(addrinfo) 93 | continue 94 | return conf 95 | 96 | def getRoutes(self): 97 | """Get routing table. 98 | 99 | @return: List of routes. 100 | 101 | """ 102 | routes = [] 103 | try: 104 | out = subprocess.Popen([routeCmd, "-n"], 105 | stdout=subprocess.PIPE).communicate()[0] 106 | except: 107 | raise Exception('Execution of command %s failed.' % ipCmd) 108 | lines = out.splitlines() 109 | if len(lines) > 1: 110 | headers = [col.lower() for col in lines[1].split()] 111 | for line in lines[2:]: 112 | routes.append(dict(zip(headers, line.split()))) 113 | return routes 114 | -------------------------------------------------------------------------------- /pysysinfo/phpapc.py: -------------------------------------------------------------------------------- 1 | """Implements APCinfo Class for gathering stats from Alternative PHP Accelerator. 2 | 3 | The statistics are obtained through a request to custom apcinfo.php script 4 | that must be placed in the Web Server Document Root Directory. 5 | 6 | """ 7 | 8 | import util 9 | 10 | __author__ = "Ali Onur Uyar" 11 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 12 | __credits__ = [] 13 | __license__ = "GPL" 14 | __version__ = "0.9.23" 15 | __maintainer__ = "Ali Onur Uyar" 16 | __email__ = "aouyar at gmail.com" 17 | __status__ = "Development" 18 | 19 | 20 | defaultHTTPport = 80 21 | defaultHTTPSport = 443 22 | 23 | 24 | class APCinfo: 25 | """Class to retrieve stats from APC from Web Server.""" 26 | 27 | def __init__(self, host=None, port=None, user=None, password=None, 28 | monpath=None, ssl=False, extras=False, autoInit=True): 29 | """Initialize URL for APC stats access. 30 | 31 | @param host: Web Server Host. (Default: 127.0.0.1) 32 | @param port: Web Server Port. (Default: 80, SSL: 443) 33 | @param user: Username. (Not needed unless authentication is required 34 | to access status page. 35 | @param password: Password. (Not needed unless authentication is required 36 | to access status page. 37 | @param monpath: APC status script path relative to Document Root. 38 | (Default: apcinfo.php) 39 | @param ssl: Use SSL if True. (Default: False) 40 | @param extras: Include extra metrics, which can be computationally more 41 | expensive. 42 | @param autoInit: If True connect to Web Server on instantiation. 43 | 44 | """ 45 | if host is not None: 46 | self._host = host 47 | else: 48 | self._host = '127.0.0.1' 49 | if port is not None: 50 | self._port = int(port) 51 | else: 52 | if ssl: 53 | self._port = defaultHTTPSport 54 | else: 55 | self._port = defaultHTTPport 56 | self._user = user 57 | self._password = password 58 | if ssl: 59 | self._proto = 'https' 60 | else: 61 | self._proto = 'http' 62 | if monpath: 63 | self._monpath = monpath 64 | else: 65 | self._monpath = 'apcinfo.php' 66 | self._extras = extras 67 | self._statusDict = None 68 | if autoInit: 69 | self.initStats() 70 | 71 | def initStats(self, extras=None): 72 | """Query and parse Web Server Status Page. 73 | 74 | @param extras: Include extra metrics, which can be computationally more 75 | expensive. 76 | 77 | """ 78 | if extras is not None: 79 | self._extras = extras 80 | if self._extras: 81 | detail = 1 82 | else: 83 | detail = 0 84 | url = "%s://%s:%d/%s?detail=%s" % (self._proto, self._host, self._port, 85 | self._monpath, detail) 86 | response = util.get_url(url, self._user, self._password) 87 | self._statusDict = {} 88 | for line in response.splitlines(): 89 | cols = line.split(':') 90 | if not self._statusDict.has_key(cols[0]): 91 | self._statusDict[cols[0]] = {} 92 | self._statusDict[cols[0]][cols[1]] = util.parse_value(cols[2]) 93 | 94 | def getMemoryStats(self): 95 | """Return Memory Utilization Stats for APC. 96 | 97 | @return: Dictionary of stats. 98 | 99 | """ 100 | return self._statusDict.get('memory'); 101 | 102 | def getSysCacheStats(self): 103 | """Return System Cache Stats for APC. 104 | 105 | @return: Dictionary of stats. 106 | 107 | """ 108 | return self._statusDict.get('cache_sys'); 109 | 110 | def getUserCacheStats(self): 111 | """Return User Cache Stats for APC. 112 | 113 | @return: Dictionary of stats. 114 | 115 | """ 116 | return self._statusDict.get('cache_user'); 117 | 118 | def getAllStats(self): 119 | """Return All Stats for APC. 120 | 121 | @return: Nested dictionary of stats. 122 | 123 | """ 124 | return self._statusDict; 125 | -------------------------------------------------------------------------------- /pymunin/plugins/fsstats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """fsstats - Munin Plugin to monitor FreeSWITCH through the Event Socket 3 | Interface. 4 | 5 | Requirements 6 | 7 | - Access to FreeSWITCH Event Socket Interface 8 | 9 | Wild Card Plugin - No 10 | 11 | Multigraph Plugin - Graph Structure 12 | 13 | - fs_calls 14 | - fs_channels 15 | 16 | 17 | Environment Variables 18 | 19 | fshost: FreeSWITCH Server (Default: 127.0.0.1) 20 | fsport: FreeSWITCH Event Socket Port (Default: 8021) 21 | fspass: FreeSWITCH Event Socket Password 22 | include_graphs: Comma separated list of enabled graphs. 23 | (All graphs enabled by default.) 24 | exclude_graphs: Comma separated list of disabled graphs. 25 | 26 | Environment Variables for Multiple Instances of Plugin (Omitted by default.) 27 | 28 | instance_name: Name of instance. 29 | instance_label: Graph title label for instance. 30 | (Default is the same as instance name.) 31 | instance_label_format: One of the following values: 32 | - suffix (Default) 33 | - prefix 34 | - none 35 | 36 | Example: 37 | [fsstats] 38 | env.fshost 192.168.1.10 39 | env.fsport 8021 40 | env.fspass secret 41 | 42 | """ 43 | # Munin - Magic Markers 44 | #%# family=auto 45 | #%# capabilities=autoconf nosuggest 46 | 47 | import sys 48 | from pymunin import MuninGraph, MuninPlugin, muninMain 49 | from pysysinfo.freeswitch import FSinfo 50 | 51 | __author__ = "Ali Onur Uyar" 52 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 53 | __credits__ = [] 54 | __license__ = "GPL" 55 | __version__ = "0.9.20" 56 | __maintainer__ = "Ali Onur Uyar" 57 | __email__ = "aouyar at gmail.com" 58 | __status__ = "Development" 59 | 60 | 61 | class MuninFreeswitchPlugin(MuninPlugin): 62 | """Multigraph Munin Plugin for monitoring FreeSWITCH. 63 | 64 | """ 65 | plugin_name = 'fsstats' 66 | isMultigraph = True 67 | isMultiInstance = True 68 | 69 | def __init__(self, argv=(), env=None, debug=False): 70 | """Populate Munin Plugin with MuninGraph instances. 71 | 72 | @param argv: List of command line arguments. 73 | @param env: Dictionary of environment variables. 74 | @param debug: Print debugging messages if True. (Default: False) 75 | 76 | """ 77 | MuninPlugin.__init__(self, argv, env, debug) 78 | 79 | self._fshost = self.envGet('fshost') 80 | self._fsport = self.envGet('fsport', None, int) 81 | self._fspass = self.envGet('fspass') 82 | self._category = 'FreeSwitch' 83 | 84 | if self.graphEnabled('fs_calls'): 85 | graph = MuninGraph('FreeSWITCH - Active Calls', self._category, 86 | info = 'FreeSWITCH - Number of Active Calls.', 87 | args = '--base 1000 --lower-limit 0') 88 | graph.addField('calls', 'calls', type='GAUGE', 89 | draw='LINE2',info='Active Calls') 90 | self.appendGraph('fs_calls', graph) 91 | 92 | if self.graphEnabled('fs_channels'): 93 | graph = MuninGraph('FreeSWITCH - Active Channels', 'FreeSWITCH', 94 | info = 'FreeSWITCH - Number of Active Channels.', 95 | args = '--base 1000 --lower-limit 0') 96 | graph.addField('channels', 'channels', type='GAUGE', 97 | draw='LINE2') 98 | self.appendGraph('fs_channels', graph) 99 | 100 | def retrieveVals(self): 101 | """Retrieve values for graphs.""" 102 | fs = FSinfo(self._fshost, self._fsport, self._fspass) 103 | if self.hasGraph('fs_calls'): 104 | count = fs.getCallCount() 105 | self.setGraphVal('fs_calls', 'calls', count) 106 | if self.hasGraph('fs_channels'): 107 | count = fs.getChannelCount() 108 | self.setGraphVal('fs_channels', 'channels', count) 109 | 110 | def autoconf(self): 111 | """Implements Munin Plugin Auto-Configuration Option. 112 | 113 | @return: True if plugin can be auto-configured, False otherwise. 114 | 115 | """ 116 | fs = FSinfo(self._fshost, self._fsport, self._fspass) 117 | return fs is not None 118 | 119 | 120 | def main(): 121 | sys.exit(muninMain(MuninFreeswitchPlugin)) 122 | 123 | 124 | if __name__ == "__main__": 125 | main() 126 | -------------------------------------------------------------------------------- /pysysinfo/freeswitch.py: -------------------------------------------------------------------------------- 1 | """Implements FSinfo Class for gathering stats from the FreeSWITCH ESL Interface. 2 | 3 | """ 4 | 5 | import re 6 | import ESL 7 | 8 | __author__ = "Ali Onur Uyar" 9 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 10 | __credits__ = [] 11 | __license__ = "GPL" 12 | __version__ = "0.5" 13 | __maintainer__ = "Ali Onur Uyar" 14 | __email__ = "aouyar at gmail.com" 15 | __status__ = "Development" 16 | 17 | 18 | # 19 | # DEFAULTS 20 | # 21 | 22 | defaultESLport = 8021 23 | defaultESLsecret = 'ClueCon' 24 | conn_timeout = 5 25 | 26 | 27 | 28 | class FSinfo: 29 | """Class that establishes connection to FreeSWITCH ESL Interface 30 | to retrieve statistics on operation. 31 | 32 | """ 33 | 34 | def __init__(self, host='127.0.0.1', port=defaultESLport, secret="ClueCon", 35 | autoInit=True): 36 | """Initialize connection to FreeSWITCH ESL Interface. 37 | 38 | @param host: FreeSWITCH Host 39 | @param port: FreeSWITCH ESL Port 40 | @param secret: FreeSWITCH ESL Secret 41 | @param autoInit: If True connect to FreeSWITCH ESL Interface on 42 | instantiation. 43 | 44 | """ 45 | # Set Connection Parameters 46 | self._eslconn = None 47 | self._eslhost = host or '127.0.0.1' 48 | self._eslport = int(port or defaultESLport) 49 | self._eslpass = secret or defaultESLsecret 50 | 51 | ESL.eslSetLogLevel(0) 52 | if autoInit: 53 | self._connect() 54 | 55 | def __del__(self): 56 | """Cleanup.""" 57 | if self._eslconn is not None: 58 | del self._eslconn 59 | 60 | def _connect(self): 61 | """Connect to FreeSWITCH ESL Interface.""" 62 | try: 63 | self._eslconn = ESL.ESLconnection(self._eslhost, 64 | str(self._eslport), 65 | self._eslpass) 66 | except: 67 | pass 68 | if not self._eslconn.connected(): 69 | raise Exception( 70 | "Connection to FreeSWITCH ESL Interface on host %s and port %d failed." 71 | % (self._eslhost, self._eslport) 72 | ) 73 | 74 | def _execCmd(self, cmd, args): 75 | """Execute command and return result body as list of lines. 76 | 77 | @param cmd: Command string. 78 | @param args: Comand arguments string. 79 | @return: Result dictionary. 80 | 81 | """ 82 | output = self._eslconn.api(cmd, args) 83 | if output: 84 | body = output.getBody() 85 | if body: 86 | return body.splitlines() 87 | return None 88 | 89 | def _execShowCmd(self, showcmd): 90 | """Execute 'show' command and return result dictionary. 91 | 92 | @param cmd: Command string. 93 | @return: Result dictionary. 94 | 95 | """ 96 | result = None 97 | lines = self._execCmd("show", showcmd) 98 | if lines and len(lines) >= 2 and lines[0] != '' and lines[0][0] != '-': 99 | result = {} 100 | result['keys'] = lines[0].split(',') 101 | items = [] 102 | for line in lines[1:]: 103 | if line == '': 104 | break 105 | items.append(line.split(',')) 106 | result['items'] = items 107 | return result 108 | 109 | def _execShowCountCmd(self, showcmd): 110 | """Execute 'show' command and return result dictionary. 111 | 112 | @param cmd: Command string. 113 | @return: Result dictionary. 114 | 115 | """ 116 | result = None 117 | lines = self._execCmd("show", showcmd + " count") 118 | for line in lines: 119 | mobj = re.match('\s*(\d+)\s+total', line) 120 | if mobj: 121 | return int(mobj.group(1)) 122 | return result 123 | 124 | def getChannelCount(self): 125 | """Get number of active channels from FreeSWITCH. 126 | 127 | @return: Integer or None. 128 | 129 | """ 130 | return self._execShowCountCmd("channels") 131 | 132 | def getCallCount(self): 133 | """Get number of active calls from FreeSWITCH. 134 | 135 | @return: Integer or None. 136 | 137 | """ 138 | return self._execShowCountCmd("calls") 139 | -------------------------------------------------------------------------------- /pymunin/plugins/ntphostoffset_.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ntphostoffset_ - Munin Plugin to monitor time offset of remote host using NTP. 3 | 4 | 5 | Requirements 6 | 7 | - Requires ntpd running on remote host and access to NTP on remote host. 8 | - Requires ntpdate utility on local host. 9 | 10 | Wild Card Plugin 11 | 12 | Symlink indicates IP of remote host to be monitored: 13 | Ex: ntphostoffset_192.168.1.1 -> /usr/shar/munin/plugins/ntphostoffset_ 14 | 15 | 16 | Multigraph Plugin - Graph Structure 17 | 18 | - ntp_host_stratum 19 | - ntp_host_stat 20 | 21 | 22 | Environment Variables 23 | 24 | include_graphs: Comma separated list of enabled graphs. 25 | (All graphs enabled by default.) 26 | exclude_graphs: Comma separated list of disabled graphs. 27 | 28 | Environment Variables for Multiple Instances of Plugin (Omitted by default.) 29 | instance_name: Name of instance. 30 | instance_label: Graph title label for instance. 31 | (Default is the same as instance name.) 32 | instance_label_format: One of the following values: 33 | - suffix (Default) 34 | - prefix 35 | - none 36 | 37 | Example: 38 | [ntphostoffset_*] 39 | env.exclude_graphs ntp_host_stratum_ 40 | 41 | """ 42 | # Munin - Magic Markers 43 | #%# family=manual 44 | #%# capabilities=noautoconf nosuggest 45 | 46 | import sys 47 | from pymunin import MuninGraph, MuninPlugin, muninMain 48 | from pysysinfo.ntp import NTPinfo 49 | 50 | __author__ = "Ali Onur Uyar" 51 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 52 | __credits__ = [] 53 | __license__ = "GPL" 54 | __version__ = "0.9.20" 55 | __maintainer__ = "Ali Onur Uyar" 56 | __email__ = "aouyar at gmail.com" 57 | __status__ = "Development" 58 | 59 | 60 | class MuninNTPhostOffsetPlugin(MuninPlugin): 61 | """Multigraph Munin Plugin for monitoring time offset of remote host using NTP. 62 | 63 | """ 64 | plugin_name = 'ntphostoffset_' 65 | isMultigraph = True 66 | isMultiInstance = True 67 | 68 | def __init__(self, argv=(), env=None, debug=False): 69 | """Populate Munin Plugin with MuninGraph instances. 70 | 71 | @param argv: List of command line arguments. 72 | @param env: Dictionary of environment variables. 73 | @param debug: Print debugging messages if True. (Default: False) 74 | 75 | """ 76 | MuninPlugin.__init__(self, argv, env, debug) 77 | self._category = 'Time' 78 | 79 | if self.arg0 is None: 80 | raise Exception("Remote host name cannot be determined.") 81 | else: 82 | self._remoteHost = self.arg0 83 | 84 | if self.graphEnabled('ntp_host_stratum'): 85 | graphName = 'ntp_host_stratum_%s' % self._remoteHost 86 | graph = MuninGraph('NTP Stratum of Host %s' % self._remoteHost, 87 | self._category, 88 | info='NTP Stratum of Host %s.' % self._remoteHost, 89 | args='--base 1000 --lower-limit 0') 90 | graph.addField('stratum', 'stratum', type='GAUGE', draw='LINE2') 91 | self.appendGraph(graphName, graph) 92 | 93 | if self.graphEnabled('ntp_host_stat'): 94 | graphName = 'ntp_host_stat_%s' % self._remoteHost 95 | graph = MuninGraph('NTP Offset of Host %s' % self._remoteHost, self._category, 96 | info=('NTP Offset of Host %s relative to current node.' 97 | % self._remoteHost), 98 | args='--base 1000 --lower-limit 0', 99 | vlabel='seconds' 100 | ) 101 | graph.addField('offset', 'offset', type='GAUGE', draw='LINE2') 102 | graph.addField('delay', 'delay', type='GAUGE', draw='LINE2') 103 | self.appendGraph(graphName, graph) 104 | 105 | def retrieveVals(self): 106 | """Retrieve values for graphs.""" 107 | ntpinfo = NTPinfo() 108 | stats = ntpinfo.getHostOffset(self._remoteHost) 109 | if stats: 110 | graph_name = 'ntp_host_stratum_%s' % self._remoteHost 111 | if self.hasGraph(graph_name): 112 | self.setGraphVal(graph_name, 'stratum', stats.get('stratum')) 113 | graph_name = 'ntp_host_stat_%s' % self._remoteHost 114 | if self.hasGraph(graph_name): 115 | self.setGraphVal(graph_name, 'offset', stats.get('offset')) 116 | self.setGraphVal(graph_name, 'delay', stats.get('delay')) 117 | 118 | 119 | def main(): 120 | sys.exit(muninMain(MuninNTPhostOffsetPlugin)) 121 | 122 | 123 | if __name__ == "__main__": 124 | main() 125 | -------------------------------------------------------------------------------- /pysysinfo/tomcat.py: -------------------------------------------------------------------------------- 1 | """Implements TomcatInfo Class for gathering stats from Apache Tomcat Server. 2 | 3 | The statistics are obtained by connecting to and querying local and/or 4 | remote Apache Tomcat Servers. 5 | 6 | """ 7 | 8 | import sys 9 | import re 10 | import util 11 | 12 | __author__ = "Ali Onur Uyar" 13 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 14 | __credits__ = [] 15 | __license__ = "GPL" 16 | __version__ = "0.9.18" 17 | __maintainer__ = "Ali Onur Uyar" 18 | __email__ = "aouyar at gmail.com" 19 | __status__ = "Development" 20 | 21 | 22 | if sys.version_info[:2] < (2,5): 23 | from elementtree import ElementTree #@UnresolvedImport @UnusedImport 24 | else: 25 | from xml.etree import ElementTree #@Reimport 26 | 27 | defaultTomcatPort = 8080 28 | defaultTomcatSSLport = 8443 29 | 30 | 31 | class TomcatInfo: 32 | """Class to retrieve stats for Apache Tomcat Application Server.""" 33 | 34 | def __init__(self, host=None, port=None, user=None, password=None, 35 | ssl=False, autoInit=True): 36 | """Initialize Apache Tomcat Manager access. 37 | 38 | @param host: Apache Tomcat Host. (Default: 127.0.0.1) 39 | @param port: Apache Tomcat Port. (Default: 8080, SSL: 8443) 40 | @param user: Apache Tomcat Manager User. 41 | @param password: Apache Tomcat Manager Password. 42 | @param ssl: Use SSL if True. (Default: False) 43 | @param autoInit: If True connect to Apache Tomcat Server on instantiation. 44 | 45 | """ 46 | if host is not None: 47 | self._host = host 48 | else: 49 | self._host = '127.0.0.1' 50 | if port is not None: 51 | self._port = int(port) 52 | else: 53 | if ssl: 54 | self._port = defaultTomcatSSLport 55 | else: 56 | self._port = defaultTomcatPort 57 | self._user = user 58 | self._password = password 59 | if ssl: 60 | self._proto = 'https' 61 | else: 62 | self._proto = 'http' 63 | self._statusxml = None 64 | if autoInit: 65 | self.initStats() 66 | 67 | def _retrieve(self): 68 | """Query Apache Tomcat Server Status Page in XML format and return 69 | the result as an ElementTree object. 70 | 71 | @return: ElementTree object of Status Page XML. 72 | 73 | """ 74 | url = "%s://%s:%d/manager/status" % (self._proto, self._host, self._port) 75 | params = {} 76 | params['XML'] = 'true' 77 | response = util.get_url(url, self._user, self._password, params) 78 | tree = ElementTree.XML(response) 79 | return tree 80 | 81 | def initStats(self): 82 | """Query Apache Tomcat Server Status Page to initialize statistics.""" 83 | self._statusxml = self._retrieve() 84 | 85 | def getMemoryStats(self): 86 | """Return JVM Memory Stats for Apache Tomcat Server. 87 | 88 | @return: Dictionary of memory utilization stats. 89 | 90 | """ 91 | if self._statusxml is None: 92 | self.initStats() 93 | node = self._statusxml.find('jvm/memory') 94 | memstats = {} 95 | if node is not None: 96 | for (key,val) in node.items(): 97 | memstats[key] = util.parse_value(val) 98 | return memstats 99 | 100 | def getConnectorStats(self): 101 | """Return dictionary of Connector Stats for Apache Tomcat Server. 102 | 103 | @return: Nested dictionary of Connector Stats. 104 | 105 | """ 106 | if self._statusxml is None: 107 | self.initStats() 108 | connnodes = self._statusxml.findall('connector') 109 | connstats = {} 110 | if connnodes: 111 | for connnode in connnodes: 112 | namestr = connnode.get('name') 113 | if namestr is not None: 114 | mobj = re.match('(.*)-(\d+)', namestr) 115 | if mobj: 116 | proto = mobj.group(1) 117 | port = int(mobj.group(2)) 118 | connstats[port] = {'proto': proto} 119 | for tag in ('threadInfo', 'requestInfo'): 120 | stats = {} 121 | node = connnode.find(tag) 122 | if node is not None: 123 | for (key,val) in node.items(): 124 | if re.search('Time$', key): 125 | stats[key] = float(val) / 1000.0 126 | else: 127 | stats[key] = util.parse_value(val) 128 | if stats: 129 | connstats[port][tag] = stats 130 | return connstats 131 | 132 | -------------------------------------------------------------------------------- /pymunin/plugins/phpfpmstats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """phpfpmstats - Munin Plugin for monitoring PHP FPM (Fast Process Manager). 3 | 4 | 5 | Requirements 6 | 7 | - The PHP FPM status page must be configured and it must have access 8 | permissions from localhost. 9 | 10 | 11 | Wild Card Plugin - No 12 | 13 | 14 | Multigraph Plugin - Graph Structure 15 | 16 | - php_fpm_connections 17 | - php_fpm_processes 18 | 19 | Environment Variables 20 | 21 | host: Web Server Host. (Default: 127.0.0.1) 22 | port: Web Server Port. (Default: 80, SSL: 443) 23 | user: User in case authentication is required for access to 24 | FPM Status page. 25 | password: Password in case authentication is required for access to 26 | FPM Status page. 27 | monpath: FPM status page path relative to Document Root. 28 | (Default: fpm_status.php) 29 | ssl: Use SSL if yes. (Default: no) 30 | include_graphs: Comma separated list of enabled graphs. 31 | (All graphs enabled by default.) 32 | exclude_graphs: Comma separated list of disabled graphs. 33 | 34 | Environment Variables for Multiple Instances of Plugin (Omitted by default.) 35 | 36 | instance_name: Name of instance. 37 | instance_label: Graph title label for instance. 38 | (Default is the same as instance name.) 39 | instance_label_format: One of the following values: 40 | - suffix (Default) 41 | - prefix 42 | - none 43 | 44 | Example: 45 | [phpfpmstats] 46 | env.exclude_graphs php_fpm_processes 47 | 48 | """ 49 | # Munin - Magic Markers 50 | #%# family=auto 51 | #%# capabilities=autoconf nosuggest 52 | 53 | import sys 54 | from pymunin import MuninGraph, MuninPlugin, muninMain 55 | from pysysinfo.phpfpm import PHPfpmInfo 56 | 57 | __author__ = "Ali Onur Uyar" 58 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 59 | __credits__ = [] 60 | __license__ = "GPL" 61 | __version__ = "0.9.20" 62 | __maintainer__ = "Ali Onur Uyar" 63 | __email__ = "aouyar at gmail.com" 64 | __status__ = "Development" 65 | 66 | 67 | class MuninPHPfpmPlugin(MuninPlugin): 68 | """Multigraph Munin Plugin for monitoring PHP Fast Process Manager (FPM). 69 | 70 | """ 71 | plugin_name = 'phpfpmstats' 72 | isMultigraph = True 73 | isMultiInstance = True 74 | 75 | def __init__(self, argv=(), env=None, debug=False): 76 | """Populate Munin Plugin with MuninGraph instances. 77 | 78 | @param argv: List of command line arguments. 79 | @param env: Dictionary of environment variables. 80 | @param debug: Print debugging messages if True. (Default: False) 81 | 82 | """ 83 | MuninPlugin.__init__(self, argv, env, debug) 84 | 85 | self._host = self.envGet('host') 86 | self._port = self.envGet('port', None, int) 87 | self._user = self.envGet('user') 88 | self._monpath = self.envGet('monpath') 89 | self._password = self.envGet('password') 90 | self._ssl = self.envCheckFlag('ssl', False) 91 | self._category = 'PHP' 92 | 93 | if self.graphEnabled('php_fpm_connections'): 94 | graph = MuninGraph('PHP FPM - Connections per second', self._category, 95 | info='PHP Fast Process Manager (FPM) - Connections per second.', 96 | args='--base 1000 --lower-limit 0') 97 | graph.addField('conn', 'conn', draw='LINE2', type='DERIVE', min=0) 98 | self.appendGraph('php_fpm_connections', graph) 99 | 100 | if self.graphEnabled('php_fpm_processes'): 101 | graph = MuninGraph('PHP FPM - Processes', self._category, 102 | info='PHP Fast Process Manager (FPM) - Active / Idle Processes.', 103 | args='--base 1000 --lower-limit 0') 104 | graph.addField('active', 'active', draw='AREASTACK', type='GAUGE') 105 | graph.addField('idle', 'idle', draw='AREASTACK', type='GAUGE') 106 | graph.addField('total', 'total', draw='LINE2', type='GAUGE', 107 | colour='000000') 108 | self.appendGraph('php_fpm_processes', graph) 109 | 110 | 111 | def retrieveVals(self): 112 | """Retrieve values for graphs.""" 113 | fpminfo = PHPfpmInfo(self._host, self._port, self._user, self._password, 114 | self._monpath, self._ssl) 115 | stats = fpminfo.getStats() 116 | if self.hasGraph('php_fpm_connections') and stats: 117 | self.setGraphVal('php_fpm_connections', 'conn', 118 | stats['accepted conn']) 119 | if self.hasGraph('php_fpm_processes') and stats: 120 | self.setGraphVal('php_fpm_processes', 'active', 121 | stats['active processes']) 122 | self.setGraphVal('php_fpm_processes', 'idle', 123 | stats['idle processes']) 124 | self.setGraphVal('php_fpm_processes', 'total', 125 | stats['total processes']) 126 | 127 | def autoconf(self): 128 | """Implements Munin Plugin Auto-Configuration Option. 129 | 130 | @return: True if plugin can be auto-configured, False otherwise. 131 | 132 | """ 133 | fpminfo = PHPfpmInfo(self._host, self._port, self._user, self._password, 134 | self._monpath, self._ssl) 135 | return fpminfo is not None 136 | 137 | 138 | def main(): 139 | sys.exit(muninMain(MuninPHPfpmPlugin)) 140 | 141 | 142 | if __name__ == "__main__": 143 | main() 144 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import os 3 | import pkgutil 4 | import shutil 5 | import glob 6 | from setuptools import setup, find_packages 7 | from setuptools.command.install import install as _install 8 | import pymunin #@UnusedImport 9 | import pymunin.plugins 10 | 11 | 12 | PYMUNIN_SCRIPT_FILENAME_PREFIX = u'pymunin' 13 | PYMUNIN_PLUGIN_DIR = u'./share/munin/plugins' 14 | 15 | 16 | def read_file(filename): 17 | """Read a file into a string""" 18 | path = os.path.abspath(os.path.dirname(__file__)) 19 | filepath = os.path.join(path, filename) 20 | try: 21 | return open(filepath).read() 22 | except IOError: 23 | return '' 24 | 25 | if hasattr(pkgutil, "iter_modules"): # Python > 2.5 26 | modules = [modname for importer, modname, ispkg in 27 | pkgutil.iter_modules(pymunin.plugins.__path__)] 28 | else: 29 | modules = [] 30 | for path in glob.glob(os.path.join(pymunin.plugins.__path__[0], 31 | u'[A-Za-z]*.py')): 32 | file = os.path.basename(path) 33 | modules.append(file[:-3]) 34 | 35 | console_scripts = [] 36 | plugin_names = [] 37 | for modname in modules: 38 | params = { 39 | 'script_name': u'%s-%s' % (PYMUNIN_SCRIPT_FILENAME_PREFIX, modname), 40 | 'script_path': u'%s.%s' % (pymunin.plugins.__name__, modname), 41 | 'entry': 'main', 42 | } 43 | plugin_names.append(modname) 44 | console_scripts.append(u'%(script_name)s = %(script_path)s:%(entry)s' % params) 45 | 46 | 47 | class install(_install): 48 | "Extend base install class to provide a post-install step." 49 | 50 | def run(self): 51 | if os.environ.has_key('MUNIN_PLUGIN_DIR'): 52 | munin_plugin_dir = os.environ.get('MUNIN_PLUGIN_DIR') 53 | elif self.root is None: 54 | munin_plugin_dir = os.path.normpath( 55 | os.path.join(self.prefix, 56 | PYMUNIN_PLUGIN_DIR)) 57 | else: 58 | munin_plugin_dir = os.path.normpath( 59 | os.path.join(self.root, 60 | os.path.relpath(self.prefix, '/'), 61 | PYMUNIN_PLUGIN_DIR)) 62 | _install.run(self) 63 | # Installing the plugins requires write permission to plugins directory 64 | # (/usr/share/munin/plugins) which is default owned by root. 65 | print "Munin Plugin Directory: %s" % munin_plugin_dir 66 | if os.path.exists(munin_plugin_dir): 67 | try: 68 | for name in plugin_names: 69 | source = os.path.join( 70 | self.install_scripts, 71 | u'%s-%s' % (PYMUNIN_SCRIPT_FILENAME_PREFIX, name) 72 | ) 73 | destination = os.path.join(munin_plugin_dir, name) 74 | print "Installing %s to %s." % (name, munin_plugin_dir) 75 | shutil.copy(source, destination) 76 | except IOError, e: 77 | if e.errno in (errno.EACCES, errno.ENOENT): 78 | # Access denied or file/directory not found. 79 | print "*" * 78 80 | if e.errno == errno.EACCES: 81 | print ("You do not have permission to install the plugins to %s." 82 | % munin_plugin_dir) 83 | if e.errno == errno.ENOENT: 84 | print ("Failed installing the plugins to %s. " 85 | "File or directory not found." % munin_plugin_dir) 86 | script = os.path.join(self.install_scripts, 'pymunin-install') 87 | f = open(script, 'w') 88 | try: 89 | f.write('#!/bin/sh\n') 90 | for name in plugin_names: 91 | source = os.path.join( 92 | self.install_scripts, 93 | u'%s-%s' % (PYMUNIN_SCRIPT_FILENAME_PREFIX, name) 94 | ) 95 | destination = os.path.join(munin_plugin_dir, name) 96 | f.write('cp %s %s\n' % (source, destination)) 97 | finally: 98 | f.close() 99 | os.chmod(script, 0755) 100 | print ("You will need to copy manually using the script: %s\n" 101 | "Example: sudo %s" 102 | % (script, script)) 103 | print "*" * 78 104 | else: 105 | # Raise original exception 106 | raise 107 | 108 | 109 | setup( 110 | cmdclass={'install': install}, 111 | name='PyMunin', 112 | version=pymunin.__version__, 113 | author=pymunin.__author__, 114 | author_email=pymunin.__email__, 115 | maintainer=pymunin.__author__, 116 | maintainer_email=pymunin.__email__, 117 | packages=find_packages(), 118 | include_package_data=True, 119 | url='http://aouyar.github.com/PyMunin', 120 | license=pymunin.__license__, 121 | description=u'Python Module for developing Munin Multigraph Monitoring Plugins.', 122 | classifiers=[ 123 | 'Topic :: System :: Monitoring', 124 | 'Intended Audience :: Developers', 125 | 'License :: OSI Approved :: GNU General Public License (GPL)', 126 | 'Programming Language :: Python', 127 | 'Topic :: Software Development :: Libraries :: Python Modules', 128 | 'Development Status :: 4 - Beta', 129 | 'Operating System :: OS Independent', 130 | ], 131 | long_description=read_file('README.md'), 132 | entry_points={'console_scripts': console_scripts}, 133 | ) 134 | -------------------------------------------------------------------------------- /pysysinfo/system.py: -------------------------------------------------------------------------------- 1 | """Implements SystemInfo Class for gathering system stats. 2 | 3 | """ 4 | 5 | import re 6 | import os 7 | import platform 8 | 9 | __author__ = "Ali Onur Uyar" 10 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 11 | __credits__ = [] 12 | __license__ = "GPL" 13 | __version__ = "0.9" 14 | __maintainer__ = "Ali Onur Uyar" 15 | __email__ = "aouyar at gmail.com" 16 | __status__ = "Development" 17 | 18 | 19 | # Defaults 20 | uptimeFile = '/proc/uptime' 21 | loadavgFile = '/proc/loadavg' 22 | cpustatFile = '/proc/stat' 23 | meminfoFile = '/proc/meminfo' 24 | swapsFile = '/proc/swaps' 25 | vmstatFile = '/proc/vmstat' 26 | 27 | 28 | 29 | class SystemInfo: 30 | """Class to retrieve stats for system resources.""" 31 | 32 | def getPlatformInfo(self): 33 | """Get platform info. 34 | 35 | @return: Platform information in dictionary format. 36 | 37 | """ 38 | info = platform.uname() 39 | return { 40 | 'hostname': info[1], 41 | 'arch': info[4], 42 | 'os': info[0], 43 | 'osversion': info[2] 44 | } 45 | 46 | def getUptime(self): 47 | """Return system uptime in seconds. 48 | 49 | @return: Float that represents uptime in seconds. 50 | 51 | """ 52 | try: 53 | fp = open(uptimeFile, 'r') 54 | line = fp.readline() 55 | fp.close() 56 | except: 57 | raise IOError('Failed reading stats from file: %s' % uptimeFile) 58 | return float(line.split()[0]) 59 | 60 | def getLoadAvg(self): 61 | """Return system Load Average. 62 | 63 | @return: List of 1 min, 5 min and 15 min Load Average figures. 64 | 65 | """ 66 | try: 67 | fp = open(loadavgFile, 'r') 68 | line = fp.readline() 69 | fp.close() 70 | except: 71 | raise IOError('Failed reading stats from file: %s' % loadavgFile) 72 | arr = line.split() 73 | if len(arr) >= 3: 74 | return [float(col) for col in arr[:3]] 75 | else: 76 | return None 77 | 78 | def getCPUuse(self): 79 | """Return cpu time utilization in seconds. 80 | 81 | @return: Dictionary of stats. 82 | 83 | """ 84 | hz = os.sysconf('SC_CLK_TCK') 85 | info_dict = {} 86 | try: 87 | fp = open(cpustatFile, 'r') 88 | line = fp.readline() 89 | fp.close() 90 | except: 91 | raise IOError('Failed reading stats from file: %s' % cpustatFile) 92 | headers = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq', 'steal', 'guest'] 93 | arr = line.split() 94 | if len(arr) > 1 and arr[0] == 'cpu': 95 | return dict(zip(headers[0:len(arr)], [(float(t) / hz) for t in arr[1:]])) 96 | return info_dict 97 | 98 | def getProcessStats(self): 99 | """Return stats for running and blocked processes, forks, 100 | context switches and interrupts. 101 | 102 | @return: Dictionary of stats. 103 | 104 | """ 105 | info_dict = {} 106 | try: 107 | fp = open(cpustatFile, 'r') 108 | data = fp.read() 109 | fp.close() 110 | except: 111 | raise IOError('Failed reading stats from file: %s' % cpustatFile) 112 | for line in data.splitlines(): 113 | arr = line.split() 114 | if len(arr) > 1 and arr[0] in ('ctxt', 'intr', 'softirq', 115 | 'processes', 'procs_running', 116 | 'procs_blocked'): 117 | info_dict[arr[0]] = arr[1] 118 | return info_dict 119 | 120 | def getMemoryUse(self): 121 | """Return stats for memory utilization. 122 | 123 | @return: Dictionary of stats. 124 | 125 | """ 126 | info_dict = {} 127 | try: 128 | fp = open(meminfoFile, 'r') 129 | data = fp.read() 130 | fp.close() 131 | except: 132 | raise IOError('Failed reading stats from file: %s' % meminfoFile) 133 | for line in data.splitlines(): 134 | mobj = re.match('^(.+):\s*(\d+)\s*(\w+|)\s*$', line) 135 | if mobj: 136 | if mobj.group(3).lower() == 'kb': 137 | mult = 1024 138 | else: 139 | mult = 1 140 | info_dict[mobj.group(1)] = int(mobj.group(2)) * mult 141 | return info_dict 142 | 143 | def getSwapStats(self): 144 | """Return information on swap partition and / or files. 145 | 146 | @return: Dictionary of stats. 147 | 148 | """ 149 | info_dict = {} 150 | try: 151 | fp = open(swapsFile, 'r') 152 | data = fp.read() 153 | fp.close() 154 | except: 155 | raise IOError('Failed reading stats from file: %s' % swapsFile) 156 | lines = data.splitlines() 157 | if len(lines) > 1: 158 | colnames = [name.lower() for name in lines[0].split()] 159 | for line in lines[1:]: 160 | cols = line.split() 161 | info_dict[cols[0]] = dict(zip(colnames[1:], cols[1:])) 162 | return info_dict 163 | 164 | def getVMstats(self): 165 | """Return stats for Virtual Memory Subsystem. 166 | 167 | @return: Dictionary of stats. 168 | 169 | """ 170 | info_dict = {} 171 | try: 172 | fp = open(vmstatFile, 'r') 173 | data = fp.read() 174 | fp.close() 175 | except: 176 | raise IOError('Failed reading stats from file: %s' % vmstatFile) 177 | for line in data.splitlines(): 178 | cols = line.split() 179 | if len(cols) == 2: 180 | info_dict[cols[0]] = cols[1] 181 | return info_dict 182 | -------------------------------------------------------------------------------- /pysysinfo/mysql.py: -------------------------------------------------------------------------------- 1 | """Implements MySQLinfo Class for gathering stats from MySQL Database Server. 2 | 3 | The statistics are obtained by connecting to and querying local and/or 4 | remote MySQL Servers. 5 | 6 | """ 7 | 8 | import MySQLdb 9 | import util 10 | 11 | __author__ = "Ali Onur Uyar" 12 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 13 | __credits__ = [] 14 | __license__ = "GPL" 15 | __version__ = "0.9" 16 | __maintainer__ = "Ali Onur Uyar" 17 | __email__ = "aouyar at gmail.com" 18 | __status__ = "Development" 19 | 20 | 21 | defaultMySQLport = 3306 22 | 23 | 24 | class MySQLinfo: 25 | """Class to retrieve stats for MySQL Database""" 26 | 27 | def __init__(self, host=None, port=None, 28 | database=None, user=None, password=None, autoInit=True): 29 | """Initialize connection to MySQL Database. 30 | 31 | @param host: MySQL Host 32 | @param port: MySQL Port 33 | @param database: MySQL Database 34 | @param user: MySQL User 35 | @param password: MySQL Password 36 | @param autoInit: If True connect to MySQL Database on instantiation. 37 | 38 | """ 39 | self._conn = None 40 | self._connParams = {} 41 | if host is not None: 42 | self._connParams['host'] = host 43 | if port is not None: 44 | self._connParams['port'] = port 45 | else: 46 | self._connParams['port'] = defaultMySQLport 47 | elif port is not None: 48 | self._connParams['host'] = '127.0.0.1' 49 | self._connParams['port'] = port 50 | if database is not None: 51 | self._connParams['db'] = database 52 | if user is not None: 53 | self._connParams['user'] = user 54 | if password is not None: 55 | self._connParams['passwd'] = password 56 | if autoInit: 57 | self._connect() 58 | 59 | def __del__(self): 60 | """Cleanup.""" 61 | if self._conn is not None: 62 | self._conn.close() 63 | 64 | def _connect(self): 65 | """Establish connection to MySQL Database.""" 66 | if self._connParams: 67 | self._conn = MySQLdb.connect(**self._connParams) 68 | else: 69 | self._conn = MySQLdb.connect('') 70 | 71 | def getStorageEngines(self): 72 | """Returns list of supported storage engines. 73 | 74 | @return: List of storage engine names. 75 | 76 | """ 77 | cur = self._conn.cursor() 78 | cur.execute("""SHOW STORAGE ENGINES;""") 79 | rows = cur.fetchall() 80 | if rows: 81 | return [row[0].lower() for row in rows if row[1] in ['YES', 'DEFAULT']] 82 | else: 83 | return [] 84 | 85 | def getParam(self, key): 86 | """Returns value of Run-time Database Parameter 'key'. 87 | 88 | @param key: Run-time parameter name. 89 | @return: Run-time parameter value. 90 | 91 | """ 92 | cur = self._conn.cursor() 93 | cur.execute("SHOW GLOBAL VARIABLES LIKE %s", key) 94 | row = cur.fetchone() 95 | return int(row[1]) 96 | 97 | def getParams(self): 98 | """Returns dictionary of all run-time parameters. 99 | 100 | @return: Dictionary of all Run-time parameters. 101 | 102 | """ 103 | cur = self._conn.cursor() 104 | cur.execute("SHOW GLOBAL VARIABLES") 105 | rows = cur.fetchall() 106 | info_dict = {} 107 | for row in rows: 108 | key = row[0] 109 | val = util.parse_value(row[1]) 110 | info_dict[key] = val 111 | return info_dict 112 | 113 | def getStats(self): 114 | """Returns global stats for database. 115 | 116 | @return: Dictionary of database statistics. 117 | 118 | """ 119 | cur = self._conn.cursor() 120 | cur.execute("SHOW GLOBAL STATUS") 121 | rows = cur.fetchall() 122 | info_dict = {} 123 | for row in rows: 124 | key = row[0] 125 | val = util.parse_value(row[1]) 126 | info_dict[key] = val 127 | return info_dict 128 | 129 | def getProcessStatus(self): 130 | """Returns number of processes discriminated by state. 131 | 132 | @return: Dictionary mapping process state to number of processes. 133 | 134 | """ 135 | info_dict = {} 136 | cur = self._conn.cursor() 137 | cur.execute("""SHOW FULL PROCESSLIST;""") 138 | rows = cur.fetchall() 139 | if rows: 140 | for row in rows: 141 | if row[6] == '': 142 | state = 'idle' 143 | elif row[6] is None: 144 | state = 'other' 145 | else: 146 | state = str(row[6]).replace(' ', '_').lower() 147 | info_dict[state] = info_dict.get(state, 0) + 1 148 | return info_dict 149 | 150 | def getProcessDatabase(self): 151 | """Returns number of processes discriminated by database name. 152 | 153 | @return: Dictionary mapping database name to number of processes. 154 | 155 | """ 156 | info_dict = {} 157 | cur = self._conn.cursor() 158 | cur.execute("""SHOW FULL PROCESSLIST;""") 159 | rows = cur.fetchall() 160 | if rows: 161 | for row in rows: 162 | db = row[3] 163 | info_dict[db] = info_dict.get(db, 0) + 1 164 | return info_dict 165 | 166 | def getDatabases(self): 167 | """Returns list of databases. 168 | 169 | @return: List of databases. 170 | 171 | """ 172 | cur = self._conn.cursor() 173 | cur.execute("""SHOW DATABASES;""") 174 | rows = cur.fetchall() 175 | if rows: 176 | return [row[0] for row in rows] 177 | else: 178 | return [] 179 | -------------------------------------------------------------------------------- /pymunin/plugins/procstats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """procstats - Munin Plugin to monitor process / thread stats. 3 | 4 | 5 | Requirements 6 | 7 | - ps command 8 | 9 | Wild Card Plugin - No 10 | 11 | 12 | Multigraph Plugin - Graph Structure 13 | 14 | - proc_status 15 | - proc_priority 16 | - thread_status 17 | - thread_priority 18 | 19 | 20 | Environment Variables 21 | 22 | include_graphs: Comma separated list of enabled graphs. 23 | (All graphs enabled by default.) 24 | exclude_graphs: Comma separated list of disabled graphs. 25 | 26 | Example: 27 | [procstats] 28 | env.include_graphs proc_status 29 | 30 | """ 31 | # Munin - Magic Markers 32 | #%# family=auto 33 | #%# capabilities=autoconf nosuggest 34 | 35 | import sys 36 | from pymunin import MuninGraph, MuninPlugin, muninMain 37 | from pysysinfo.process import ProcessInfo 38 | 39 | __author__ = "Ali Onur Uyar" 40 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 41 | __credits__ = [] 42 | __license__ = "GPL" 43 | __version__ = "0.9" 44 | __maintainer__ = "Ali Onur Uyar" 45 | __email__ = "aouyar at gmail.com" 46 | __status__ = "Development" 47 | 48 | 49 | class MuninProcStatsPlugin(MuninPlugin): 50 | """Multigraph Munin Plugin for monitoring Process Stats. 51 | 52 | """ 53 | plugin_name = 'procstats' 54 | isMultigraph = True 55 | 56 | def __init__(self, argv=(), env=None, debug=False): 57 | """Populate Munin Plugin with MuninGraph instances. 58 | 59 | @param argv: List of command line arguments. 60 | @param env: Dictionary of environment variables. 61 | @param debug: Print debugging messages if True. (Default: False) 62 | 63 | """ 64 | MuninPlugin.__init__(self, argv, env, debug) 65 | 66 | self._category = 'Processes' 67 | 68 | for (prefix, title, desc) in (('proc', self._category, 'Number of processes'), 69 | ('thread', 'Threads', 'Number of threads')): 70 | graph_name = '%s_status' % prefix 71 | graph_title = '%s - Status' % title 72 | graph_desc = '%s discriminated by status.' % desc 73 | if self.graphEnabled(graph_name): 74 | graph = MuninGraph(graph_title, self._category, info=graph_desc, 75 | args='--base 1000 --lower-limit 0') 76 | for (fname, fdesc) in ( 77 | ('unint_sleep', 'Uninterruptable sleep. (Usually I/O)'), 78 | ('stopped', 'Stopped, either by job control signal ' 79 | 'or because it is being traced.'), 80 | ('defunct', 'Defunct (zombie) process. ' 81 | 'Terminated but not reaped by parent.'), 82 | ('running', 'Running or runnable (on run queue).'), 83 | ('sleep', 'Interruptable sleep. ' 84 | 'Waiting for an event to complete.')): 85 | graph.addField(fname, fname, type='GAUGE', draw='AREA', 86 | info=fdesc) 87 | self.appendGraph(graph_name, graph) 88 | 89 | graph_name = '%s_prio' % prefix 90 | graph_title = '%s - Priority' % title 91 | graph_desc = '%s discriminated by priority.' % desc 92 | if self.graphEnabled(graph_name): 93 | graph = MuninGraph(graph_title, self._category, info=graph_desc, 94 | args='--base 1000 --lower-limit 0') 95 | for (fname, fdesc) in ( 96 | ('high', 'High priority.'), 97 | ('low', 'Low priority.'), 98 | ('norm', 'Normal priority.')): 99 | graph.addField(fname, fname, type='GAUGE', draw='AREA', 100 | info=fdesc) 101 | graph.addField('locked', 'locked', type='GAUGE', draw='LINE2', 102 | info='Has pages locked into memory.') 103 | self.appendGraph(graph_name, graph) 104 | 105 | def retrieveVals(self): 106 | """Retrieve values for graphs.""" 107 | proc_info = ProcessInfo() 108 | stats = {} 109 | for (prefix, is_thread) in (('proc', False), 110 | ('thread', True)): 111 | graph_name = '%s_status' % prefix 112 | if self.hasGraph(graph_name): 113 | if not stats.has_key(prefix): 114 | stats[prefix] = proc_info.getProcStatStatus(is_thread) 115 | for (fname, stat_key) in ( 116 | ('unint_sleep', 'uninterruptable_sleep'), 117 | ('stopped', 'stopped'), 118 | ('defunct', 'defunct'), 119 | ('running', 'running'), 120 | ('sleep', 'sleep')): 121 | self.setGraphVal(graph_name, fname, 122 | stats[prefix]['status'].get(stat_key)) 123 | graph_name = '%s_prio' % prefix 124 | if self.hasGraph(graph_name): 125 | if not stats.has_key(prefix): 126 | stats[prefix] = proc_info.getProcStatStatus(is_thread) 127 | for (fname, stat_key) in ( 128 | ('high', 'high'), 129 | ('low', 'low'), 130 | ('norm', 'norm'), 131 | ('locked', 'locked_in_mem')): 132 | self.setGraphVal(graph_name, fname, 133 | stats[prefix]['prio'].get(stat_key)) 134 | 135 | def autoconf(self): 136 | """Implements Munin Plugin Auto-Configuration Option. 137 | 138 | @return: True if plugin can be auto-configured, False otherwise. 139 | 140 | """ 141 | proc_info = ProcessInfo() 142 | return len(proc_info.getProcList()) > 0 143 | 144 | 145 | def main(): 146 | sys.exit(muninMain(MuninProcStatsPlugin)) 147 | 148 | 149 | if __name__ == "__main__": 150 | main() 151 | -------------------------------------------------------------------------------- /pymunin/plugins/ntphostoffsets.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ntphostoffsets - Munin Plugin to monitor time offset of multiple remote hosts 3 | using NTP. 4 | 5 | Requirements 6 | 7 | - Requires ntpd running on remote hosts and access to NTP on remote host. 8 | - Requires ntpdate utility on local host. 9 | 10 | Wild Card Plugin - No 11 | 12 | 13 | Multigraph Plugin - Graph Structure 14 | 15 | - ntp_host_stratums 16 | - ntp_host_offsets 17 | - ntp_host_delays 18 | 19 | 20 | Environment Variables 21 | 22 | ntphosts: Comma separated list of IP addresses of hosts to be monitored. 23 | include_graphs: Comma separated list of enabled graphs. 24 | (All graphs enabled by default.) 25 | exclude_graphs: Comma separated list of disabled graphs. 26 | 27 | Environment Variables for Multiple Instances of Plugin (Omitted by default.) 28 | instance_name: Name of instance. 29 | instance_label: Graph title label for instance. 30 | (Default is the same as instance name.) 31 | instance_label_format: One of the following values: 32 | - suffix (Default) 33 | - prefix 34 | - none 35 | 36 | Example: 37 | [ntphostoffsets] 38 | env.ntphosts 192.168.1.1,192.168.1.2 39 | env.exclude_graphs ntp_host_stratums 40 | 41 | """ 42 | # Munin - Magic Markers 43 | #%# family=auto 44 | #%# capabilities=autoconf nosuggest 45 | 46 | import sys 47 | import re 48 | from pymunin import MuninGraph, MuninPlugin, muninMain 49 | from pysysinfo.ntp import NTPinfo 50 | 51 | __author__ = "Ali Onur Uyar" 52 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 53 | __credits__ = [] 54 | __license__ = "GPL" 55 | __version__ = "0.9.20" 56 | __maintainer__ = "Ali Onur Uyar" 57 | __email__ = "aouyar at gmail.com" 58 | __status__ = "Development" 59 | 60 | 61 | class MuninNTPhostOffsetsPlugin(MuninPlugin): 62 | """Multigraph Munin Plugin for monitoring time offsets of multiple remote 63 | hosts using NTP. 64 | 65 | """ 66 | plugin_name = 'ntphostoffsets' 67 | isMultigraph = True 68 | isMultiInstance = True 69 | 70 | def __init__(self, argv=(), env=None, debug=False): 71 | """Populate Munin Plugin with MuninGraph instances. 72 | 73 | @param argv: List of command line arguments. 74 | @param env: Dictionary of environment variables. 75 | @param debug: Print debugging messages if True. (Default: False) 76 | 77 | """ 78 | MuninPlugin.__init__(self, argv, env, debug) 79 | self._category = 'Time' 80 | 81 | if self.envHasKey('ntphosts'): 82 | hosts_str = re.sub('[^\d\.,]', '', self.envGet('ntphosts')) 83 | self._remoteHosts = hosts_str.split(',') 84 | else: 85 | raise AttributeError("Remote host list must be passed in the " 86 | "'ntphosts' environment variable.") 87 | 88 | if self.graphEnabled('ntp_host_stratums'): 89 | graph = MuninGraph('NTP Stratums of Multiple Hosts', self._category, 90 | info='NTP Stratum of Multiple Remote Hosts.', 91 | args='--base 1000 --lower-limit 0') 92 | for host in self._remoteHosts: 93 | hostkey = re.sub('\.', '_', host) 94 | graph.addField(hostkey, host, type='GAUGE', draw='LINE2') 95 | self.appendGraph('ntp_host_stratums', graph) 96 | 97 | if self.graphEnabled('ntp_host_offsets'): 98 | graph = MuninGraph('NTP Offsets of Multiple Hosts', self._category, 99 | info='NTP Delays of Multiple Hosts relative to current node.', 100 | args ='--base 1000 --lower-limit 0', 101 | vlabel='seconds' 102 | ) 103 | for host in self._remoteHosts: 104 | hostkey = re.sub('\.', '_', host) 105 | graph.addField(hostkey, host, type='GAUGE', draw='LINE2') 106 | self.appendGraph('ntp_host_offsets', graph) 107 | 108 | if self.graphEnabled('ntp_host_delays'): 109 | graph = MuninGraph('NTP Delays of Multiple Hosts', self._category, 110 | info='NTP Delays of Multiple Hosts relative to current node.', 111 | args='--base 1000 --lower-limit 0', 112 | vlabel='seconds' 113 | ) 114 | for host in self._remoteHosts: 115 | hostkey = re.sub('\.', '_', host) 116 | graph.addField(hostkey, host, type='GAUGE', draw='LINE2') 117 | self.appendGraph('ntp_host_delays', graph) 118 | 119 | def retrieveVals(self): 120 | """Retrieve values for graphs.""" 121 | ntpinfo = NTPinfo() 122 | ntpstats = ntpinfo.getHostOffsets(self._remoteHosts) 123 | if ntpstats: 124 | for host in self._remoteHosts: 125 | hostkey = re.sub('\.', '_', host) 126 | hoststats = ntpstats.get(host) 127 | if hoststats: 128 | if self.hasGraph('ntp_host_stratums'): 129 | self.setGraphVal('ntp_host_stratums', hostkey, 130 | hoststats.get('stratum')) 131 | if self.hasGraph('ntp_host_offsets'): 132 | self.setGraphVal('ntp_host_offsets', hostkey, 133 | hoststats.get('offset')) 134 | if self.hasGraph('ntp_host_delays'): 135 | self.setGraphVal('ntp_host_delays', hostkey, 136 | hoststats.get('delay')) 137 | 138 | def autoconf(self): 139 | """Implements Munin Plugin Auto-Configuration Option. 140 | 141 | @return: True if plugin can be auto-configured, False otherwise. 142 | 143 | """ 144 | ntpinfo = NTPinfo() 145 | ntpstats = ntpinfo.getHostOffsets(self._remoteHosts) 146 | return len(ntpstats) > 0 147 | 148 | 149 | def main(): 150 | sys.exit(muninMain(MuninNTPhostOffsetsPlugin)) 151 | 152 | 153 | if __name__ == "__main__": 154 | main() 155 | -------------------------------------------------------------------------------- /pysysinfo/memcached.py: -------------------------------------------------------------------------------- 1 | """Implements MemcachedInfo Class for gathering stats from Memcached. 2 | 3 | The statistics are obtained by connecting to and querying the Memcached. 4 | 5 | """ 6 | 7 | import re 8 | import os 9 | import util 10 | 11 | __author__ = "Ali Onur Uyar" 12 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 13 | __credits__ = [] 14 | __license__ = "GPL" 15 | __version__ = "0.9" 16 | __maintainer__ = "Ali Onur Uyar" 17 | __email__ = "aouyar at gmail.com" 18 | __status__ = "Development" 19 | 20 | 21 | defaultMemcachedPort = 11211 22 | 23 | 24 | class MemcachedInfo: 25 | """Class that establishes connection to Memcached Instance 26 | to retrieve statistics on operation. 27 | 28 | """ 29 | 30 | def __init__(self, host='127.0.0.1', port=defaultMemcachedPort, 31 | socket_file=None, timeout=None, autoInit=True): 32 | """Initialize connection to Memcached. 33 | 34 | @param host: Memcached Host for TCP connections. 35 | @param port: Memcached Port for TCP connections. 36 | @param socket_file: Memcached Socket File Path for UNIX Socket connections. 37 | @param timeout: Memcached Socket Timeout in seconds. 38 | @param autoInit: If True connect to Memcached on init. 39 | 40 | """ 41 | self._conn = None 42 | if socket_file is not None: 43 | self._host = None 44 | self._port = None 45 | self._socketFile = socket_file 46 | self._instanceName = ("Memcached Instance on socket file %s" 47 | % self._socketFile) 48 | else: 49 | self._host = host or '127.0.0.1' 50 | self._port = int(port or defaultMemcachedPort) 51 | self._socketFile = None 52 | self._instanceName = ("Memcached Instance on host %s and port %s" 53 | % (self._host, self._port)) 54 | if timeout is not None: 55 | self._timeout = float(timeout) 56 | else: 57 | self._timeout = None 58 | if autoInit: 59 | self._connect() 60 | 61 | def __del__(self): 62 | """Cleanup.""" 63 | if self._conn is not None: 64 | self._conn.close() 65 | 66 | def _connect(self): 67 | """Connect to Memcached.""" 68 | if self._socketFile is not None: 69 | if not os.path.exists(self._socketFile): 70 | raise Exception("Socket file (%s) for Memcached Instance not found." 71 | % self._socketFile) 72 | try: 73 | if self._timeout is not None: 74 | self._conn = util.Telnet(self._host, self._port, self._socketFile, 75 | timeout) 76 | else: 77 | self._conn = util.Telnet(self._host, self._port, self._socketFile) 78 | except: 79 | raise Exception("Connection to %s failed." % self._instanceName) 80 | 81 | def _sendStatCmd(self, cmd): 82 | """Send stat command to Memcached Server and return response lines. 83 | 84 | @param cmd: Command string. 85 | @return: Array of strings. 86 | 87 | """ 88 | try: 89 | self._conn.write("%s\r\n" % cmd) 90 | regex = re.compile('^(END|ERROR)\r\n', re.MULTILINE) 91 | (idx, mobj, text) = self._conn.expect([regex,], self._timeout) #@UnusedVariable 92 | except: 93 | raise Exception("Communication with %s failed" % self._instanceName) 94 | if mobj is not None: 95 | if mobj.group(1) == 'END': 96 | return text.splitlines()[:-1] 97 | elif mobj.group(1) == 'ERROR': 98 | raise Exception("Protocol error in communication with %s." 99 | % self._instanceName) 100 | else: 101 | raise Exception("Connection with %s timed out." % self._instanceName) 102 | def _parseStats(self, lines, parse_slabs = False): 103 | """Parse stats output from memcached and return dictionary of stats- 104 | 105 | @param lines: Array of lines of input text. 106 | @param parse_slabs: Parse slab stats if True. 107 | @return: Stats dictionary. 108 | 109 | """ 110 | info_dict = {} 111 | info_dict['slabs'] = {} 112 | for line in lines: 113 | mobj = re.match('^STAT\s(\w+)\s(\S+)$', line) 114 | if mobj: 115 | info_dict[mobj.group(1)] = util.parse_value(mobj.group(2), True) 116 | continue 117 | elif parse_slabs: 118 | mobj = re.match('STAT\s(\w+:)?(\d+):(\w+)\s(\S+)$', line) 119 | if mobj: 120 | (slab, key, val) = mobj.groups()[-3:] 121 | if not info_dict['slabs'].has_key(slab): 122 | info_dict['slabs'][slab] = {} 123 | info_dict['slabs'][slab][key] = util.parse_value(val, True) 124 | return info_dict 125 | 126 | def getStats(self): 127 | """Query Memcached and return operational stats. 128 | 129 | @return: Dictionary of stats. 130 | 131 | """ 132 | lines = self._sendStatCmd('stats') 133 | return self._parseStats(lines, False) 134 | 135 | def getStatsItems(self): 136 | """Query Memcached and return stats on items broken down by slab. 137 | 138 | @return: Dictionary of stats. 139 | 140 | """ 141 | lines = self._sendStatCmd('stats items') 142 | return self._parseStats(lines, True) 143 | 144 | def getStatsSlabs(self): 145 | """Query Memcached and return stats on slabs. 146 | 147 | @return: Dictionary of stats. 148 | 149 | """ 150 | lines = self._sendStatCmd('stats slabs') 151 | return self._parseStats(lines, True) 152 | 153 | def getSettings(self): 154 | """Query Memcached and return settings. 155 | 156 | @return: Dictionary of settings. 157 | 158 | """ 159 | lines = self._sendStatCmd('stats settings') 160 | return self._parseStats(lines, False) 161 | -------------------------------------------------------------------------------- /pymunin/plugins/rackspacestats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """rackspacestats - Munin Plugin to monitor stats for Rackspace Cloud.. 3 | 4 | 5 | Requirements 6 | 7 | - Valid username and api_key for accessing Rackspace Cloud. 8 | 9 | Wild Card Plugin - No 10 | 11 | Multigraph Plugin - Graph Structure 12 | 13 | - rackspace_cloudfiles_count 14 | - rackspace_cloudfiles_size 15 | 16 | Environment Variables 17 | 18 | username: Rackspace Cloud username. 19 | api_key: Rackspace Cloud api_key. 20 | region: Rackspace Auth Server Region. 21 | (US Auth Server is used by default.) 22 | Examples: 23 | - us: USA 24 | - uk: United Kingdom. 25 | servicenet: Enable (on) / disable (off) using the Rackspace ServiceNet for 26 | accessing the cloud. 27 | (Disabled by default.) 28 | include_container: Comma separated list of containers to include in graphs. 29 | (All enabled by default.) 30 | exclude_container: Comma separated list of containers to exclude from graphs. 31 | include_graphs: Comma separated list of enabled graphs. 32 | (All graphs enabled by default.) 33 | exclude_graphs: Comma separated list of disabled graphs. 34 | 35 | Environment Variables for Multiple Instances of Plugin (Omitted by default.) 36 | 37 | instance_name: Name of instance. 38 | instance_label: Graph title label for instance. 39 | (Default is the same as instance name.) 40 | instance_label_format: One of the following values: 41 | - suffix (Default) 42 | - prefix 43 | - none 44 | 45 | Example: 46 | 47 | [rackspacestats] 48 | env.username joe 49 | env.api_key ******************************** 50 | env.region uk 51 | env.include_container test1,test3 52 | 53 | """ 54 | # Munin - Magic Markers 55 | #%# family=auto 56 | #%# capabilities=autoconf nosuggest 57 | 58 | import sys 59 | from pymunin import MuninGraph, MuninPlugin, muninMain 60 | from pysysinfo.rackspace import CloudFilesInfo 61 | 62 | __author__ = "Ben Welsh" 63 | __copyright__ = "Copyright 2012, Ben Welsh" 64 | __credits__ = [] 65 | __license__ = "GPL" 66 | __version__ = "0.9.20" 67 | __maintainer__ = "Ali Onur Uyar" 68 | __email__ = "aouyar at gmail.com" 69 | __status__ = "Development" 70 | 71 | 72 | class MuninRackspacePlugin(MuninPlugin): 73 | """Multigraph Munin Plugin for monitoring Rackspace Cloud Usage. 74 | 75 | """ 76 | plugin_name = 'rackspacestats' 77 | isMultigraph = True 78 | isMultiInstance = True 79 | 80 | def __init__(self, argv=(), env=None, debug=False): 81 | """ 82 | Populate Munin Plugin with MuninGraph instances. 83 | 84 | @param argv: List of command line arguments. 85 | @param env: Dictionary of environment variables. 86 | @param debug: Print debugging messages if True. (Default: False) 87 | 88 | """ 89 | MuninPlugin.__init__(self, argv, env, debug) 90 | 91 | self.envRegisterFilter('container', '^\w+$') 92 | self._username = self.envGet('username') 93 | self._api_key = self.envGet('api_key') 94 | self._region = self.envGet('region') 95 | self._servicenet = self.envCheckFlag('servicenet', False) 96 | self._category = 'Rackspace' 97 | 98 | self._fileInfo = CloudFilesInfo(username=self._username, 99 | api_key=self._api_key, 100 | region=self._region, 101 | servicenet=self._servicenet) 102 | self._fileContList = [name for name in self._fileInfo.getContainerList() 103 | if self.containerIncluded(name)] 104 | 105 | if self.graphEnabled('rackspace_cloudfiles_container_size'): 106 | graph = MuninGraph('Rackspace Cloud Files - Container Size (bytes)', 107 | self._category, 108 | info='The total size of files for each Rackspace Cloud Files container.', 109 | args='--base 1024 --lower-limit 0', autoFixNames=True) 110 | for contname in self._fileContList: 111 | graph.addField(contname, contname, draw='AREASTACK', 112 | type='GAUGE') 113 | self.appendGraph('rackspace_cloudfiles_container_size', graph) 114 | 115 | if self.graphEnabled('rackspace_cloudfiles_container_count'): 116 | graph = MuninGraph('Rackspace Cloud Files - Container Object Count', 117 | self._category, 118 | info='The total number of files for each Rackspace Cloud Files container.', 119 | args='--base 1024 --lower-limit 0', autoFixNames=True) 120 | for contname in self._fileContList: 121 | graph.addField(contname, contname, draw='AREASTACK', 122 | type='GAUGE') 123 | self.appendGraph('rackspace_cloudfiles_container_count', graph) 124 | 125 | def retrieveVals(self): 126 | """Retrieve values for graphs.""" 127 | file_stats = self._fileInfo.getContainerStats() 128 | for contname in self._fileContList: 129 | stats = file_stats.get(contname) 130 | if stats is not None: 131 | if self.hasGraph('rackspace_cloudfiles_container_size'): 132 | self.setGraphVal('rackspace_cloudfiles_container_size', contname, 133 | stats.get('size')) 134 | if self.hasGraph('rackspace_cloudfiles_container_count'): 135 | self.setGraphVal('rackspace_cloudfiles_container_count', contname, 136 | stats.get('count')) 137 | 138 | def containerIncluded(self, name): 139 | """Utility method to check if database is included in graphs. 140 | 141 | @param name: Name of container. 142 | @return: Returns True if included in graphs, False otherwise. 143 | 144 | """ 145 | return self.envCheckFilter('container', name) 146 | 147 | 148 | def main(): 149 | sys.exit(muninMain(MuninRackspacePlugin)) 150 | 151 | 152 | if __name__ == "__main__": 153 | main() 154 | -------------------------------------------------------------------------------- /pymunin/plugins/apachestats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """apachestats - Munin Plugin to monitor stats for Apache Web Server. 3 | 4 | 5 | Requirements 6 | 7 | - Access to Apache Web Server server-status page. 8 | 9 | 10 | Wild Card Plugin - No 11 | 12 | 13 | Multigraph Plugin - Graph Structure 14 | 15 | - apache_access 16 | - apache_bytes 17 | - apache_workers 18 | 19 | 20 | Environment Variables 21 | 22 | host: Apache Web Server Host. (Default: 127.0.0.1) 23 | port: Apache Web Server Port. (Default: 80, SSL: 443) 24 | user: User in case authentication is required for access to 25 | server-status page. 26 | password: Password in case authentication is required for access 27 | to server-status page. 28 | statuspath: Path for Apache Web Server Status Page. 29 | (Default: server-status) 30 | ssl: Use SSL if yes. (Default: no) 31 | include_graphs: Comma separated list of enabled graphs. 32 | (All graphs enabled by default.) 33 | exclude_graphs: Comma separated list of disabled graphs. 34 | 35 | Environment Variables for Multiple Instances of Plugin (Omitted by default.) 36 | 37 | instance_name: Name of instance. 38 | instance_label: Graph title label for instance. 39 | (Default is the same as instance name.) 40 | instance_label_format: One of the following values: 41 | - suffix (Default) 42 | - prefix 43 | - none 44 | 45 | Example: 46 | [apachestats] 47 | env.exclude_graphs apache_access,apache_load 48 | 49 | """ 50 | # Munin - Magic Markers 51 | #%# family=auto 52 | #%# capabilities=autoconf nosuggest 53 | 54 | import sys 55 | from pymunin import MuninGraph, MuninPlugin, muninMain 56 | from pysysinfo.apache import ApacheInfo 57 | 58 | __author__ = "Ali Onur Uyar" 59 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 60 | __credits__ = [] 61 | __license__ = "GPL" 62 | __version__ = "0.9.20" 63 | __maintainer__ = "Ali Onur Uyar" 64 | __email__ = "aouyar at gmail.com" 65 | __status__ = "Development" 66 | 67 | 68 | class MuninApachePlugin(MuninPlugin): 69 | """Multigraph Munin Plugin for monitoring Apache Web Server. 70 | 71 | """ 72 | plugin_name = 'apachestats' 73 | isMultigraph = True 74 | isMultiInstance = True 75 | 76 | def __init__(self, argv=(), env=None, debug=False): 77 | """Populate Munin Plugin with MuninGraph instances. 78 | 79 | @param argv: List of command line arguments. 80 | @param env: Dictionary of environment variables. 81 | @param debug: Print debugging messages if True. (Default: False) 82 | 83 | """ 84 | MuninPlugin.__init__(self, argv, env, debug) 85 | 86 | self._host = self.envGet('host') 87 | self._port = self.envGet('port', None, int) 88 | self._user = self.envGet('user') 89 | self._password = self.envGet('password') 90 | self._statuspath = self.envGet('statuspath') 91 | self._ssl = self.envCheckFlag('ssl', False) 92 | self._category = 'Apache' 93 | 94 | if self.graphEnabled('apache_access'): 95 | graph = MuninGraph('Apache Web Server - Throughput (Requests / sec)', 96 | self._category, 97 | info='Throughput in Requests per second for Apache Web Server.', 98 | args='--base 1000 --lower-limit 0') 99 | graph.addField('reqs', 'reqs', draw='LINE2', type='DERIVE', min=0, 100 | info="Requests per second.") 101 | self.appendGraph('apache_access', graph) 102 | 103 | if self.graphEnabled('apache_bytes'): 104 | graph = MuninGraph('Apache Web Server - Througput (bytes/sec)', 105 | self._category, 106 | info='Throughput in bytes per second for Apache Web Server.', 107 | args='--base 1024 --lower-limit 0') 108 | graph.addField('bytes', 'bytes', draw='LINE2', type='DERIVE', min=0) 109 | self.appendGraph('apache_bytes', graph) 110 | 111 | if self.graphEnabled('apache_workers'): 112 | graph = MuninGraph('Apache Web Server - Workers', self._category, 113 | info='Worker utilization stats for Apache Web server.', 114 | args='--base 1000 --lower-limit 0') 115 | graph.addField('busy', 'busy', draw='AREASTACK', type='GAUGE', 116 | info="Number of busy workers.") 117 | graph.addField('idle', 'idle', draw='AREASTACK', type='GAUGE', 118 | info="Number of idle workers.") 119 | graph.addField('max', 'max', draw='LINE2', type='GAUGE', 120 | info="Maximum number of workers permitted.", 121 | colour='FF0000') 122 | self.appendGraph('apache_workers', graph) 123 | 124 | def retrieveVals(self): 125 | """Retrieve values for graphs.""" 126 | apacheInfo = ApacheInfo(self._host, self._port, 127 | self._user, self._password, 128 | self._statuspath, self._ssl) 129 | stats = apacheInfo.getServerStats() 130 | if self.hasGraph('apache_access'): 131 | self.setGraphVal('apache_access', 'reqs', stats['Total Accesses']) 132 | if self.hasGraph('apache_bytes'): 133 | self.setGraphVal('apache_bytes', 'bytes', 134 | stats['Total kBytes'] * 1000) 135 | if self.hasGraph('apache_workers'): 136 | self.setGraphVal('apache_workers', 'busy', stats['BusyWorkers']) 137 | self.setGraphVal('apache_workers', 'idle', stats['IdleWorkers']) 138 | self.setGraphVal('apache_workers', 'max', stats['MaxWorkers']) 139 | 140 | def autoconf(self): 141 | """Implements Munin Plugin Auto-Configuration Option. 142 | 143 | @return: True if plugin can be auto-configured, False otherwise. 144 | 145 | """ 146 | apacheInfo = ApacheInfo(self._host, self._port, 147 | self._user, self._password, 148 | self._statuspath, self._ssl) 149 | return apacheInfo is not None 150 | 151 | 152 | def main(): 153 | sys.exit(muninMain(MuninApachePlugin)) 154 | 155 | 156 | if __name__ == "__main__": 157 | main() 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PyMunin - Python Multigraph Munin Plugins 2 | ========================================= 3 | 4 | 5 | About 6 | ----- 7 | 8 | Python Module for developing Munin Multigraph Monitoring Plugins. 9 | 10 | More detailed documentation for the project and sample graphs for plugins are 11 | published in the [PyMunin Project Web Page](http://aouyar.github.com/PyMunin/). 12 | 13 | Regular Munin Plugins employ one-plugin one-graph logic and require the 14 | execution of a script for data retrieval for each graph. 15 | Multigraph plugins permit retrieval of data for multiple graphs in one execution 16 | run (one-plugin many-graphs), reducing the processing time and delay for the 17 | fetch cycle significantly. 18 | More information on Multigraph Plugins can be found in the 19 | [Munin Wiki](http://munin-monitoring.org/wiki/): 20 | 21 | * [Multigraph Plugins](http://munin-monitoring.org/wiki/MultigraphSampleOutput) 22 | * [Multigraph Plugin Protocol](http://munin-monitoring.org/wiki/protocol-multigraph) 23 | 24 | The plugins consist of the following components: 25 | 26 | * The _pymunin_ module _(./pymunin)_ implements the base classes for 27 | developing Munin plugins. 28 | * The plugin logic is implemented in the plugin scripts in _./pymunin/plugins_. 29 | * The actual data retrieval logic is separated from the plugins to facilitate 30 | code reuse. Individual modules in the directory _./pysysinfo_ implement classes 31 | for getting the monitoring data and returning them in dictionary objects. 32 | The separation of the data retrieval logic should facilitate the use of the 33 | same code in other monitoring solutions. 34 | 35 | Although the solution is focused on implementing _Multigraph Plugins_ the module 36 | also supports simple single graph plugins. 37 | 38 | For information on other projects you can check 39 | my [GitHub Personal Page](http://aouyar.github.com) 40 | and [GitHub Profile](https://github.com/aouyar). 41 | 42 | 43 | Munin Plugins 44 | ------------- 45 | 46 | Multigraph Monitoring Plugins for the following applications are already 47 | included: 48 | 49 | * Apache Tomcat 50 | * Apache Web Server 51 | * Asterisk Telephony Server 52 | * Disk Usage 53 | * Disk I/O 54 | * FreeSWITCH Soft Switch 55 | * Lighttpd Web Server 56 | * Memcached 57 | * MySQL Database 58 | * Network Interface Traffic and Errors 59 | * Network Connection Stats (netstat) 60 | * Nginx Web Server 61 | * NTP - Time Server 62 | * PHP APC - PHP Cache 63 | * PHP FPM (FastCGI Process Manager) 64 | * PostgreSQL Database 65 | * Processes and Threads 66 | * Rackspace Cloud 67 | * Redis Server 68 | * System Resources 69 | (Load, CPU, Memory, Processes, Interrupts, Paging, Swapping, etc.) 70 | * Sangoma Wanpipe Telephony Interfaces 71 | * Varnish Cache Web Application Accelerator 72 | 73 | 74 | Classes for retrieving stats are available, but no plugins have been developed 75 | yet for the following: 76 | 77 | * Squid Web Proxy 78 | 79 | 80 | Licensing 81 | --------- 82 | 83 | _PyMunin_ is copyrighted free software made available under the terms of the 84 | _GPL License Version 3_ or later. 85 | 86 | See the _COPYING_ file that accompanies the code for full licensing information. 87 | 88 | 89 | Download 90 | -------- 91 | 92 | New versions of the code are be published for download 93 | at [PyPI - the Python Package Index](http://pypi.python.org/pypi/PyMunin) 94 | periodically. 95 | 96 | You can download the latest development version of this code that is hosted 97 | at [GitHub](https://github.com/{{ site.user }}/{{ page.prjname }}) either 98 | in [ZIP](https://github.com/aouyar/PyMunin/zipball/master) 99 | or [TAR](https://github.com/aouyar/PyMunin/tarball/master) 100 | format. 101 | 102 | You can also get the latest development version of the code by cloning 103 | the [Git](http://git-scm.com) repository for the project by running: 104 | 105 | git clone git://github.com/aouyar/PyMunin 106 | 107 | 108 | Installation 109 | ------------ 110 | 111 | The easiest way to install the code is to use [pip](http://www.pip-installer.org/). 112 | 113 | Install the newest version from [PyPI](http://pypi.python.org/pypi/PyMunin): 114 | 115 | pip install PyMunin 116 | 117 | Install the latest development version: 118 | 119 | pip install git+https://github.com/aouyar/PyMunin.git#egg=PyMunin 120 | 121 | The other option is to download and uncompress the code manually and execute the 122 | included _setup.py_ script for installation: 123 | 124 | ./setup.py install 125 | 126 | 127 | Collaboration 128 | ------------- 129 | 130 | I would be happy to receive suggestions on improving the code for developing 131 | Munin Plugins. Alternatively you can use the _Issues_ functionality of _GitHub_ 132 | to document problems and to propose improvements. You can use the internal 133 | messaging system of _GitHub_ or my e-mail address in case you prefer to 134 | contact me directly. 135 | 136 | I hope that by sharing the code, the existing plugins will get more testing and 137 | receive improvements, and many more Multigraph plugins will be developed 138 | collaboratively. 139 | 140 | I would be glad to receive some sample graphs from anyone using the plugins. 141 | 142 | 143 | Credits 144 | ------- 145 | 146 | _PyMunin_ has been developed 147 | by [aouyar](https://github.com/aouyar) (Ali Onur Uyar). 148 | 149 | Some of the people that have knowingly or unknowingly contributed with the 150 | development are: 151 | 152 | * Initial packaging of the code was done by Mark Lavin 153 | ([mlavin](https://github.com/mlavin)). 154 | PyMunin is installable pip / easy_install thanks to Mark. :-) 155 | * _PyMunin_ has been packaged for _Fedora_ and _Red Hat Enterprise Linux_ by 156 | [Matthias Runge](www.matthias-runge.de). 157 | * The initial design of the solution was inspired by 158 | [python-munin](https://github.com/samuel/python-munin) 159 | by [Samuel Stauffer](https://github.com/samuel). 160 | * The Rackspace Cloud plugin was initially developed 161 | by [Brian Welsh](https://github.com/palewire). 162 | * [Sebastian Rojo](https://github.com/arpagon) has contributed 163 | many improvements to the Asterisk Plugin. 164 | * [Preston Mason](https://github.com/pentie) has made significant contributions to 165 | the Varnish Cache and PHP APC Cache Plugins. 166 | * Many plugins were inspired by existing _Munin Plugins_developed by other 167 | people. (Before developing any plugins, I always try to check existing 168 | solutions.) 169 | * Many people have contributed by testing the plugins and identifying issues. 170 | 171 | I hope that more people will be using PyMunin for developing plugins in the 172 | future. 173 | -------------------------------------------------------------------------------- /pymunin/plugins/lighttpdstats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """lighttpdstats - Munin Plugin to monitor stats for Lighttpd Web Server. 3 | 4 | 5 | Requirements 6 | 7 | - Access to Lighttpd Web Server server-status page. 8 | 9 | 10 | Wild Card Plugin - No 11 | 12 | 13 | Multigraph Plugin - Graph Structure 14 | 15 | - lighttpd_access 16 | - lighttpd_bytes 17 | - lighttpd_servers 18 | 19 | 20 | Environment Variables 21 | 22 | host: Lighttpd Web Server Host. (Default: 127.0.0.1) 23 | port: Lighttpd Web Server Port. (Default: 80, SSL: 443) 24 | user: User in case authentication is required for access to 25 | server-status page. 26 | password: Password in case authentication is required for access 27 | to server-status page. 28 | statuspath: Path for Lighttpd Web Server Status Page. 29 | (Default: server-status) 30 | ssl: Use SSL if yes. (Default: no) 31 | include_graphs: Comma separated list of enabled graphs. 32 | (All graphs enabled by default.) 33 | exclude_graphs: Comma separated list of disabled graphs. 34 | 35 | Environment Variables for Multiple Instances of Plugin (Omitted by default.) 36 | 37 | instance_name: Name of instance. 38 | instance_label: Graph title label for instance. 39 | (Default is the same as instance name.) 40 | instance_label_format: One of the following values: 41 | - suffix (Default) 42 | - prefix 43 | - none 44 | 45 | Example: 46 | [lighttpdstats] 47 | env.exclude_graphs lighttpd_access,lighttpd_load 48 | 49 | """ 50 | # Munin - Magic Markers 51 | #%# family=auto 52 | #%# capabilities=autoconf nosuggest 53 | 54 | import sys 55 | from pymunin import MuninGraph, MuninPlugin, muninMain 56 | from pysysinfo.lighttpd import LighttpdInfo 57 | 58 | __author__ = "Ali Onur Uyar" 59 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 60 | __credits__ = [] 61 | __license__ = "GPL" 62 | __version__ = "0.9.20" 63 | __maintainer__ = "Ali Onur Uyar" 64 | __email__ = "aouyar at gmail.com" 65 | __status__ = "Development" 66 | 67 | 68 | class MuninLighttpdPlugin(MuninPlugin): 69 | """Multigraph Munin Plugin for monitoring Lighttpd Web Server. 70 | 71 | """ 72 | plugin_name = 'lighttpdstats' 73 | isMultigraph = True 74 | isMultiInstance = True 75 | 76 | def __init__(self, argv=(), env=None, debug=False): 77 | """Populate Munin Plugin with MuninGraph instances. 78 | 79 | @param argv: List of command line arguments. 80 | @param env: Dictionary of environment variables. 81 | @param debug: Print debugging messages if True. (Default: False) 82 | 83 | """ 84 | MuninPlugin.__init__(self, argv, env, debug) 85 | 86 | self._host = self.envGet('host') 87 | self._port = self.envGet('port', None, int) 88 | self._user = self.envGet('user') 89 | self._password = self.envGet('password') 90 | self._statuspath = self.envGet('statuspath') 91 | self._ssl = self.envCheckFlag('ssl', False) 92 | self._category = 'Lighttpd' 93 | 94 | if self.graphEnabled('lighttpd_access'): 95 | graph = MuninGraph('Lighttpd Web Server - Throughput (Requests / sec)', 96 | self._category, 97 | info='Throughput in Requests per second for Lighttpd Web Server.', 98 | args='--base 1000 --lower-limit 0') 99 | graph.addField('reqs', 'reqs', draw='LINE2', type='DERIVE', min=0, 100 | info="Requests per second.") 101 | self.appendGraph('lighttpd_access', graph) 102 | 103 | if self.graphEnabled('lighttpd_bytes'): 104 | graph = MuninGraph('Lighttpd Web Server - Througput (bytes/sec)', 105 | self._category, 106 | info='Throughput in bytes per second for Lighttpd Web Server.', 107 | args='--base 1024 --lower-limit 0') 108 | graph.addField('bytes', 'bytes', draw='LINE2', type='DERIVE', min=0) 109 | self.appendGraph('lighttpd_bytes', graph) 110 | 111 | if self.graphEnabled('lighttpd_servers'): 112 | graph = MuninGraph('Lighttpd Web Server - Servers', self._category, 113 | info='Server utilization stats for Lighttpd Web server.', 114 | args='--base 1000 --lower-limit 0') 115 | graph.addField('busy', 'busy', draw='AREASTACK', type='GAUGE', 116 | info="Number of busy servers.") 117 | graph.addField('idle', 'idle', draw='AREASTACK', type='GAUGE', 118 | info="Number of idle servers.") 119 | graph.addField('max', 'max', draw='LINE2', type='GAUGE', 120 | info="Maximum number of servers permitted.", 121 | colour='FF0000') 122 | self.appendGraph('lighttpd_servers', graph) 123 | 124 | def retrieveVals(self): 125 | """Retrieve values for graphs.""" 126 | lighttpdInfo = LighttpdInfo(self._host, self._port, 127 | self._user, self._password, 128 | self._statuspath, self._ssl) 129 | stats = lighttpdInfo.getServerStats() 130 | if self.hasGraph('lighttpd_access'): 131 | self.setGraphVal('lighttpd_access', 'reqs', stats['Total Accesses']) 132 | if self.hasGraph('lighttpd_bytes'): 133 | self.setGraphVal('lighttpd_bytes', 'bytes', 134 | stats['Total kBytes'] * 1000) 135 | if self.hasGraph('lighttpd_servers'): 136 | self.setGraphVal('lighttpd_servers', 'busy', stats['BusyServers']) 137 | self.setGraphVal('lighttpd_servers', 'idle', stats['IdleServers']) 138 | self.setGraphVal('lighttpd_servers', 'max', stats['MaxServers']) 139 | 140 | def autoconf(self): 141 | """Implements Munin Plugin Auto-Configuration Option. 142 | 143 | @return: True if plugin can be auto-configured, False otherwise. 144 | 145 | """ 146 | lighttpdInfo = LighttpdInfo(self._host, self._port, 147 | self._user, self._password, 148 | self._statuspath, self._ssl) 149 | return lighttpdInfo is not None 150 | 151 | 152 | def main(): 153 | sys.exit(muninMain(MuninLighttpdPlugin)) 154 | 155 | 156 | if __name__ == "__main__": 157 | main() 158 | -------------------------------------------------------------------------------- /pymunin/plugins/diskusagestats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """diskusagestats - Munin Plugin to monitor disk space and inode usage of 3 | filesystems. 4 | 5 | Requirements 6 | 7 | - Root user privileges may be required to access stats for filesystems 8 | without any read access for munin user. 9 | 10 | 11 | Wild Card Plugin - No 12 | 13 | 14 | Multigraph Plugin - Graph Structure 15 | 16 | - diskspace 17 | - diskinode 18 | 19 | 20 | Environment Variables 21 | 22 | include_fspaths: Comma separated list of filesystems to include in monitoring. 23 | (All enabled by default.) 24 | exclude_fspaths: Comma separated list of filesystems to exclude from monitoring. 25 | include_fstypes: Comma separated list of filesystem types to include in 26 | monitoring. (All enabled by default.) 27 | exclude_fstypes: Comma separated list of filesystem types to exclude from 28 | monitoring. 29 | include_graphs: Comma separated list of enabled graphs. 30 | (All graphs enabled by default.) 31 | exclude_graphs: Comma separated list of disabled graphs. 32 | 33 | 34 | Example: 35 | [diskusagestats] 36 | env.exclude_graphs diskinode 37 | env.exclude_fstype tmpfs 38 | 39 | """ 40 | # Munin - Magic Markers 41 | #%# family=auto 42 | #%# capabilities=autoconf nosuggest 43 | 44 | import sys 45 | from pymunin import (MuninGraph, MuninPlugin, muninMain, 46 | fixLabel, maxLabelLenGraphSimple) 47 | from pysysinfo.filesystem import FilesystemInfo 48 | 49 | __author__ = "Ali Onur Uyar" 50 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 51 | __credits__ = [] 52 | __license__ = "GPL" 53 | __version__ = "0.9" 54 | __maintainer__ = "Ali Onur Uyar" 55 | __email__ = "aouyar at gmail.com" 56 | __status__ = "Development" 57 | 58 | 59 | class MuninDiskUsagePlugin(MuninPlugin): 60 | """Multigraph Munin Plugin for Disk Usage of filesystems. 61 | 62 | """ 63 | plugin_name = 'diskusagestats' 64 | isMultigraph = True 65 | 66 | def __init__(self, argv=(), env=None, debug=False): 67 | """Populate Munin Plugin with MuninGraph instances. 68 | 69 | @param argv: List of command line arguments. 70 | @param env: Dictionary of environment variables. 71 | @param debug: Print debugging messages if True. (Default: False) 72 | 73 | """ 74 | MuninPlugin.__init__(self, argv, env, debug) 75 | 76 | self.envRegisterFilter('fspaths', '^[\w\-\/]+$') 77 | self.envRegisterFilter('fstypes', '^\w+$') 78 | self._category = 'Disk Usage' 79 | 80 | self._statsSpace = None 81 | self._statsInode = None 82 | self._info = FilesystemInfo() 83 | 84 | self._fslist = [fs for fs in self._info.getFSlist() 85 | if (self.fsPathEnabled(fs) 86 | and self.fsTypeEnabled(self._info.getFStype(fs)))] 87 | self._fslist.sort() 88 | 89 | name = 'diskspace' 90 | if self.graphEnabled(name): 91 | self._statsSpace = self._info.getSpaceUse() 92 | graph = MuninGraph('Disk Space Usage (%)', self._category, 93 | info='Disk space usage of filesystems.', 94 | args='--base 1000 --lower-limit 0', printf='%6.1lf', 95 | autoFixNames=True) 96 | for fspath in self._fslist: 97 | if self._statsSpace.has_key(fspath): 98 | graph.addField(fspath, 99 | fixLabel(fspath, maxLabelLenGraphSimple, 100 | delim='/', repl='..', truncend=False), 101 | draw='LINE2', type='GAUGE', 102 | info="Disk space usage for: %s" % fspath) 103 | self.appendGraph(name, graph) 104 | 105 | name = 'diskinode' 106 | if self.graphEnabled(name): 107 | self._statsInode = self._info.getInodeUse() 108 | graph = MuninGraph('Inode Usage (%)', self._category, 109 | info='Inode usage of filesystems.', 110 | args='--base 1000 --lower-limit 0', printf='%6.1lf', 111 | autoFixNames=True) 112 | for fspath in self._fslist: 113 | if self._statsInode.has_key(fspath): 114 | graph.addField(fspath, 115 | fixLabel(fspath, maxLabelLenGraphSimple, 116 | delim='/', repl='..', truncend=False), 117 | draw='LINE2', type='GAUGE', 118 | info="Inode usage for: %s" % fspath) 119 | self.appendGraph(name, graph) 120 | 121 | def retrieveVals(self): 122 | """Retrieve values for graphs.""" 123 | name = 'diskspace' 124 | if self.hasGraph(name): 125 | for fspath in self._fslist: 126 | if self._statsSpace.has_key(fspath): 127 | self.setGraphVal(name, fspath, 128 | self._statsSpace[fspath]['inuse_pcent']) 129 | name = 'diskinode' 130 | if self.hasGraph(name): 131 | for fspath in self._fslist: 132 | if self._statsInode.has_key(fspath): 133 | self.setGraphVal(name, fspath, 134 | self._statsInode[fspath]['inuse_pcent']) 135 | 136 | def fsPathEnabled(self, fspath): 137 | """Utility method to check if a filesystem path is included in monitoring. 138 | 139 | @param fspath: Filesystem path. 140 | @return: Returns True if included in graphs, False otherwise. 141 | 142 | """ 143 | return self.envCheckFilter('fspaths', fspath) 144 | 145 | def fsTypeEnabled(self, fstype): 146 | """Utility method to check if a filesystem type is included in monitoring. 147 | 148 | @param fstype: Filesystem type. 149 | @return: Returns True if included in graphs, False otherwise. 150 | 151 | """ 152 | return self.envCheckFilter('fstypes', fstype) 153 | 154 | def autoconf(self): 155 | """Implements Munin Plugin Auto-Configuration Option. 156 | 157 | @return: True if plugin can be auto-configured, False otherwise. 158 | 159 | """ 160 | # If no exception is thrown during initialization, the plugin should work. 161 | return True 162 | 163 | 164 | def main(): 165 | sys.exit(muninMain(MuninDiskUsagePlugin)) 166 | 167 | 168 | if __name__ == "__main__": 169 | main() 170 | -------------------------------------------------------------------------------- /pymunin/plugins/netstats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """netstats - Munin Plugin to monitor network stats. 3 | 4 | 5 | Requirements 6 | 7 | - netstat command 8 | 9 | Wild Card Plugin - No 10 | 11 | 12 | Multigraph Plugin - Graph Structure 13 | 14 | - netstat_conn_status 15 | - netstat_conn_server 16 | 17 | 18 | Environment Variables 19 | 20 | list_server_ports: Comma separated list of Name:PortNumber tuples for services 21 | that are to be monitored in the netstat_server_conn graph. 22 | A service can be associated to multiple port numbers 23 | separated by colon. 24 | include_graphs: Comma separated list of enabled graphs. 25 | (All graphs enabled by default.) 26 | exclude_graphs: Comma separated list of disabled graphs. 27 | 28 | 29 | Example: 30 | [netstats] 31 | env.include_graphs netstat_conn_server 32 | env.server_ports www:80:443,mysql:3306 33 | 34 | """ 35 | # Munin - Magic Markers 36 | #%# family=auto 37 | #%# capabilities=autoconf nosuggest 38 | 39 | import sys 40 | from pymunin import MuninGraph, MuninPlugin, muninMain 41 | from pysysinfo.netstat import NetstatInfo 42 | 43 | __author__ = "Ali Onur Uyar" 44 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 45 | __credits__ = [] 46 | __license__ = "GPL" 47 | __version__ = "0.9" 48 | __maintainer__ = "Ali Onur Uyar" 49 | __email__ = "aouyar at gmail.com" 50 | __status__ = "Development" 51 | 52 | 53 | class MuninNetstatsPlugin(MuninPlugin): 54 | """Multigraph Munin Plugin for monitoring Network Stats. 55 | 56 | """ 57 | plugin_name = 'netstats' 58 | isMultigraph = True 59 | 60 | def __init__(self, argv=(), env=None, debug=False): 61 | """Populate Munin Plugin with MuninGraph instances. 62 | 63 | @param argv: List of command line arguments. 64 | @param env: Dictionary of environment variables. 65 | @param debug: Print debugging messages if True. (Default: False) 66 | 67 | """ 68 | MuninPlugin.__init__(self, argv, env, debug) 69 | self._category = 'Network' 70 | 71 | if self.graphEnabled('netstat_conn_status'): 72 | graph = MuninGraph('Network - Connection Status', self._category, 73 | info='TCP connection status stats.', 74 | args='--base 1000 --lower-limit 0') 75 | for (fname, fdesc) in ( 76 | ('listen', 'Socket listening for incoming connections.'), 77 | ('established', 'Socket with established connection.'), 78 | ('syn_sent', 'Socket actively attempting connection.'), 79 | ('syn_recv', 'Socket that has received a connection request' 80 | ' from network.'), 81 | ('fin_wait1', 'Connection closed, and connection shutting down.'), 82 | ('fin_wait2', 'Connection is closed, and the socket is waiting' 83 | ' for a shutdown from the remote end.'), 84 | ('time_wait', 'Socket is waiting after close ' 85 | 'to handle packets still in the network.'), 86 | ('close', 'Socket is not being used.'), 87 | ('close_wait', 'The remote end has shut down, ' 88 | 'waiting for the socket to close.'), 89 | ('last_ack', 'The remote end has shut down, and the socket' 90 | ' is closed. Waiting for acknowledgement.'), 91 | ('closing', 'Both sockets are shut down' 92 | ' but not all data is sent yet.'), 93 | ('unknown', 'Sockets with unknown state.'), 94 | ): 95 | graph.addField(fname, fname, type='GAUGE', draw='AREA', 96 | info=fdesc) 97 | self.appendGraph('netstat_conn_status', graph) 98 | 99 | if self.graphEnabled('netstat_server_conn'): 100 | self._srv_dict = {} 101 | self._srv_list = [] 102 | self._port_list = [] 103 | for srv_str in self.envGetList('server_ports', '(\w+)(:\d+)+$'): 104 | elems = srv_str.split(':') 105 | if len(elems) > 1: 106 | srv = elems[0] 107 | ports = elems[1:] 108 | self._srv_list.append(srv) 109 | self._srv_dict[srv] = ports 110 | self._port_list.extend(ports) 111 | self._srv_list.sort() 112 | if len(self._srv_list) > 0: 113 | graph = MuninGraph('Network - Server Connections', self._category, 114 | info='Number of TCP connections to server ports.', 115 | args='--base 1000 --lower-limit 0') 116 | for srv in self._srv_list: 117 | graph.addField(srv, srv, type='GAUGE', draw='AREA', 118 | info=('Number of connections for service %s on ports: %s' 119 | % (srv, ','.join(self._srv_dict[srv])))) 120 | self.appendGraph('netstat_conn_server', graph) 121 | 122 | def retrieveVals(self): 123 | """Retrieve values for graphs.""" 124 | net_info = NetstatInfo() 125 | if self.hasGraph('netstat_conn_status'): 126 | stats = net_info.getTCPportConnStatus(include_listen=True) 127 | for fname in ('listen', 'established', 'syn_sent', 'syn_recv', 128 | 'fin_wait1', 'fin_wait2', 'time_wait', 129 | 'close','close_wait', 'last_ack', 'closing', 130 | 'unknown',): 131 | self.setGraphVal('netstat_conn_status', fname, 132 | stats.get(fname,0)) 133 | if self.hasGraph('netstat_conn_server'): 134 | stats = net_info.getTCPportConnCount(localport=self._port_list) 135 | for srv in self._srv_list: 136 | numconn = 0 137 | for port in self._srv_dict[srv]: 138 | numconn += stats.get(port, 0) 139 | self.setGraphVal('netstat_conn_server', srv, numconn) 140 | 141 | def autoconf(self): 142 | """Implements Munin Plugin Auto-Configuration Option. 143 | 144 | @return: True if plugin can be auto-configured, False otherwise. 145 | 146 | """ 147 | net_info = NetstatInfo() 148 | return len(net_info.getStats()) > 0 149 | 150 | 151 | def main(): 152 | sys.exit(muninMain(MuninNetstatsPlugin)) 153 | 154 | 155 | if __name__ == "__main__": 156 | main() 157 | -------------------------------------------------------------------------------- /pymunin/plugins/netifacestats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """netifacestats - Munin Plugin to monitor Network Interfaces. 3 | 4 | 5 | Requirements 6 | 7 | 8 | Wild Card Plugin - No 9 | 10 | 11 | Multigraph Plugin - Graph Structure 12 | 13 | - netiface_traffic 14 | - netiface_errors 15 | 16 | 17 | Environment Variables 18 | 19 | include_ifaces: Comma separated list of network interfaces to include in 20 | graphs. (All Network Interfaces are monitored by default.) 21 | exclude_ifaces: Comma separated list of network interfaces to exclude from 22 | graphs. 23 | include_graphs: Comma separated list of enabled graphs. 24 | (All graphs enabled by default.) 25 | exclude_graphs: Comma separated list of disabled graphs. 26 | 27 | Example: 28 | [netifacestats] 29 | env.include_ifaces eth0,eth1 30 | env.exclude_graphs netiface_errors 31 | 32 | """ 33 | # Munin - Magic Markers 34 | #%# family=auto 35 | #%# capabilities=autoconf nosuggest 36 | 37 | import sys 38 | from pymunin import MuninGraph, MuninPlugin, muninMain 39 | from pysysinfo.netiface import NetIfaceInfo 40 | 41 | __author__ = "Ali Onur Uyar" 42 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 43 | __credits__ = [] 44 | __license__ = "GPL" 45 | __version__ = "0.9" 46 | __maintainer__ = "Ali Onur Uyar" 47 | __email__ = "aouyar at gmail.com" 48 | __status__ = "Development" 49 | 50 | 51 | class MuninNetIfacePlugin(MuninPlugin): 52 | """Multigraph Munin Plugin for monitoring Network Interfaces. 53 | 54 | """ 55 | plugin_name = 'netifacestats' 56 | isMultigraph = True 57 | 58 | def __init__(self, argv=(), env=None, debug=False): 59 | """Populate Munin Plugin with MuninGraph instances. 60 | 61 | @param argv: List of command line arguments. 62 | @param env: Dictionary of environment variables. 63 | @param debug: Print debugging messages if True. (Default: False) 64 | 65 | """ 66 | MuninPlugin.__init__(self, argv, env, debug) 67 | 68 | self.envRegisterFilter('ifaces', '^[\w\d:]+$') 69 | self._category = 'Network' 70 | 71 | self._ifaceInfo = NetIfaceInfo() 72 | self._ifaceStats = self._ifaceInfo.getIfStats() 73 | self._ifaceList = [] 74 | for iface in list(self._ifaceStats): 75 | if iface not in ['lo',] and self.ifaceIncluded(iface): 76 | if max(self._ifaceStats[iface].values()) > 0: 77 | self._ifaceList.append(iface) 78 | self._ifaceList.sort() 79 | 80 | for iface in self._ifaceList: 81 | if self.graphEnabled('netiface_traffic'): 82 | graph = MuninGraph('Network Interface - Traffic - %s' % iface, 83 | self._category, 84 | info='Traffic Stats for Network Interface %s in bps.' % iface, 85 | args='--base 1000 --lower-limit 0', 86 | vlabel='bps in (-) / out (+) per second') 87 | graph.addField('rx', 'bps', draw='LINE2', type='DERIVE', 88 | min=0, graph=False) 89 | graph.addField('tx', 'bps', draw='LINE2', type='DERIVE', 90 | min=0, negative='rx') 91 | self.appendGraph('netiface_traffic_%s' % iface, graph) 92 | 93 | if self.graphEnabled('netiface_errors'): 94 | graph = MuninGraph('Network Interface - Errors - %s' % iface, 95 | self._category, 96 | info='Error Stats for Network Interface %s in errors/sec.' % iface, 97 | args='--base 1000 --lower-limit 0', 98 | vlabel='errors in (-) / out (+) per second') 99 | graph.addField('rxerrs', 'errors', draw='LINE2', type='DERIVE', 100 | min=0, graph=False) 101 | graph.addField('txerrs', 'errors', draw='LINE2', type='DERIVE', 102 | min=0, negative='rxerrs', 103 | info='Rx(-)/Tx(+) Errors per second.') 104 | graph.addField('rxframe', 'frm/crr', draw='LINE2', type='DERIVE', 105 | min=0, graph=False) 106 | graph.addField('txcarrier', 'frm/crr', draw='LINE2', type='DERIVE', 107 | min=0, negative='rxframe', 108 | info='Frame(-)/Carrier(+) Errors per second.') 109 | graph.addField('rxdrop', 'drop', draw='LINE2', type='DERIVE', 110 | min=0, graph=False) 111 | graph.addField('txdrop', 'drop', draw='LINE2', type='DERIVE', 112 | min=0, negative='rxdrop', 113 | info='Rx(-)/Tx(+) Dropped Packets per second.') 114 | graph.addField('rxfifo', 'fifo', draw='LINE2', type='DERIVE', 115 | min=0, graph=False) 116 | graph.addField('txfifo', 'fifo', draw='LINE2', type='DERIVE', 117 | min=0, negative='rxfifo', 118 | info='Rx(-)/Tx(+) FIFO Errors per second.') 119 | self.appendGraph('netiface_errors_%s' % iface, graph) 120 | 121 | 122 | def retrieveVals(self): 123 | """Retrieve values for graphs.""" 124 | for iface in self._ifaceList: 125 | stats = self._ifaceStats.get(iface) 126 | graph_name = 'netiface_traffic_%s' % iface 127 | if self.hasGraph(graph_name): 128 | self.setGraphVal(graph_name, 'rx', stats.get('rxbytes') * 8) 129 | self.setGraphVal(graph_name, 'tx', stats.get('txbytes') * 8) 130 | graph_name = 'netiface_errors_%s' % iface 131 | if self.hasGraph(graph_name): 132 | for field in ('rxerrs', 'txerrs', 'rxframe', 'txcarrier', 133 | 'rxdrop', 'txdrop', 'rxfifo', 'txfifo'): 134 | self.setGraphVal(graph_name, field, stats.get(field)) 135 | 136 | def ifaceIncluded(self, iface): 137 | """Utility method to check if interface is included in monitoring. 138 | 139 | @param iface: Interface name. 140 | @return: Returns True if included in graphs, False otherwise. 141 | 142 | """ 143 | return self.envCheckFilter('ifaces', iface) 144 | 145 | def autoconf(self): 146 | """Implements Munin Plugin Auto-Configuration Option. 147 | 148 | @return: True if plugin can be auto-configured, False otherwise. 149 | 150 | """ 151 | return len(self._ifaceList) > 0 152 | 153 | 154 | def main(): 155 | sys.exit(muninMain(MuninNetIfacePlugin)) 156 | 157 | 158 | if __name__ == "__main__": 159 | main() 160 | -------------------------------------------------------------------------------- /pysysinfo/squid.py: -------------------------------------------------------------------------------- 1 | """Implements SquidInfo Class for gathering stats from Squid Proxy Server. 2 | 3 | The statistics are obtained by connecting to and querying local and/or 4 | remote Squid Proxy Servers. 5 | 6 | """ 7 | 8 | import sys 9 | import re 10 | import httplib 11 | import urllib 12 | import util 13 | 14 | __author__ = "Ali Onur Uyar" 15 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 16 | __credits__ = [] 17 | __license__ = "GPL" 18 | __version__ = "0.9" 19 | __maintainer__ = "Ali Onur Uyar" 20 | __email__ = "aouyar at gmail.com" 21 | __status__ = "Development" 22 | 23 | 24 | defaultSquidPort = 3128 25 | defaultTimeout = 8 26 | buffSize = 4096 27 | 28 | memMultiplier = {'G': 1024 * 1024 * 1024, 'M':1024 * 1024, 'K':1024} 29 | 30 | 31 | def parse_value(val): 32 | """Parse input string and return int, float or str depending on format. 33 | 34 | @param val: Input string. 35 | @return: Value of type int, float or str. 36 | 37 | """ 38 | 39 | mobj = re.match('(-{0,1}\d+)\s*(\sseconds|/\s*\w+)$', val) 40 | if mobj: 41 | return int(mobj.group(1)) 42 | mobj = re.match('(-{0,1}\d*\.\d+)\s*(\sseconds|/\s*\w+)$', val) 43 | if mobj: 44 | return float(mobj.group(1)) 45 | re.match('(-{0,1}\d+)\s*([GMK])B$', val) 46 | if mobj: 47 | return int(mobj.group(1)) * memMultiplier[mobj.group(2)] 48 | mobj = re.match('(-{0,1}\d+(\.\d+){0,1})\s*\%$', val) 49 | if mobj: 50 | return float(mobj.group(1)) / 100 51 | return val 52 | 53 | 54 | class SquidInfo: 55 | """Class to retrieve stats from Squid Proxy Server.""" 56 | 57 | def __init__(self, host=None, port=None, user=None, password=None, 58 | autoInit=True): 59 | """Initialize Squid Proxy Manager access. 60 | 61 | @param host: Squid Proxy Host. (Default: 127.0.0.1) 62 | @param port: Squid Proxy Port. (Default: 3128) 63 | @param user: Squid Proxy Manager User. 64 | @param password: Squid Proxy Manager Password. 65 | @param autoInit: If True connect to Apache Tomcat Server on instantiation. 66 | 67 | """ 68 | self._conn = None 69 | if host is not None: 70 | self._host = host 71 | else: 72 | self._host = '127.0.0.1' 73 | if port is not None: 74 | self._port = port 75 | else: 76 | self._port = defaultSquidPort 77 | self._httpHeaders = {'Accept': '*/*',} 78 | if user is not None and password is not None: 79 | authstr = "%s:%s" % (urllib.quote(user), urllib.quote(password)) 80 | self._httpHeaders['Authorization'] = "Basic %s" % authstr 81 | self._httpHeaders['Proxy-Authorization'] = "Basic %s" % authstr 82 | if autoInit: 83 | self._connect() 84 | 85 | def __del__(self): 86 | """Cleanup.""" 87 | if self._conn is not None: 88 | self._conn.close() 89 | 90 | def _connect(self): 91 | """Connect to Squid Proxy Manager interface.""" 92 | if sys.version_info[:2] < (2,6): 93 | self._conn = httplib.HTTPConnection(self._host, self._port) 94 | else: 95 | self._conn = httplib.HTTPConnection(self._host, self._port, 96 | False, defaultTimeout) 97 | 98 | def _retrieve(self, map): 99 | """Query Squid Proxy Server Manager Interface for stats. 100 | 101 | @param map: Statistics map name. 102 | @return: Dictionary of query results. 103 | 104 | """ 105 | self._conn.request('GET', "cache_object://%s/%s" % (self._host, map), 106 | None, self._httpHeaders) 107 | rp = self._conn.getresponse() 108 | if rp.status == 200: 109 | data = rp.read() 110 | return data 111 | else: 112 | raise Exception("Retrieval of stats from Squid Proxy Server" 113 | "on host %s and port %s failed.\n" 114 | "HTTP - Status: %s Reason: %s" 115 | % (self._host, self._port, rp.status, rp.reason)) 116 | 117 | def _parseCounters(self, data): 118 | """Parse simple stats list of key, value pairs. 119 | 120 | @param data: Multiline data with one key-value pair in each line. 121 | @return: Dictionary of stats. 122 | 123 | """ 124 | info_dict = util.NestedDict() 125 | for line in data.splitlines(): 126 | mobj = re.match('^\s*([\w\.]+)\s*=\s*(\S.*)$', line) 127 | if mobj: 128 | (key, value) = mobj.groups() 129 | klist = key.split('.') 130 | info_dict.set_nested(klist, parse_value(value)) 131 | return info_dict 132 | 133 | def _parseSections(self, data): 134 | """Parse data and separate sections. Returns dictionary that maps 135 | section name to section data. 136 | 137 | @param data: Multiline data. 138 | @return: Dictionary that maps section names to section data. 139 | 140 | """ 141 | section_dict = {} 142 | lines = data.splitlines() 143 | idx = 0 144 | numlines = len(lines) 145 | section = None 146 | while idx < numlines: 147 | line = lines[idx] 148 | idx += 1 149 | mobj = re.match('^(\w[\w\s\(\)]+[\w\)])\s*:$', line) 150 | if mobj: 151 | section = mobj.group(1) 152 | section_dict[section] = [] 153 | else: 154 | mobj = re.match('(\t|\s)\s*(\w.*)$', line) 155 | if mobj: 156 | section_dict[section].append(mobj.group(2)) 157 | else: 158 | mobj = re.match('^(\w[\w\s\(\)]+[\w\)])\s*:\s*(\S.*)$', line) 159 | if mobj: 160 | section = None 161 | if not section_dict.has_key(section): 162 | section_dict[section] = [] 163 | section_dict[section].append(line) 164 | else: 165 | if not section_dict.has_key('PARSEERROR'): 166 | section_dict['PARSEERROR'] = [] 167 | section_dict['PARSEERROR'].append(line) 168 | return section_dict 169 | 170 | def getMenu(self): 171 | """Get manager interface section list from Squid Proxy Server 172 | 173 | @return: List of tuples (section, description, type) 174 | 175 | """ 176 | data = self._retrieve('') 177 | info_list = [] 178 | for line in data.splitlines(): 179 | mobj = re.match('^\s*(\S.*\S)\s*\t\s*(\S.*\S)\s*\t\s*(\S.*\S)$', line) 180 | if mobj: 181 | info_list.append(mobj.groups()) 182 | return info_list 183 | 184 | def getCounters(self): 185 | """Get Traffic and Resource Counters from Squid Proxy Server. 186 | 187 | @return: Dictionary of stats. 188 | 189 | """ 190 | data = self._retrieve('counters') 191 | return self._parseCounters(data) 192 | 193 | def getInfo(self): 194 | """Get General Run-time Information from Squid Proxy Server. 195 | 196 | @return: Dictionary of stats. 197 | 198 | """ 199 | data = self._retrieve('info') 200 | return data 201 | -------------------------------------------------------------------------------- /pymunin/plugins/phpopcstats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """phpopcstats - Munin Plugin for monitoring PHP Zend-OPCache. 3 | 4 | 5 | Requirements 6 | 7 | - The PHP script opcinfo.php must be placed in the document root and have 8 | access permissions from localhost. 9 | 10 | 11 | Wild Card Plugin - No 12 | 13 | 14 | Multigraph Plugin - Graph Structure 15 | 16 | - php_opc_memory 17 | - php_opc_key_status 18 | - php_opc_opcache_statistics 19 | - php_opc_opcache_hitrate 20 | 21 | 22 | Environment Variables 23 | 24 | host: Web Server Host. (Default: 127.0.0.1) 25 | port: Web Server Port. (Default: 80, SSL: 443) 26 | user: User in case authentication is required for access to 27 | APC Status page. 28 | password: Password in case authentication is required for access to 29 | APC Status page. 30 | monpath: APC status script path relative to Document Root. 31 | (Default: opcinfo.php) 32 | ssl: Use SSL if yes. (Default: no) 33 | include_graphs: Comma separated list of enabled graphs. 34 | (All graphs enabled by default.) 35 | exclude_graphs: Comma separated list of disabled graphs. 36 | 37 | Environment Variables for Multiple Instances of Plugin (Omitted by default.) 38 | 39 | instance_name: Name of instance. 40 | instance_label: Graph title label for instance. 41 | (Default is the same as instance name.) 42 | instance_label_format: One of the following values: 43 | - suffix (Default) 44 | - prefix 45 | - none 46 | 47 | Example: 48 | [phpopcstats] 49 | env.exclude_graphs php_opc_key_status,php_opc_opcache_statistics 50 | 51 | """ 52 | # Munin - Magic Markers 53 | #%# family=auto 54 | #%# capabilities=autoconf nosuggest 55 | 56 | import sys 57 | from pymunin import MuninGraph, MuninPlugin, muninMain 58 | from pysysinfo.phpopc import OPCinfo 59 | 60 | __author__ = "Preston M." 61 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 62 | __credits__ = [] 63 | __license__ = "GPL" 64 | __version__ = "0.9.24" 65 | __maintainer__ = "Preston M." 66 | __email__ = "pentie at gmail.com" 67 | __status__ = "Development" 68 | 69 | 70 | class MuninPHPOPCPlugin(MuninPlugin): 71 | """Multigraph Munin Plugin for monitoring APC PHP Cache. 72 | 73 | """ 74 | plugin_name = 'phpopcstats' 75 | isMultigraph = True 76 | isMultiInstance = True 77 | 78 | def __init__(self, argv=(), env=None, debug=False): 79 | """Populate Munin Plugin with MuninGraph instances. 80 | 81 | @param argv: List of command line arguments. 82 | @param env: Dictionary of environment variables. 83 | @param debug: Print debugging messages if True. (Default: False) 84 | 85 | """ 86 | MuninPlugin.__init__(self, argv, env, debug) 87 | 88 | self._host = self.envGet('host') 89 | self._port = self.envGet('port', None, int) 90 | self._user = self.envGet('user') 91 | self._monpath = self.envGet('monpath') 92 | self._password = self.envGet('password') 93 | self._ssl = self.envCheckFlag('ssl', False) 94 | self._category = 'PHP' 95 | 96 | graph_name = 'php_opc_memory' 97 | if self.graphEnabled(graph_name): 98 | graph = MuninGraph('PHP Zend OPCache - Memory Usage (bytes)', self._category, 99 | info='Memory usage of Zend OPCache in bytes.', 100 | total='Total Memory', 101 | args='--base 1024 --lower-limit 0') 102 | graph.addField('used_memory', 'Used Memory', draw='AREASTACK', 103 | type='GAUGE',colour='FFCC33') 104 | graph.addField('wasted_memory', 'Wasted Memory', draw='AREASTACK', 105 | type='GAUGE', colour='FF3333') 106 | graph.addField('free_memory', 'Free Memory', draw='AREASTACK', 107 | type='GAUGE', colour='3790E8') 108 | 109 | self.appendGraph(graph_name, graph) 110 | 111 | graph_name = 'php_opc_opcache_statistics' 112 | if self.graphEnabled(graph_name): 113 | graph = MuninGraph('PHP Zend OPCache - Opcache Statistics', self._category, 114 | info='Hits and Misses of Zend OPCache Opcache.', 115 | args='--base 1000 --lower-limit 0') 116 | graph.addField('hits', 'hits', draw='AREASTACK', 117 | type='DERIVE', min=0, colour='3790E8') 118 | graph.addField('misses', 'misses', draw='AREASTACK', 119 | type='DERIVE', min=0, colour='FF3333') 120 | self.appendGraph(graph_name, graph) 121 | 122 | graph_name = 'php_opc_opcache_hitrate' 123 | if self.graphEnabled(graph_name): 124 | graph = MuninGraph('PHP Zend OPCache - Hit Percent', self._category, 125 | info='Hit percent for PHP Zend OPCache.', 126 | vlabel='%', args='--base 1000 --lower-limit 0') 127 | graph.addField('opcache_hit_rate', 'Hit Percentage', draw='LINE2', type='GAUGE', 128 | info='Hit Percentage', min=0) 129 | 130 | self.appendGraph(graph_name, graph) 131 | 132 | 133 | graph_name = 'php_opc_key_status' 134 | if self.graphEnabled(graph_name): 135 | graph = MuninGraph('PHP Zend OPCache - Key Statistics', self._category, 136 | info='Key usage of Zend OPCache Opcache.', 137 | total='Total Keys', 138 | args='--base 1000 --lower-limit 0') 139 | graph.addField('num_cached_scripts', 'Used Key (for scripts)', draw='AREASTACK', 140 | type='GAUGE', min=0, colour='FFCC33') 141 | graph.addField('num_wasted_keys', 'Wasted Key', draw='AREASTACK', 142 | type='GAUGE', colour='FF3333') 143 | graph.addField('num_free_keys', 'Free Key', draw='AREASTACK', 144 | type='GAUGE', colour='3790E8') 145 | 146 | self.appendGraph(graph_name, graph) 147 | 148 | def retrieveVals(self): 149 | """Retrieve values for graphs.""" 150 | opcinfo = OPCinfo(self._host, self._port, self._user, self._password, 151 | self._monpath, self._ssl) 152 | stats = opcinfo.getAllStats() 153 | 154 | if self.hasGraph('php_opc_memory') and stats: 155 | mem = stats['memory_usage'] 156 | keys = ('used_memory', 'wasted_memory', 'free_memory') 157 | map(lambda k:self.setGraphVal('php_opc_memory',k,mem[k]), keys) 158 | 159 | if self.hasGraph('php_opc_opcache_statistics') and stats: 160 | st = stats['opcache_statistics'] 161 | self.setGraphVal('php_opc_opcache_statistics', 'hits', 162 | st['hits']) 163 | self.setGraphVal('php_opc_opcache_statistics', 'misses', 164 | st['misses']) 165 | 166 | if self.hasGraph('php_opc_opcache_hitrate') and stats: 167 | st = stats['opcache_statistics'] 168 | self.setGraphVal('php_opc_opcache_hitrate', 'opcache_hit_rate', 169 | st['opcache_hit_rate']) 170 | 171 | if self.hasGraph('php_opc_key_status') and stats: 172 | st = stats['opcache_statistics'] 173 | wasted = st['num_cached_keys'] - st['num_cached_scripts'] 174 | free = st['max_cached_keys'] - st['num_cached_keys'] 175 | self.setGraphVal('php_opc_key_status', 'num_cached_scripts', st['num_cached_scripts']) 176 | self.setGraphVal('php_opc_key_status', 'num_wasted_keys', wasted) 177 | self.setGraphVal('php_opc_key_status', 'num_free_keys', free) 178 | 179 | def autoconf(self): 180 | """Implements Munin Plugin Auto-Configuration Option. 181 | 182 | @return: True if plugin can be auto-configured, False otherwise. 183 | 184 | """ 185 | opcinfo = OPCinfo(self._host, self._port, self._user, self._password, 186 | self._monpath, self._ssl) 187 | return opcinfo is not None 188 | 189 | 190 | def main(): 191 | sys.exit(muninMain(MuninPHPOPCPlugin)) 192 | 193 | 194 | if __name__ == "__main__": 195 | main() 196 | -------------------------------------------------------------------------------- /pymunin/plugins/nginxstats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """nginxstats - Munin Plugin to monitor stats for Nginx Web Server. 3 | 4 | 5 | Requirements 6 | 7 | - Access to Nginx Web Server server-status page. 8 | 9 | 10 | Wild Card Plugin - No 11 | 12 | 13 | Multigraph Plugin - Graph Structure 14 | 15 | - nginx_activeconn 16 | - nginx_connections 17 | - nginx_requests 18 | - nginx_requestsperconn 19 | 20 | 21 | Environment Variables 22 | 23 | host: Nginx Web Server Host. (Default: 127.0.0.1) 24 | port: Nginx Web Server Port. (Default: 80, SSL: 443) 25 | user: User in case authentication is required for access to 26 | server-status page. 27 | password: Password in case authentication is required for access 28 | to server-status page. 29 | statuspath: Path for Nginx Web Server Status Page. 30 | (Default: server-status) 31 | ssl: Use SSL if yes. (Default: no) 32 | samples: Number of samples to collect for calculating running averages. 33 | (Six samples for 30 minute running average are stored by 34 | default.) 35 | include_graphs: Comma separated list of enabled graphs. 36 | (All graphs enabled by default.) 37 | exclude_graphs: Comma separated list of disabled graphs. 38 | 39 | Environment Variables for Multiple Instances of Plugin (Omitted by default.) 40 | 41 | instance_name: Name of instance. 42 | instance_label: Graph title label for instance. 43 | (Default is the same as instance name.) 44 | instance_label_format: One of the following values: 45 | - suffix (Default) 46 | - prefix 47 | - none 48 | 49 | Example: 50 | [nginxstats] 51 | env.include_graphs nginx_activeconn 52 | env.samples 3 53 | 54 | """ 55 | # Munin - Magic Markers 56 | #%# family=auto 57 | #%# capabilities=autoconf nosuggest 58 | 59 | import sys 60 | from pymunin import MuninGraph, MuninPlugin, muninMain 61 | from pysysinfo.nginx import NginxInfo 62 | 63 | __author__ = "Ali Onur Uyar" 64 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 65 | __credits__ = [] 66 | __license__ = "GPL" 67 | __version__ = "0.9.20" 68 | __maintainer__ = "Ali Onur Uyar" 69 | __email__ = "aouyar at gmail.com" 70 | __status__ = "Development" 71 | 72 | 73 | defaultNumSamples = 6 74 | """Number of samples to store for calculating the running averages.""" 75 | 76 | 77 | class MuninNginxPlugin(MuninPlugin): 78 | """Multigraph Munin Plugin for monitoring Nginx Web Server. 79 | 80 | """ 81 | plugin_name = 'nginxstats' 82 | isMultigraph = True 83 | isMultiInstance = True 84 | 85 | def __init__(self, argv=(), env=None, debug=False): 86 | """Populate Munin Plugin with MuninGraph instances. 87 | 88 | @param argv: List of command line arguments. 89 | @param env: Dictionary of environment variables. 90 | @param debug: Print debugging messages if True. (Default: False) 91 | 92 | """ 93 | MuninPlugin.__init__(self, argv, env, debug) 94 | 95 | self._host = self.envGet('host') 96 | self._port = self.envGet('port', None, int) 97 | self._user = self.envGet('user') 98 | self._password = self.envGet('password') 99 | self._statuspath = self.envGet('statuspath') 100 | self._ssl = self.envCheckFlag('ssl', False) 101 | self._numSamples = self.envGet('samples', defaultNumSamples, int) 102 | self._category = 'Nginx' 103 | 104 | if self.graphEnabled('nginx_activeconn'): 105 | graph = MuninGraph('Nginx - Active Connections', 106 | self._category, 107 | info='Active connections to Nginx Web Server.', 108 | args='--base 1000 --lower-limit 0') 109 | graph.addField('proc', 'proc', draw='AREASTACK', type='GAUGE', 110 | info="Connections with Nginx reading request body, " 111 | "processing request or writing response to client.") 112 | graph.addField('read', 'read', draw='AREASTACK', type='GAUGE', 113 | info="Connections with Nginx reading request headers.") 114 | graph.addField('wait', 'wait', draw='AREASTACK', type='GAUGE', 115 | info="Keep-alive connections with Nginx in wait state..") 116 | graph.addField('total', 'total', draw='LINE2', type='GAUGE', 117 | info="Total active connections.", colour='000000') 118 | self.appendGraph('nginx_activeconn', graph) 119 | 120 | if self.graphEnabled('nginx_connections'): 121 | graph = MuninGraph('Nginx - Connections per Second', 122 | self._category, 123 | info='Connections per second to Nginx Web Server.', 124 | args='--base 1000 --lower-limit 0') 125 | graph.addField('handled', 'handled', draw='AREASTACK', type='DERIVE', 126 | min=0, info="Connections handled by Nginx per second.") 127 | graph.addField('nothandled', 'nothandled', draw='AREASTACK', type='DERIVE', 128 | min=0, info="Connections accepted, but not handled " 129 | "by Nginx per second.") 130 | self.appendGraph('nginx_connections', graph) 131 | 132 | if self.graphEnabled('nginx_requests'): 133 | graph = MuninGraph('Nginx - Requests per Second', 134 | self._category, 135 | info='Requests per second to Nginx Web Server.', 136 | args='--base 1000 --lower-limit 0') 137 | graph.addField('requests', 'requests', draw='LINE2', type='DERIVE', 138 | min=0, info="Requests handled by Nginx per second.") 139 | self.appendGraph('nginx_requests', graph) 140 | 141 | if self.graphEnabled('nginx_requestsperconn'): 142 | graph = MuninGraph('Nginx - Requests per Connection', 143 | self._category, 144 | info='Requests per handled connections for Nginx Web Server.', 145 | args='--base 1000 --lower-limit 0') 146 | graph.addField('requests', 'requests', draw='LINE2', type='GAUGE', 147 | min=0, info="Average number of requests per" 148 | " connections handled by Nginx.") 149 | self.appendGraph('nginx_requestsperconn', graph) 150 | 151 | def retrieveVals(self): 152 | """Retrieve values for graphs.""" 153 | nginxInfo = NginxInfo(self._host, self._port, 154 | self._user, self._password, 155 | self._statuspath, self._ssl) 156 | stats = nginxInfo.getServerStats() 157 | if stats: 158 | if self.hasGraph('nginx_activeconn'): 159 | self.setGraphVal('nginx_activeconn', 'proc', stats['writing']) 160 | self.setGraphVal('nginx_activeconn', 'read', stats['reading']) 161 | self.setGraphVal('nginx_activeconn', 'wait', stats['waiting']) 162 | self.setGraphVal('nginx_activeconn', 'total', 163 | stats['connections']) 164 | if self.hasGraph('nginx_connections'): 165 | self.setGraphVal('nginx_connections', 'handled', stats['handled']) 166 | self.setGraphVal('nginx_connections', 'nothandled', 167 | stats['accepts'] - stats['handled']) 168 | if self.hasGraph('nginx_requests'): 169 | self.setGraphVal('nginx_requests', 'requests', stats['requests']) 170 | if self.hasGraph('nginx_requestsperconn'): 171 | curr_stats = (stats['handled'], stats['requests']) 172 | hist_stats = self.restoreState() 173 | if hist_stats: 174 | prev_stats = hist_stats[0] 175 | else: 176 | hist_stats = [] 177 | prev_stats = (0,0) 178 | conns = max(curr_stats[0] - prev_stats[0], 0) 179 | reqs = max(curr_stats[1] - prev_stats[1], 0) 180 | if conns > 0: 181 | self.setGraphVal('nginx_requestsperconn', 'requests', 182 | float(reqs) / float(conns)) 183 | else: 184 | self.setGraphVal('nginx_requestsperconn', 'requests', 0) 185 | hist_stats.append(curr_stats) 186 | self.saveState(hist_stats[-self._numSamples:]) 187 | 188 | def autoconf(self): 189 | """Implements Munin Plugin Auto-Configuration Option. 190 | 191 | @return: True if plugin can be auto-configured, False otherwise. 192 | 193 | """ 194 | nginxInfo = NginxInfo(self._host, self._port, 195 | self._user, self._password, 196 | self._statuspath, self._ssl) 197 | return nginxInfo is not None 198 | 199 | 200 | def main(): 201 | sys.exit(muninMain(MuninNginxPlugin)) 202 | 203 | 204 | if __name__ == "__main__": 205 | main() 206 | -------------------------------------------------------------------------------- /pymunin/plugins/varnishstats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """varnishstats - Munin Plugin to monitor stats for Varnish Cache. 3 | 4 | 5 | Requirements 6 | 7 | - Access to varnishstat executable for retrieving stats. 8 | 9 | 10 | Wild Card Plugin - No 11 | 12 | 13 | Multigraph Plugin - Graph Structure 14 | 15 | - varnish_requests 16 | - varnish_hits 17 | - varnish_client_conn 18 | - varnish_backend_conn 19 | - varnish_traffic 20 | - varnish_workers 21 | - varnish_work_queue 22 | - varnish_memory 23 | - varnish_expire_purge 24 | 25 | 26 | Environment Variables 27 | 28 | instance: Name of the Varnish Cache instance. 29 | (Defaults to hostname.) 30 | include_graphs: Comma separated list of enabled graphs. 31 | (All graphs enabled by default.) 32 | exclude_graphs: Comma separated list of disabled graphs. 33 | 34 | Environment Variables for Multiple Instances of Plugin (Omitted by default.) 35 | 36 | instance_name: Name of instance. 37 | instance_label: Graph title label for instance. 38 | (Default is the same as instance name.) 39 | instance_label_format: One of the following values: 40 | - suffix (Default) 41 | - prefix 42 | - none 43 | 44 | Example: 45 | [varnishstats] 46 | env.exclude_graphs varnish_workers 47 | 48 | """ 49 | # Munin - Magic Markers 50 | #%# family=auto 51 | #%# capabilities=autoconf nosuggest 52 | 53 | import sys 54 | from pymunin import MuninGraph, MuninPlugin, muninMain 55 | from pysysinfo.varnish import VarnishInfo 56 | 57 | __author__ = "Ali Onur Uyar" 58 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 59 | __credits__ = ["Preston Mason (https://github.com/pentie)",] 60 | __license__ = "GPL" 61 | __version__ = "0.9.22" 62 | __maintainer__ = "Ali Onur Uyar" 63 | __email__ = "aouyar at gmail.com" 64 | __status__ = "Development" 65 | 66 | 67 | class MuninVarnishPlugin(MuninPlugin): 68 | """Multigraph Munin Plugin for monitoring Varnish Cache. 69 | 70 | """ 71 | plugin_name = 'varnishstats' 72 | isMultigraph = True 73 | isMultiInstance = True 74 | 75 | def __init__(self, argv=(), env=None, debug=False): 76 | """Populate Munin Plugin with MuninGraph instances. 77 | 78 | @param argv: List of command line arguments. 79 | @param env: Dictionary of environment variables. 80 | @param debug: Print debugging messages if True. (Default: False) 81 | 82 | """ 83 | MuninPlugin.__init__(self, argv, env, debug) 84 | 85 | self._instance = self.envGet('instance') 86 | self._category = 'Varnish' 87 | varnish_info = VarnishInfo(self._instance) 88 | self._stats = varnish_info.getStats() 89 | self._desc = varnish_info.getDescDict() 90 | 91 | graph_name = 'varnish_requests' 92 | if self.graphEnabled(graph_name): 93 | graph = MuninGraph('Varnish - Client/Backend Requests / sec', 94 | self._category, 95 | info='Number of client and backend requests per second for Varnish Cache.', 96 | args='--base 1000 --lower-limit 0') 97 | for flabel in ('client', 'backend',): 98 | fname = '%s_req' % flabel 99 | finfo = self._desc.get(fname, '') 100 | graph.addField(fname, flabel, draw='LINE2', type='DERIVE', 101 | min=0, info=finfo) 102 | self.appendGraph(graph_name, graph) 103 | 104 | graph_name = 'varnish_hits' 105 | if self.graphEnabled(graph_name): 106 | graph = MuninGraph('Varnish - Cache Hits vs. Misses (%)', 107 | self._category, 108 | info='Number of Cache Hits and Misses per second.', 109 | args='--base 1000 --lower-limit 0') 110 | for flabel, fname in (('hit', 'cache_hit'), 111 | ('pass', 'cache_hitpass'), 112 | ('miss', 'cache_miss')): 113 | finfo = self._desc.get(fname, '') 114 | graph.addField(fname, flabel, draw='AREASTACK', type='DERIVE', 115 | min=0, info=finfo) 116 | self.appendGraph(graph_name, graph) 117 | 118 | graph_name = 'varnish_client_conn' 119 | if self.graphEnabled(graph_name): 120 | graph = MuninGraph('Varnish - Client Connections / sec', 121 | self._category, 122 | info='Client connections per second for Varnish Cache.', 123 | args='--base 1000 --lower-limit 0') 124 | for flabel in ('conn', 'drop',): 125 | fname = 'client_%s' % flabel 126 | finfo = self._desc.get(fname, '') 127 | graph.addField(fname, flabel, draw='AREASTACK', type='DERIVE', 128 | min=0, info=finfo) 129 | self.appendGraph(graph_name, graph) 130 | 131 | graph_name = 'varnish_backend_conn' 132 | if self.graphEnabled(graph_name): 133 | graph = MuninGraph('Varnish - Backend Connections / sec', 134 | self._category, 135 | info='Connections per second from Varnish Cache to backends.', 136 | args='--base 1000 --lower-limit 0') 137 | for flabel in ('conn', 'reuse', 'busy', 'fail', 'retry', 'unhealthy',): 138 | fname = 'backend_%s' % flabel 139 | finfo = self._desc.get(fname, '') 140 | graph.addField(fname, flabel, draw='AREASTACK', type='DERIVE', 141 | min=0, info=finfo) 142 | self.appendGraph(graph_name, graph) 143 | 144 | graph_name = 'varnish_traffic' 145 | if self.graphEnabled(graph_name): 146 | graph = MuninGraph('Varnish - Traffic (bytes/sec)', 147 | self._category, 148 | info='HTTP Header and Body traffic. ' 149 | '(TCP/IP overhead not included.)', 150 | args='--base 1024 --lower-limit 0') 151 | for flabel, fname in (('header', 's_hdrbytes'), 152 | ('body', 's_bodybytes'),): 153 | finfo = self._desc.get(fname, '') 154 | graph.addField(fname, flabel, draw='AREASTACK', type='DERIVE', 155 | min=0, info=finfo) 156 | self.appendGraph(graph_name, graph) 157 | 158 | graph_name = 'varnish_workers' 159 | if self.graphEnabled(graph_name): 160 | graph = MuninGraph('Varnish - Worker Threads', 161 | self._category, 162 | info='Number of worker threads.', 163 | args='--base 1000 --lower-limit 0') 164 | fname = 'n_wrk' 165 | flabel = 'req' 166 | finfo = self._desc.get(fname, '') 167 | graph.addField(fname, flabel, draw='LINE2', type='GAUGE', 168 | min=0, info=finfo) 169 | self.appendGraph(graph_name, graph) 170 | 171 | graph_name = 'varnish_work_queue' 172 | if self.graphEnabled(graph_name): 173 | graph = MuninGraph('Varnish - Queued/Dropped Work Requests / sec', 174 | self._category, 175 | info='Requests queued for waiting for a worker thread to become ' 176 | 'available and requests dropped because of overflow of queue.', 177 | args='--base 1000 --lower-limit 0') 178 | for flabel, fname in (('queued', 'n_wrk_queued'), 179 | ('dropped', 'n_wrk_drop')): 180 | finfo = self._desc.get(fname, '') 181 | graph.addField(fname, flabel, draw='LINE2', type='DERIVE', 182 | min=0, info=finfo) 183 | self.appendGraph(graph_name, graph) 184 | 185 | graph_name = 'varnish_memory' 186 | if self.graphEnabled(graph_name): 187 | graph = MuninGraph('Varnish - Cache Memory Usage (bytes)', 188 | self._category, 189 | info='Varnish cache memory usage in bytes.', 190 | args='--base 1024 --lower-limit 0') 191 | for flabel, fname in (('used', 'SMA_s0_g_bytes'), 192 | ('free', 'SMA_s0_g_space')): 193 | finfo = self._desc.get(fname, '') 194 | graph.addField(fname, flabel, draw='AREASTACK', type='GAUGE', 195 | min=0, info=finfo) 196 | self.appendGraph(graph_name, graph) 197 | 198 | graph_name = 'varnish_expire_purge' 199 | if self.graphEnabled(graph_name): 200 | graph = MuninGraph('Varnish - Expired/Purged Objects / sec', 201 | self._category, 202 | info='Expired objects and LRU purged objects per second.', 203 | args='--base 1000 --lower-limit 0') 204 | for flabel, fname in (('expire', 'n_expired'), 205 | ('purge', 'n_lru_nuked')): 206 | finfo = self._desc.get(fname, '') 207 | graph.addField(fname, flabel, draw='LINE2', type='DERIVE', 208 | min=0, info=finfo) 209 | self.appendGraph(graph_name, graph) 210 | 211 | def retrieveVals(self): 212 | """Retrieve values for graphs.""" 213 | for graph_name in self.getGraphList(): 214 | for field_name in self.getGraphFieldList(graph_name): 215 | self.setGraphVal(graph_name, field_name, 216 | self._stats.get(field_name)) 217 | 218 | def autoconf(self): 219 | """Implements Munin Plugin Auto-Configuration Option. 220 | 221 | @return: True if plugin can be auto-configured, False otherwise. 222 | 223 | """ 224 | return len(self._stats) > 0 225 | 226 | 227 | def main(): 228 | sys.exit(muninMain(MuninVarnishPlugin)) 229 | 230 | 231 | if __name__ == "__main__": 232 | main() 233 | 234 | -------------------------------------------------------------------------------- /pymunin/plugins/wanpipestats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """wanpipestats - Munin Plugin to monitor Wanpipe Interfaces. 3 | 4 | 5 | Requirements 6 | 7 | - Wanpipe utility wanpipemon. 8 | - Plugin must be executed with root user privileges. 9 | 10 | Wild Card Plugin - No 11 | 12 | 13 | Multigraph Plugin - Graph Structure 14 | 15 | - wanpipe_traffic 16 | - wanpipe_errors 17 | - wanpipe_pri_errors_ 18 | - wanpipe_pri_rxlevel 19 | 20 | Environment Variables 21 | 22 | include_ifaces: Comma separated list of wanpipe interfaces to include in 23 | graphs. (All Wanpipe Interfaces are monitored by default.) 24 | exclude_ifaces: Comma separated list of wanpipe interfaces to exclude from 25 | graphs. 26 | include_graphs: Comma separated list of enabled graphs. 27 | (All graphs enabled by default.) 28 | exclude_graphs: Comma separated list of disabled graphs. 29 | 30 | Example: 31 | [wanpipestats] 32 | user root 33 | env.include_ifaces w1g1,w2g2 34 | env.exclude_graphs wanpipe_errors 35 | 36 | """ 37 | # Munin - Magic Markers 38 | #%# family=auto 39 | #%# capabilities=autoconf nosuggest 40 | 41 | import sys 42 | from pymunin import MuninGraph, MuninPlugin, muninMain 43 | from pysysinfo.wanpipe import WanpipeInfo 44 | 45 | __author__ = "Ali Onur Uyar" 46 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 47 | __credits__ = [] 48 | __license__ = "GPL" 49 | __version__ = "0.9" 50 | __maintainer__ = "Ali Onur Uyar" 51 | __email__ = "aouyar at gmail.com" 52 | __status__ = "Development" 53 | 54 | 55 | class MuninWanpipePlugin(MuninPlugin): 56 | """Multigraph Munin Plugin for monitoring Wanpipe Interfaces. 57 | 58 | """ 59 | plugin_name = 'wanpipestats' 60 | isMultigraph = True 61 | 62 | def __init__(self, argv=(), env=None, debug=False): 63 | """Populate Munin Plugin with MuninGraph instances. 64 | 65 | @param argv: List of command line arguments. 66 | @param env: Dictionary of environment variables. 67 | @param debug: Print debugging messages if True. (Default: False) 68 | 69 | """ 70 | MuninPlugin.__init__(self, argv, env, debug) 71 | 72 | self.envRegisterFilter('ifaces', '^[\w\d]+$') 73 | self._category = 'Wanpipe' 74 | 75 | self._wanpipeInfo = WanpipeInfo() 76 | self._ifaceStats = self._wanpipeInfo.getIfaceStats() 77 | self._ifaceList = [] 78 | for iface in list(self._ifaceStats): 79 | if self.ifaceIncluded(iface): 80 | self._ifaceList.append(iface) 81 | self._ifaceList.sort() 82 | 83 | for iface in self._ifaceList: 84 | if self._reqIfaceList is None or iface in self._reqIfaceList: 85 | if self.graphEnabled('wanpipe_traffic'): 86 | graph = MuninGraph('Wanpipe - Traffic - %s' % iface, 87 | self._category, 88 | info='Traffic Stats for Wanpipe Interface %s ' 89 | 'in packets/sec.' % iface, 90 | args='--base 1000 --lower-limit 0', 91 | vlabel='packets in (-) / out (+) per second') 92 | graph.addField('rxpackets', 'packets', draw='LINE2', 93 | type='DERIVE', min=0, graph=False) 94 | graph.addField('txpackets', 'packets', draw='LINE2', 95 | type='DERIVE', min=0, 96 | negative='rxpackets') 97 | self.appendGraph('wanpipe_traffic_%s' % iface, graph) 98 | 99 | if self.graphEnabled('wanpipe_errors'): 100 | graph = MuninGraph('Wanpipe - Errors - %s' % iface, self._category, 101 | info='Error Stats for Wanpipe Interface %s' 102 | ' in errors/sec.' % iface, 103 | args='--base 1000 --lower-limit 0', 104 | vlabel='errors in (-) / out (+) per second') 105 | graph.addField('rxerrs', 'errors', draw='LINE2', 106 | type='DERIVE', min=0, graph=False) 107 | graph.addField('txerrs', 'errors', draw='LINE2', 108 | type='DERIVE', min=0, negative='txerrs', 109 | info='Rx(-)/Tx(+) Errors per second.') 110 | graph.addField('rxframe', 'frm/crr', draw='LINE2', 111 | type='DERIVE', min=0, graph=False) 112 | graph.addField('txcarrier', 'frm/crr', draw='LINE2', 113 | type='DERIVE', min=0, negative='rxframe', 114 | info='Frame(-)/Carrier(+) Errors per second.') 115 | graph.addField('rxdrop', 'drop', draw='LINE2', 116 | type='DERIVE', min=0, graph=False) 117 | graph.addField('txdrop', 'drop', draw='LINE2', 118 | type='DERIVE', min=0, negative='rxdrop', 119 | info='Rx(-)/Tx(+) Dropped Packets per second.') 120 | graph.addField('rxfifo', 'fifo', draw='LINE2', 121 | type='DERIVE', min=0, graph=False) 122 | graph.addField('txfifo', 'fifo', draw='LINE2', 123 | type='DERIVE', min=0, negative='rxfifo', 124 | info='Rx(-)/Tx(+) FIFO Errors per second.') 125 | self.appendGraph('wanpipe_errors_%s' % iface, graph) 126 | 127 | if self.graphEnabled('wanpipe_pri_errors'): 128 | graph = MuninGraph('Wanpipe - ISDN PRI Stats - %s' % iface, 129 | self._category, 130 | info='ISDN PRI Error Stats for Wanpipe Interface %s' 131 | ' in errors/sec.' % iface, 132 | args='--base 1000 --lower-limit 0', 133 | vlabel='errors in (-) / out (+) per second') 134 | graph.addField('linecodeviolation', 'Line Code Violation', 135 | draw='LINE2', type='DERIVE', min=0, 136 | info='Line Code Violation errors per second.') 137 | graph.addField('farendblockerrors', 'Far End Block Errors', 138 | draw='LINE2', type='DERIVE', min=0, 139 | info='Far End Block errors per second.') 140 | graph.addField('crc4errors', 'CRC4 Errors', draw='LINE2', 141 | type='DERIVE', min=0, info='CRC4 errors per second.') 142 | graph.addField('faserrors', 'FAS Errors', draw='LINE2', 143 | type='DERIVE', min=0, info='FAS errors per second.') 144 | self.appendGraph('wanpipe_pri_errors_%s' % iface, graph) 145 | 146 | if self.graphEnabled('wanpipe_pri_rxlevel'): 147 | graph = MuninGraph('Wanpipe - ISDN PRI Signal Level', self._category, 148 | info='ISDN PRI received signal level in DB.', 149 | args='--base 1000 --lower-limit 0', 150 | vlabel='db') 151 | for iface in self._ifaceList: 152 | if self._reqIfaceList is None or iface in self._reqIfaceList: 153 | graph.addField(iface, iface, draw='LINE2') 154 | self.appendGraph('wanpipe_pri_rxlevel', graph) 155 | 156 | def retrieveVals(self): 157 | """Retrieve values for graphs.""" 158 | for iface in self._ifaceList: 159 | if self._reqIfaceList is None or iface in self._reqIfaceList: 160 | if (self.graphEnabled('wanpipe_traffic') 161 | or self.graphEnabled('wanpipe_errors')): 162 | stats = self._ifaceStats.get(iface) 163 | if stats: 164 | graph_name = 'wanpipe_traffic_%s' % iface 165 | if self.hasGraph(graph_name): 166 | for field in ('rxpackets', 'txpackets'): 167 | self.setGraphVal(graph_name, field, 168 | stats.get(field)) 169 | graph_name = 'wanpipe_errors_%s' % iface 170 | if self.hasGraph(graph_name): 171 | for field in ('rxerrs', 'txerrs', 'rxframe', 'txcarrier', 172 | 'rxdrop', 'txdrop', 'rxfifo', 'txfifo'): 173 | self.setGraphVal(graph_name, field, 174 | stats.get(field)) 175 | if (self.graphEnabled('wanpipe_pri_errors') 176 | or self.graphEnabled('wanpipe_rxlevel')): 177 | try: 178 | stats = self._wanpipeInfo.getPRIstats(iface) 179 | except: 180 | stats = None 181 | if stats: 182 | graph_name = 'wanpipe_pri_errors_%s' % iface 183 | if self.hasGraph(graph_name): 184 | for field in ('linecodeviolation', 185 | 'farendblockerrors', 186 | 'crc4errors', 'faserrors'): 187 | self.setGraphVal(graph_name, field, 188 | stats.get(field)) 189 | if self.hasGraph('wanpipe_rxlevel'): 190 | self.setGraphVal('wanpipe_pri_rxlevel', 191 | iface, stats.get('rxlevel')) 192 | 193 | def ifaceIncluded(self, iface): 194 | """Utility method to check if interface is included in monitoring. 195 | 196 | @param iface: Interface name. 197 | @return: Returns True if included in graphs, False otherwise. 198 | 199 | """ 200 | return self.envCheckFilter('ifaces', iface) 201 | 202 | def autoconf(self): 203 | """Implements Munin Plugin Auto-Configuration Option. 204 | 205 | @return: True if plugin can be auto-configured, False otherwise. 206 | 207 | """ 208 | return len(self._ifaceList) > 0 209 | 210 | 211 | def main(): 212 | sys.exit(muninMain(MuninWanpipePlugin)) 213 | 214 | 215 | if __name__ == "__main__": 216 | main() 217 | -------------------------------------------------------------------------------- /pysysinfo/process.py: -------------------------------------------------------------------------------- 1 | """Implements ProcessInfo Class for gathering process stats. 2 | 3 | """ 4 | 5 | import re 6 | import util 7 | 8 | __author__ = "Ali Onur Uyar" 9 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 10 | __credits__ = [] 11 | __license__ = "GPL" 12 | __version__ = "0.9" 13 | __maintainer__ = "Ali Onur Uyar" 14 | __email__ = "aouyar at gmail.com" 15 | __status__ = "Development" 16 | 17 | 18 | # Defaults 19 | psCmd = '/bin/ps' 20 | 21 | 22 | # Maps 23 | procStatusNames = {'D': 'uninterruptable_sleep', 24 | 'R': 'running', 25 | 'S': 'sleep', 26 | 'T': 'stopped', 27 | 'W': 'paging', 28 | 'X': 'dead', 29 | 'Z': 'defunct'} 30 | 31 | psFieldWidth = {'args': 128, 32 | 'cmd': 128, 33 | 'command': 128, 34 | 's': 4, 35 | 'stat': 8, 36 | 'state': 4,} 37 | psDefaultFieldWidth = 16 38 | 39 | 40 | class ProcessInfo: 41 | """Class to retrieve stats for processes.""" 42 | 43 | def __init__(self): 44 | """Initialize Process Stats.""" 45 | pass 46 | 47 | def execProcCmd(self, *args): 48 | """Execute ps command with positional params args and return result as 49 | list of lines. 50 | 51 | @param *args: Positional params for ps command. 52 | @return: List of output lines 53 | 54 | """ 55 | out = util.exec_command([psCmd,] + list(args)) 56 | return out.splitlines() 57 | 58 | def parseProcCmd(self, fields=('pid', 'user', 'cmd',), threads=False): 59 | """Execute ps command with custom output format with columns from 60 | fields and return result as a nested list. 61 | 62 | The Standard Format Specifiers from ps man page must be used for the 63 | fields parameter. 64 | 65 | @param fields: List of fields included in the output. 66 | Default: pid, user, cmd 67 | @param threads: If True, include threads in output. 68 | @return: List of headers and list of rows and columns. 69 | 70 | """ 71 | args = [] 72 | headers = [f.lower() for f in fields] 73 | args.append('--no-headers') 74 | args.append('-e') 75 | if threads: 76 | args.append('-T') 77 | field_ranges = [] 78 | fmt_strs = [] 79 | start = 0 80 | for header in headers: 81 | field_width = psFieldWidth.get(header, psDefaultFieldWidth) 82 | fmt_strs.append('%s:%d' % (header, field_width)) 83 | end = start + field_width + 1 84 | field_ranges.append((start,end)) 85 | start = end 86 | args.append('-o') 87 | args.append(','.join(fmt_strs)) 88 | lines = self.execProcCmd(*args) 89 | if len(lines) > 0: 90 | stats = [] 91 | for line in lines: 92 | cols = [] 93 | for (start, end) in field_ranges: 94 | cols.append(line[start:end].strip()) 95 | stats.append(cols) 96 | return {'headers': headers, 'stats': stats} 97 | else: 98 | return None 99 | 100 | def getProcList(self, fields=('pid', 'user', 'cmd',), threads=False, 101 | **kwargs): 102 | """Execute ps command with custom output format with columns columns 103 | from fields, select lines using the filters defined by kwargs and return 104 | result as a nested list. 105 | 106 | The Standard Format Specifiers from ps man page must be used for the 107 | fields parameter. 108 | 109 | @param fields: Fields included in the output. 110 | Default: pid, user, cmd 111 | @param threads: If True, include threads in output. 112 | @param **kwargs: Keyword variables are used for filtering the results 113 | depending on the values of the columns. Each keyword 114 | must correspond to a field name with an optional 115 | suffix: 116 | field: Field equal to value or in list of 117 | values. 118 | field_ic: Field equal to value or in list of 119 | values, using case insensitive 120 | comparison. 121 | field_regex: Field matches regex value or matches 122 | with any regex in list of values. 123 | field_ic_regex: Field matches regex value or matches 124 | with any regex in list of values 125 | using case insensitive match. 126 | @return: List of headers and list of rows and columns. 127 | 128 | """ 129 | field_list = list(fields) 130 | for key in kwargs: 131 | col = re.sub('(_ic)?(_regex)?$', '', key) 132 | if not col in field_list: 133 | field_list.append(col) 134 | pinfo = self.parseProcCmd(field_list, threads) 135 | if pinfo: 136 | if len(kwargs) > 0: 137 | pfilter = util.TableFilter() 138 | pfilter.registerFilters(**kwargs) 139 | stats = pfilter.applyFilters(pinfo['headers'], pinfo['stats']) 140 | return {'headers': pinfo['headers'], 'stats': stats} 141 | else: 142 | return pinfo 143 | else: 144 | return None 145 | 146 | def getProcDict(self, fields=('user', 'cmd',), threads=False, **kwargs): 147 | """Execute ps command with custom output format with columns format with 148 | columns from fields, and return result as a nested dictionary with 149 | the key PID or SPID. 150 | 151 | The Standard Format Specifiers from ps man page must be used for the 152 | fields parameter. 153 | 154 | @param fields: Fields included in the output. 155 | Default: user, cmd 156 | (PID or SPID column is included by default.) 157 | @param threads: If True, include threads in output. 158 | @param **kwargs: Keyword variables are used for filtering the results 159 | depending on the values of the columns. Each keyword 160 | must correspond to a field name with an optional 161 | suffix: 162 | field: Field equal to value or in list of 163 | values. 164 | field_ic: Field equal to value or in list of 165 | values, using case insensitive 166 | comparison. 167 | field_regex: Field matches regex value or matches 168 | with any regex in list of values. 169 | field_ic_regex: Field matches regex value or matches 170 | with any regex in list of values 171 | using case insensitive match. 172 | @return: Nested dictionary indexed by: 173 | PID for process info. 174 | SPID for thread info. 175 | 176 | """ 177 | stats = {} 178 | field_list = list(fields) 179 | num_cols = len(field_list) 180 | if threads: 181 | key = 'spid' 182 | else: 183 | key = 'pid' 184 | try: 185 | key_idx = field_list.index(key) 186 | except ValueError: 187 | field_list.append(key) 188 | key_idx = len(field_list) - 1 189 | result = self.getProcList(field_list, threads, **kwargs) 190 | if result is not None: 191 | headers = result['headers'][:num_cols] 192 | lines = result['stats'] 193 | if len(lines) > 1: 194 | for cols in lines: 195 | stats[cols[key_idx]] = dict(zip(headers, cols[:num_cols])) 196 | return stats 197 | else: 198 | return None 199 | 200 | def getProcStatStatus(self, threads=False, **kwargs): 201 | """Return process counts per status and priority. 202 | 203 | @param **kwargs: Keyword variables are used for filtering the results 204 | depending on the values of the columns. Each keyword 205 | must correspond to a field name with an optional 206 | suffix: 207 | field: Field equal to value or in list of 208 | values. 209 | field_ic: Field equal to value or in list of 210 | values, using case insensitive 211 | comparison. 212 | field_regex: Field matches regex value or matches 213 | with any regex in list of values. 214 | field_ic_regex: Field matches regex value or matches 215 | with any regex in list of values 216 | using case insensitive match. 217 | @return: Dictionary of process counters. 218 | 219 | """ 220 | procs = self.getProcList(['stat',], threads=threads, **kwargs) 221 | status = dict(zip(procStatusNames.values(), 222 | [0,] * len(procStatusNames))) 223 | prio = {'high': 0, 'low': 0, 'norm': 0, 'locked_in_mem': 0} 224 | total = 0 225 | locked_in_mem = 0 226 | if procs is not None: 227 | for cols in procs['stats']: 228 | col_stat = cols[0] 229 | status[procStatusNames[col_stat[0]]] += 1 230 | if '<' in col_stat[1:]: 231 | prio['high'] += 1 232 | elif 'N' in col_stat[1:]: 233 | prio['low'] += 1 234 | else: 235 | prio['norm'] += 1 236 | if 'L' in col_stat[1:]: 237 | locked_in_mem += 1 238 | total += 1 239 | return {'status': status, 240 | 'prio': prio, 241 | 'locked_in_mem': locked_in_mem, 242 | 'total': total} 243 | -------------------------------------------------------------------------------- /pymunin/plugins/diskiostats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """diskiostats - Munin Plugin to monitor Disk I/O. 3 | 4 | 5 | Requirements - NA 6 | 7 | 8 | Wild Card Plugin - No 9 | 10 | 11 | Multigraph Plugin - Graph Structure 12 | 13 | - diskio_disk_requests 14 | - diskio_disk_bytes 15 | - diskio_disk_active 16 | - diskio_part_requests 17 | - diskio_part_bytes 18 | - diskio_part_active 19 | - diskio_md_requests 20 | - diskio_md_bytes 21 | - diskio_md_active 22 | - diskio_lv_requests 23 | - diskio_lv_bytes 24 | - diskio_lv_active 25 | - diskio_fs_requests 26 | - diskio_fs_bytes 27 | - diskio_fs_active 28 | 29 | 30 | Environment Variables 31 | 32 | include_graphs: Comma separated list of enabled graphs. 33 | (All graphs enabled by default.) 34 | exclude_graphs: Comma separated list of disabled graphs. 35 | 36 | 37 | Example: 38 | [diskiostats] 39 | env.include_graphs diskio_disk_requests, diskio_disk_bytes 40 | 41 | 42 | """ 43 | # Munin - Magic Markers 44 | #%# family=auto 45 | #%# capabilities=autoconf nosuggest 46 | 47 | import sys 48 | from pymunin import (MuninGraph, MuninPlugin, muninMain, 49 | fixLabel, maxLabelLenGraphSimple, maxLabelLenGraphDual) 50 | from pysysinfo.diskio import DiskIOinfo 51 | 52 | __author__ = "Ali Onur Uyar" 53 | __copyright__ = "Copyright 2011, Ali Onur Uyar" 54 | __credits__ = [] 55 | __license__ = "GPL" 56 | __version__ = "0.9.27" 57 | __maintainer__ = "Ali Onur Uyar" 58 | __email__ = "aouyar at gmail.com" 59 | __status__ = "Development" 60 | 61 | 62 | 63 | class MuninDiskIOplugin(MuninPlugin): 64 | """Multigraph Munin Plugin for monitoring Disk I/O. 65 | 66 | """ 67 | plugin_name = 'diskiostats' 68 | isMultigraph = True 69 | 70 | def __init__(self, argv=(), env=None, debug=False): 71 | """Populate Munin Plugin with MuninGraph instances. 72 | 73 | @param argv: List of command line arguments. 74 | @param env: Dictionary of environment variables. 75 | @param debug: Print debugging messages if True. (Default: False) 76 | 77 | """ 78 | MuninPlugin.__init__(self, argv, env, debug) 79 | self._category = 'Disk IO' 80 | 81 | self._info = DiskIOinfo() 82 | 83 | self._labelDelim = { 'fs': '/', 'lv': '-'} 84 | 85 | self._diskList = self._info.getDiskList() 86 | if self._diskList: 87 | self._diskList.sort() 88 | self._configDevRequests('disk', 'Disk', self._diskList) 89 | self._configDevBytes('disk', 'Disk', self._diskList) 90 | self._configDevActive('disk', 'Disk', self._diskList) 91 | 92 | self._mdList = self._info.getMDlist() 93 | if self._mdList: 94 | self._mdList.sort() 95 | self._configDevRequests('md', 'MD', self._mdList) 96 | self._configDevBytes('md', 'MD', self._mdList) 97 | self._configDevActive('md', 'MD', self._mdList) 98 | 99 | devlist = self._info.getPartitionList() 100 | if devlist: 101 | devlist.sort() 102 | self._partList = [x[1] for x in devlist] 103 | self._configDevRequests('part', 'Partition', self._partList) 104 | self._configDevBytes('part', 'Partition', self._partList) 105 | self._configDevActive('part', 'Partition', self._partList) 106 | else: 107 | self._partList = None 108 | 109 | self._lvList = self._info.getLVnameList() 110 | if self._lvList: 111 | self._lvList.sort() 112 | self._configDevRequests('lv', 'LV', self._lvList) 113 | self._configDevBytes('lv', 'LV', self._lvList) 114 | self._configDevActive('lv', 'LV', self._lvList) 115 | else: 116 | self._lvList = None 117 | 118 | self._fsList = self._info.getFilesystemList() 119 | self._fsList.sort() 120 | self._configDevRequests('fs', 'Filesystem', self._fsList) 121 | self._configDevBytes('fs', 'Filesystem', self._fsList) 122 | self._configDevActive('fs', 'Filesystem', self._fsList) 123 | 124 | 125 | def retrieveVals(self): 126 | """Retrieve values for graphs.""" 127 | if self._diskList: 128 | self._fetchDevAll('disk', self._diskList, 129 | self._info.getDiskStats) 130 | if self._mdList: 131 | self._fetchDevAll('md', self._mdList, 132 | self._info.getMDstats) 133 | if self._partList: 134 | self._fetchDevAll('part', self._partList, 135 | self._info.getPartitionStats) 136 | if self._lvList: 137 | self._fetchDevAll('lv', self._lvList, 138 | self._info.getLVstats) 139 | self._fetchDevAll('fs', self._fsList, 140 | self._info.getFilesystemStats) 141 | 142 | def _configDevRequests(self, namestr, titlestr, devlist): 143 | """Generate configuration for I/O Request stats. 144 | 145 | @param namestr: Field name component indicating device type. 146 | @param titlestr: Title component indicating device type. 147 | @param devlist: List of devices. 148 | 149 | """ 150 | name = 'diskio_%s_requests' % namestr 151 | if self.graphEnabled(name): 152 | graph = MuninGraph('Disk I/O - %s - Requests' % titlestr, self._category, 153 | info='Disk I/O - %s Throughput, Read / write requests per second.' 154 | % titlestr, 155 | args='--base 1000 --lower-limit 0', 156 | vlabel='reqs/sec read (-) / write (+)', printf='%6.1lf', 157 | autoFixNames = True) 158 | for dev in devlist: 159 | graph.addField(dev + '_read', 160 | fixLabel(dev, maxLabelLenGraphDual, 161 | repl = '..', truncend=False, 162 | delim = self._labelDelim.get(namestr)), 163 | draw='LINE2', type='DERIVE', min=0, graph=False) 164 | graph.addField(dev + '_write', 165 | fixLabel(dev, maxLabelLenGraphDual, 166 | repl = '..', truncend=False, 167 | delim = self._labelDelim.get(namestr)), 168 | draw='LINE2', type='DERIVE', min=0, 169 | negative=(dev + '_read'),info=dev) 170 | self.appendGraph(name, graph) 171 | 172 | def _configDevBytes(self, namestr, titlestr, devlist): 173 | """Generate configuration for I/O Throughput stats. 174 | 175 | @param namestr: Field name component indicating device type. 176 | @param titlestr: Title component indicating device type. 177 | @param devlist: List of devices. 178 | 179 | """ 180 | name = 'diskio_%s_bytes' % namestr 181 | if self.graphEnabled(name): 182 | graph = MuninGraph('Disk I/O - %s - Throughput' % titlestr, self._category, 183 | info='Disk I/O - %s Throughput, bytes read / written per second.' 184 | % titlestr, 185 | args='--base 1000 --lower-limit 0', printf='%6.1lf', 186 | vlabel='bytes/sec read (-) / write (+)', 187 | autoFixNames = True) 188 | for dev in devlist: 189 | graph.addField(dev + '_read', 190 | fixLabel(dev, maxLabelLenGraphDual, 191 | repl = '..', truncend=False, 192 | delim = self._labelDelim.get(namestr)), 193 | draw='LINE2', type='DERIVE', min=0, graph=False) 194 | graph.addField(dev + '_write', 195 | fixLabel(dev, maxLabelLenGraphDual, 196 | repl = '..', truncend=False, 197 | delim = self._labelDelim.get(namestr)), 198 | draw='LINE2', type='DERIVE', min=0, 199 | negative=(dev + '_read'), info=dev) 200 | self.appendGraph(name, graph) 201 | 202 | def _configDevActive(self, namestr, titlestr, devlist): 203 | """Generate configuration for I/O Queue Length. 204 | 205 | @param namestr: Field name component indicating device type. 206 | @param titlestr: Title component indicating device type. 207 | @param devlist: List of devices. 208 | 209 | """ 210 | name = 'diskio_%s_active' % namestr 211 | if self.graphEnabled(name): 212 | graph = MuninGraph('Disk I/O - %s - Queue Length' % titlestr, 213 | self._category, 214 | info='Disk I/O - Number of I/O Operations in Progress for every %s.' 215 | % titlestr, 216 | args='--base 1000 --lower-limit 0', printf='%6.1lf', 217 | autoFixNames = True) 218 | for dev in devlist: 219 | graph.addField(dev, 220 | fixLabel(dev, maxLabelLenGraphSimple, 221 | repl = '..', truncend=False, 222 | delim = self._labelDelim.get(namestr)), 223 | draw='AREASTACK', type='GAUGE', info=dev) 224 | self.appendGraph(name, graph) 225 | 226 | def _fetchDevAll(self, namestr, devlist, statsfunc): 227 | """Initialize I/O stats for devices. 228 | 229 | @param namestr: Field name component indicating device type. 230 | @param devlist: List of devices. 231 | @param statsfunc: Function for retrieving stats for device. 232 | 233 | """ 234 | for dev in devlist: 235 | stats = statsfunc(dev) 236 | name = 'diskio_%s_requests' % namestr 237 | if self.hasGraph(name): 238 | self.setGraphVal(name, dev + '_read', stats['rios']) 239 | self.setGraphVal(name, dev + '_write', stats['wios']) 240 | name = 'diskio_%s_bytes' % namestr 241 | if self.hasGraph(name): 242 | self.setGraphVal(name, dev + '_read', stats['rbytes']) 243 | self.setGraphVal(name, dev + '_write', stats['wbytes']) 244 | name = 'diskio_%s_active' % namestr 245 | if self.hasGraph(name): 246 | self.setGraphVal(name, dev, stats['ios_active']) 247 | 248 | def autoconf(self): 249 | """Implements Munin Plugin Auto-Configuration Option. 250 | 251 | @return: True if plugin can be auto-configured, False otherwise. 252 | 253 | """ 254 | # If no exception is thrown during initialization, the plugin should work. 255 | return True 256 | 257 | 258 | def main(): 259 | sys.exit(muninMain(MuninDiskIOplugin)) 260 | 261 | 262 | if __name__ == "__main__": 263 | main() 264 | --------------------------------------------------------------------------------