]> arthur.barton.de Git - netdata.git/blob - python.d/isc_dhcpd.chart.py
add dhcp.leases file size chart to Parse statistics family
[netdata.git] / python.d / isc_dhcpd.chart.py
1 # -*- coding: utf-8 -*-
2 # Description: isc dhcpd lease netdata python.d module
3 # Author: l2isbad
4
5 from base import SimpleService
6 from re import compile
7 from time import mktime, strptime, gmtime, time
8 from os import stat
9 try:
10     from ipaddress import IPv4Address as ipaddress
11     from ipaddress import ip_network
12     have_ipaddress = True
13 except ImportError:
14     have_ipaddress = False
15
16 priority = 60000
17 retries = 60
18 update_every = 60
19
20 class Service(SimpleService):
21     def __init__(self, configuration=None, name=None):
22         SimpleService.__init__(self, configuration=configuration, name=name)
23         self.leases_path = self.configuration.get('leases_path', '/var/lib/dhcp/dhcpd.leases')
24         self.pools = self.configuration.get('pools')
25
26         # Will work only with 'default' db-time-format (weekday year/month/day hour:minute:second)
27         # TODO: update algorithm to parse correctly 'local' db-time-format
28         # (epoch <seconds-since-epoch>; # <day-name> <month-name> <day-number> <hours>:<minutes>:<seconds> <year>)
29         # Also only ipv4 supported
30         self.regex = compile(r'\d+(?:\.\d+){3}')
31
32     def check(self):
33         if not self._get_raw_data():
34             self.error('Make sure leases_path is correct and leases log file is readable by netdata')
35             return False
36         elif not have_ipaddress:
37             self.error('No ipaddress module. Please install (py2-ipaddress in case of python2)')
38             return False
39         else:
40             try:
41                 self.pools = self.pools.split()
42                 if not [ip_network(pool) for pool in self.pools]:
43                     self.error('Pools list is empty')
44                     return False
45             except (ValueError, IndexError, AttributeError, SyntaxError):
46                 self.error('Pools configurations is incorrect')
47                 return False
48             
49             # Creating dynamic charts
50             self.order = ['parse_time', 'leases_size', 'utilization']
51             self.definitions = {'utilization':
52                                     {'options':
53                                          [None, 'Pools utilization', 'used %', 'Utulization', 'isc_dhcpd.util', 'line'],
54                                      'lines': []},
55                                'parse_time':
56                                    {'options':
57                                         [None, 'Parse time', 'ms', 'Parse statistics', 'isc_dhcpd.parse', 'line'],
58                                     'lines': [['ptime', 'time', 'absolute']]},
59                                'leases_size':
60                                    {'options':
61                                         [None, 'dhcpd.leases file size', 'kilobytes', 'Parse statistics', 'isc_dhcpd.lsize', 'line'],
62                                     'lines': [['lsize', 'size', 'absolute']]}}
63             for pool in self.pools:
64                 self.definitions['utilization']['lines'].append([''.join(['ut_', pool]), pool, 'absolute'])
65                 self.order.append(''.join(['leases_', pool]))
66                 self.definitions[''.join(['leases_', pool])] = \
67                     {'options': [None, 'Active leases', 'leases', 'Leases', 'isc_dhcpd.lease', 'area'], 
68                      'lines': [[''.join(['le_', pool]), pool, 'absolute']]}
69
70             self.info('Plugin was started succesfully')
71             return True
72
73     def _get_raw_data(self):
74         """
75         Parses log file
76         :return: tuple(
77                        [ipaddress, lease end time, ...],
78                        length of list,
79                        time to parse leases file
80                       )
81         """
82         try:
83             with open(self.leases_path, 'rt') as dhcp_leases:
84                 raw_result = []
85
86                 time_start = time()
87                 for line in dhcp_leases:
88                     if line[0:3] == 'lea':
89                         raw_result.append(self.regex.search(line).group())
90                     elif line[2:6] == 'ends':
91                         raw_result.append(line[7:28])
92                     else:
93                         continue
94                 time_end = time()
95                 file_parse_time = round((time_end - time_start) * 1000)
96
97         except Exception:
98             return None
99
100         else:
101             raw_result_length = len(raw_result)
102             result = (raw_result, raw_result_length, file_parse_time)
103             return result
104
105     def _get_data(self):
106         """
107         :return: dict
108         """
109         raw_leases = self._get_raw_data()
110         
111         if not raw_leases:
112             return None
113
114         # Result: {ipaddress: end lease time, ...}
115         all_leases = dict(zip([raw_leases[0][_] for _ in range(0, raw_leases[1], 2)],
116                               [raw_leases[0][_] for _ in range(1, raw_leases[1], 2)]))
117
118         # Result: [active binding, active binding....]. (Expire time (ends date;) - current time > 0)
119         active_leases = [k for k, v in all_leases.items() if is_binding_active(all_leases[k])]
120
121         # Result: {pool: number of active bindings in pool, ...}
122         pools_count = {pool: len([lease for lease in active_leases if is_address_in(lease, pool)])
123                        for pool in self.pools}
124
125         # Result: {pool: number of host ip addresses in pool, ...}
126         pools_max = {pool: (2 ** (32 - int(pool.split('/')[1])) - 2)
127                      for pool in self.pools}
128
129         # Result: {pool: % utilization, ....} (percent)
130         pools_util = {pool:int(round(float(pools_count[pool]) / pools_max[pool] * 100, 0))
131                       for pool in self.pools}
132
133         # Bulding dicts to send to netdata
134         final_count = {''.join(['le_', k]): v for k, v in pools_count.items()}
135         final_util = {''.join(['ut_', k]): v for k, v in pools_util.items()}
136         
137         to_netdata = {'lsize': int(stat(self.leases_path)[6] / 1024)}
138         to_netdata.update({'ptime': int(raw_leases[2])})
139         to_netdata.update(final_util)
140         to_netdata.update(final_count)
141  
142         return to_netdata
143     
144 def is_binding_active(binding):
145     return mktime(strptime(binding, '%w %Y/%m/%d %H:%M:%S')) - mktime(gmtime()) > 0
146
147 def is_address_in(address, pool):
148     return ipaddress(address) in ip_network(pool)