X-Git-Url: https://arthur.barton.de/gitweb/?a=blobdiff_plain;f=python.d%2Fisc_dhcpd.chart.py;h=bb9ba5cbc51347c23291554cb6c1be7da3576f00;hb=0711ceaacadf4b435b11377ce9f0e65392675d2a;hp=82b2c4593f42e2fa56b4ed66ef87a1a3b195181b;hpb=04f15818d0b3e1ad91d88f3541d2c9a35f351d4d;p=netdata.git diff --git a/python.d/isc_dhcpd.chart.py b/python.d/isc_dhcpd.chart.py index 82b2c459..bb9ba5cb 100644 --- a/python.d/isc_dhcpd.chart.py +++ b/python.d/isc_dhcpd.chart.py @@ -3,14 +3,19 @@ # Author: l2isbad from base import SimpleService -from re import compile, search -from time import mktime, strptime, gmtime +from time import mktime, strptime, gmtime, time +from os import stat try: from ipaddress import IPv4Address as ipaddress from ipaddress import ip_network have_ipaddress = True except ImportError: have_ipaddress = False +try: + from itertools import filterfalse as filterfalse +except ImportError: + from itertools import ifilterfalse as filterfalse + priority = 60000 retries = 60 @@ -23,14 +28,13 @@ class Service(SimpleService): self.pools = self.configuration.get('pools') # Will work only with 'default' db-time-format (weekday year/month/day hour:minute:second) - # TODO: update the regex to parse correctly 'local' db-time-format + # TODO: update algorithm to parse correctly 'local' db-time-format # (epoch ; # :: ) # Also only ipv4 supported - self.regex = compile(r'(\d+(?:\.\d+){3}).*?((?<=ends )[0-9].*?(?=;))') def check(self): if not self._get_raw_data(): - self.error('Make sure leases_path is correct and dhcpd.leases is readable by netdata') + self.error('Make sure leases_path is correct and leases log file is readable by netdata') return False elif not have_ipaddress: self.error('No ipaddress module. Please install (py2-ipaddress in case of python2)') @@ -38,21 +42,37 @@ class Service(SimpleService): else: try: self.pools = self.pools.split() - if not [ip_network(pool) for pool in self.pools]: + if not [ip_network(return_utf(pool)) for pool in self.pools]: self.error('Pools list is empty') return False - except (ValueError, IndexError, AttributeError, SyntaxError): - self.error('Pools configurations is incorrect') + except (ValueError, IndexError, AttributeError, SyntaxError) as e: + self.error('Pools configurations is incorrect', str(e)) return False - + + # Creating static charts + self.order = ['parse_time', 'leases_size', 'utilization', 'total'] + self.definitions = {'utilization': + {'options': + [None, 'Pools utilization', 'used %', 'utilization', 'isc_dhcpd.util', 'line'], + 'lines': []}, + 'total': + {'options': + [None, 'Total all pools', 'leases', 'utilization', 'isc_dhcpd.total', 'line'], + 'lines': [['total', 'leases', 'absolute']]}, + 'parse_time': + {'options': + [None, 'Parse time', 'ms', 'parse stats', 'isc_dhcpd.parse', 'line'], + 'lines': [['ptime', 'time', 'absolute']]}, + 'leases_size': + {'options': + [None, 'dhcpd.leases file size', 'kilobytes', 'parse stats', 'isc_dhcpd.lsize', 'line'], + 'lines': [['lsize', 'size', 'absolute']]}} # Creating dynamic charts - self.order = ['utilization'] - self.definitions = {'utilization': {'options': [None, 'Pools utilization', 'used %', 'Utulization', 'isc_dhcpd.util', 'line'], 'lines': []} } for pool in self.pools: self.definitions['utilization']['lines'].append([''.join(['ut_', pool]), pool, 'absolute']) self.order.append(''.join(['leases_', pool])) self.definitions[''.join(['leases_', pool])] = \ - {'options': [None, 'Active leases', 'leases', 'Leases', 'isc_dhcpd.lease', 'area'], + {'options': [None, 'Active leases', 'leases', 'pools', 'isc_dhcpd.lease', 'area'], 'lines': [[''.join(['le_', pool]), pool, 'absolute']]} self.info('Plugin was started succesfully') @@ -60,50 +80,85 @@ class Service(SimpleService): def _get_raw_data(self): """ - Open log file - :return: str + Parses log file + :return: tuple( + [ipaddress, lease end time, ...], + time to parse leases file + ) """ try: - with open(self.leases_path, 'rt') as leases: - result = leases.read() - except Exception: + with open(self.leases_path, 'rt') as dhcp_leases: + time_start = time() + part1 = filterfalse(find_lease, dhcp_leases) + part2 = filterfalse(find_ends, dhcp_leases) + raw_result = dict(zip(part1, part2)) + time_end = time() + file_parse_time = round((time_end - time_start) * 1000) + except Exception as e: + self.error("Failed to parse leases file:", str(e)) return None else: + result = (raw_result, file_parse_time) return result def _get_data(self): """ - Parse dhcpd.leases file. + :return: dict """ raw_leases = self._get_raw_data() - all_leases = dict(self.regex.findall(' '.join(raw_leases.split()))) - - if not all_leases: - self.error('Cant parse leases file correctly') + if not raw_leases: return None + # Result: {ipaddress: end lease time, ...} + all_leases = dict([(k[6:len(k)-3], v[7:len(v)-2]) for k, v in raw_leases[0].items()]) + # Result: [active binding, active binding....]. (Expire time (ends date;) - current time > 0) - active_leases = [k for k, v in all_leases.items() if is_bind_active(all_leases[k])] + active_leases = [k for k, v in all_leases.items() if is_binding_active(all_leases[k])] # Result: {pool: number of active bindings in pool, ...} - pools_count = {pool: len([lease for lease in active_leases if is_address_in(lease, pool)]) for pool in self.pools} + pools_count = dict([(pool, len([lease for lease in active_leases if is_address_in(lease, pool)])) + for pool in self.pools]) - # Result: {pool: number of host ip addresses in pool, } - pools_max = {pool: (2 ** (32 - int(pool.split('/')[1])) - 2) for pool in self.pools} + # Result: {pool: number of host ip addresses in pool, ...} + pools_max = dict([(pool, (2 ** (32 - int(pool.split('/')[1])) - 2)) + for pool in self.pools]) # Result: {pool: % utilization, ....} (percent) - pools_util = {pool:int(round(float(pools_count[pool]) / pools_max[pool] * 100, 0)) for pool in self.pools} + pools_util = dict([(pool, int(round(float(pools_count[pool]) / pools_max[pool] * 100, 0))) + for pool in self.pools]) # Bulding dicts to send to netdata - final_count = {''.join(['le_', k]): v for k, v in pools_count.items()} - final_util = {''.join(['ut_', k]): v for k, v in pools_util.items()} + final_count = dict([(''.join(['le_', k]), v) for k, v in pools_count.items()]) + final_util = dict([(''.join(['ut_', k]), v) for k, v in pools_util.items()]) + + to_netdata = {'total': len(active_leases)} + to_netdata.update({'lsize': int(stat(self.leases_path)[6] / 1024)}) + to_netdata.update({'ptime': int(raw_leases[1])}) + to_netdata.update(final_util) + to_netdata.update(final_count) + + return to_netdata - final_count.update(final_util) - return final_count - -def is_bind_active(binding): +def is_binding_active(binding): return mktime(strptime(binding, '%w %Y/%m/%d %H:%M:%S')) - mktime(gmtime()) > 0 + def is_address_in(address, pool): - return ipaddress(address) in ip_network(pool) + return ipaddress(return_utf(address)) in ip_network(return_utf(pool)) + + +def find_lease(value): + return value[0:3] != 'lea' + + +def find_ends(value): + return value[2:6] != 'ends' + + +def return_utf(s): + # python2 returns "" for simple strings + # python3 returns "" for unicode strings + if str(type(s)) == "": + return unicode(s, 'utf-8') + return s