]> arthur.barton.de Git - netdata.git/blob - python.d/varnish.chart.py
typo fix
[netdata.git] / python.d / varnish.chart.py
1 # -*- coding: utf-8 -*-
2 # Description:  varnish netdata python.d module
3 # Author: l2isbad
4
5 from base import SimpleService
6 from re import compile
7 from os import access as is_executable, X_OK
8 from subprocess import Popen, PIPE
9
10
11 # default module values (can be overridden per job in `config`)
12 # update_every = 2
13 priority = 60000
14 retries = 60
15
16 ORDER = ['hit_rate', 'chit_rate', 'request_rate', 'transfer_rates', 'session', 'backend_traffic', 'memory_usage', 'bad', 'uptime']
17 EXTRA_ORDER = ['hit_rate','chit_rate', 'request_rate', 'transfer_rates', 'session', 'backend_traffic', 'bad',
18                'objects', 'threads', 'memory_usage', 'objects_per_objhead', 'losthdr', 'hcb', 'esi', 'session_herd',
19                'shm_writes', 'shm', 'allocations', 'vcl', 'bans', 'bans_lurker', 'expunge', 'lru', 'gzip', 'uptime']
20
21 CHARTS = {'allocations': 
22              {'lines': [['sm_nreq', None, 'incremental', 1, 1],
23                        ['sma_nreq', None, 'incremental', 1, 1],
24                        ['sms_nreq', None, 'incremental', 1, 1]],
25               'options': [None, 'Memory allocation requests', 'units', 'Extra charts', 'varnish.alloc','line']},
26           'backend_traffic': 
27              {'lines': [['backend_conn_bt', 'conn', 'incremental', 1, 1],
28                        ['backend_unhealthy', 'unhealthy', 'incremental', 1, 1],
29                        ['backend_busy', 'busy', 'incremental', 1, 1],
30                        ['backend_fail', 'fail', 'incremental', 1, 1],
31                        ['backend_reuse', 'reuse', 'incremental', 1, 1],
32                        ['backend_recycle', 'resycle', 'incremental', 1, 1],
33                        ['backend_toolate', 'toolate', 'incremental', 1, 1],
34                        ['backend_retry', 'retry', 'incremental', 1, 1],
35                        ['backend_req', 'req', 'incremental', 1, 1]],
36               'options': [None, 'Backend health', 'units', 'Backend health', 'varnish.backend_traf', 'line']},
37           'bad': 
38              {'lines': [['sess_drop_b', None, 'incremental', 1, 1],
39                        ['backend_unhealthy_b', None, 'incremental', 1, 1],
40                        ['fetch_failed', None, 'incremental', 1, 1],
41                        ['backend_busy_b', None, 'incremental', 1, 1],
42                        ['threads_failed_b', None, 'incremental', 1, 1],
43                        ['threads_limited_b', None, 'incremental', 1, 1],
44                        ['threads_destroyed_b', None, 'incremental', 1, 1],
45                        ['thread_queue_len', None, 'absolute', 1, 1],
46                        ['losthdr_b', None, 'incremental', 1, 1],
47                        ['esi_errors_b', None, 'incremental', 1, 1],
48                        ['esi_warnings_b', None, 'incremental', 1, 1],
49                        ['sess_fail_b', None, 'incremental', 1, 1],
50                        ['sess_pipe_overflow_b', None, 'incremental', 1, 1]],
51               'options': [None, 'Misbehavior', 'units', 'Problems summary', 'varnish.bad', 'line']},
52           'bans': 
53              {'lines': [['bans', None, 'absolute', 1, 1],
54                        ['bans_added', 'added', 'incremental', 1, 1],
55                        ['bans_deleted', 'deleted', 'incremental', 1, 1],
56                        ['bans_completed', 'completed', 'absolute', 1, 1],
57                        ['bans_obj', 'obj', 'absolute', 1, 1],
58                        ['bans_req', 'req', 'absolute', 1, 1],
59                        ['bans_tested', 'tested', 'incremental', 1, 1],
60                        ['bans_obj_killed', 'obj_killed', 'incremental', 1, 1],
61                        ['bans_tests_tested', 'tests_tested', 'incremental', 1, 1],
62                        ['bans_dups', 'dups', 'absolute', 1, 1],
63                        ['bans_persisted_bytes', 'pers_bytes', 'absolute', 1, 1],
64                        ['bans_persisted_fragmentation', 'pers_fragmentation', 'absolute', 1, 1]],
65               'options': [None, 'Bans', 'units', 'Extra charts', 'varnish.bans', 'line']},
66           'bans_lurker': 
67              {'lines': [['bans_lurker_tested', 'tested', 'incremental', 1, 1],
68                        ['bans_lurker_tests_tested', 'tests_tested', 'incremental', 1, 1],
69                        ['bans_lurker_obj_killed', 'obj_killed', 'incremental', 1, 1],
70                        ['bans_lurker_contention', 'contention', 'incremental', 1, 1]],
71               'options': [None, 'Ban Lurker', 'units', 'Extra charts', 'varnish.bans_lurker', 'line']},
72           'esi':
73              {'lines': [['esi_parse', None, 'incremental', 1, 1],
74                        ['esi_errors', None, 'incremental', 1, 1],
75                        ['esi_warnings', None, 'incremental', 1, 1]],
76               'options': [None, 'ESI', 'units', 'Extra charts', 'varnish.esi', 'line']},
77           'expunge':
78              {'lines': [['n_expired', None, 'incremental', 1, 1],
79                        ['n_lru_nuked_e', None, 'incremental', 1, 1]],
80               'options': [None, 'Object expunging', 'units', 'Extra charts', 'varnish.expunge', 'line']},
81           'gzip': 
82              {'lines': [['n_gzip', None, 'incremental', 1, 1],
83                        ['n_gunzip', None, 'incremental', 1, 1]],
84               'options': [None, 'GZIP activity', 'units', 'Extra charts', 'varnish.gzip', 'line']},
85           'hcb': 
86              {'lines': [['hcb_nolock', 'nolock', 'incremental', 1, 1],
87                        ['hcb_lock', 'lock', 'incremental', 1, 1],
88                        ['hcb_insert', 'insert', 'incremental', 1, 1]],
89               'options': [None, 'Critbit data', 'units', 'Extra charts', 'varnish.hcb', 'line']},
90           'hit_rate': 
91              {'lines': [['cache_hit_perc', 'hit', 'absolute', 1, 100],
92                        ['cache_miss_perc', 'miss', 'absolute', 1, 100],
93                        ['cache_hitpass_perc', 'hitpass', 'absolute', 1, 100]],
94               'options': [None, 'All history hit rate ratio','percent', 'Cache performance', 'varnish.hit_rate', 'stacked']},
95           'chit_rate': 
96              {'lines': [['cache_hit_cperc', 'hit', 'absolute', 1, 100],
97                        ['cache_miss_cperc', 'miss', 'absolute', 1, 100],
98                        ['cache_hitpass_cperc', 'hitpass', 'absolute', 1, 100]],
99               'options': [None, 'Current poll hit rate ratio','percent', 'Cache performance', 'varnish.chit_rate', 'stacked']},
100           'losthdr': 
101              {'lines': [['losthdr', None, 'incremental', 1, 1]],
102               'options': [None, 'HTTP Header overflows', 'units', 'Extra charts', 'varnish.losthdr', 'line']},
103           'lru':
104              {'lines': [['n_lru_nuked', 'nuked', 'incremental', 1, 1],
105                        ['n_lru_moved', 'moved', 'incremental', 1, 1]],
106               'options': [None, 'LRU activity', 'units', 'Extra charts', 'varnish.lru', 'line']},
107           'memory_usage': 
108              {'lines': [['s0.g_space', 'available', 'absolute', 1, 1048576],
109                        ['s0.g_bytes', 'allocated', 'absolute', -1, 1048576]],
110               'options': [None, 'Memory usage', 'megabytes', 'Memory usage', 'varnish.memory_usage', 'stacked']},
111           'objects': 
112              {'lines': [['n_object', 'object', 'absolute', 1, 1],
113                        ['n_objectcore', 'objectcore', 'absolute', 1, 1],
114                        ['n_vampireobject', 'vampireobject, ''absolute', 1, 1],
115                        ['n_objecthead', 'objecthead', 'absolute', 1, 1]],
116               'options': [None, 'Number of objects', 'units', 'Extra charts', 'varnish.objects', 'line']},
117           'objects_per_objhead': 
118              {'lines': [['obj_per_objhead', 'per_objhead', 'absolute', 1, 100]],
119               'options': [None, 'Objects per objecthead', 'units', 'Extra charts', 'varnish.objects_per_objhead', 'line']},
120           'request_rate': 
121              {'lines': [['sess_conn_rr', None, 'incremental', 1, 1],
122                        ['client_req', None, 'incremental', 1, 1],
123                        ['cache_hit', None, 'incremental', 1, 1],
124                        ['cache_hitpass', None, 'incremental', 1, 1],
125                        ['cache_miss', None, 'incremental', 1, 1],
126                        ['backend_conn', None, 'incremental', 1, 1],
127                        ['backend_unhealthy', None, 'incremental', 1, 1],
128                        ['s_pipe', None, 'incremental', 1, 1],
129                        ['s_pass', None, 'incremental', 1, 1]],
130               'options': [None, 'Request rates', 'units', 'Varnish statistics', 'varnish.request_rate', 'line']},
131           'session': 
132              {'lines': [['sess_conn', 'conn', 'incremental', 1, 1],
133                        ['sess_drop', 'drop', 'incremental', 1, 1],
134                        ['sess_fail', 'fail', 'incremental', 1, 1],
135                        ['sess_pipe_overflow', 'pipe_overflow', 'incremental', 1, 1],
136                        ['sess_queued', 'queued', 'incremental', 1, 1],
137                        ['sess_dropped', 'dropped', 'incremental', 1, 1],
138                        ['sess_closed', 'closed', 'incremental', 1, 1],
139                        ['sess_pipeline', 'pipeline', 'incremental', 1, 1],
140                        ['sess_readahead' , 'readhead', 'incremental', 1, 1]],
141
142               'options': [None, 'Sessions', 'units', 'Varnish statistics', 'varnish.session', 'line']},
143           'session_herd': 
144              {'lines': [['sess_herd', None, 'incremental', 1, 1]],
145               'options': [None, 'Session herd', 'units', 'Extra charts', 'varnish.session_herd', 'line']},
146           'shm': 
147              {'lines': [['shm_flushes', 'flushes', 'incremental', 1, 1],
148                        ['shm_cont', 'cont', 'incremental', 1, 1],
149                        ['shm_cycles', 'cycles', 'incremental', 1, 1]],
150               'options': [None, 'SHM writes and records', 'units', 'Extra charts', 'varnish.shm', 'line']},
151           'shm_writes': 
152              {'lines': [['shm_records', 'records', 'incremental', 1, 1],
153                        ['shm_writes', 'writes', 'incremental', 1, 1]],
154               'options': [None, 'SHM writes and records', 'units', 'Extra charts', 'varnish.shm_writes', 'line']},
155           'threads': 
156              {'lines': [['threads', None, 'absolute', 1, 1],
157                        ['threads_created', 'created', 'incremental', 1, 1],
158                        ['threads_failed', 'failed', 'incremental', 1, 1],
159                        ['threads_limited', 'limited', 'incremental', 1, 1],
160                        ['threads_destroyed', 'destroyed', 'incremental', 1, 1]],
161               'options': [None, 'Thread status', 'units', 'Extra charts', 'varnish.threads', 'line']},
162           'transfer_rates': 
163              {'lines': [['s_resp_hdrbytes', 'header', 'incremental', 8, 1000],
164                        ['s_resp_bodybytes', 'body', 'incremental', -8, 1000]],
165               'options': [None, 'Transfer rates', 'kilobit/s', 'Varnish statistics', 'varnish.transfer_rates', 'area']},
166           'uptime': 
167              {'lines': [['uptime', None, 'absolute', 1, 1]],
168               'options': [None, 'Varnish uptime', 'seconds', 'Varnish statistics', 'varnish.uptime', 'line']},
169           'vcl': 
170              {'lines': [['n_backend', None, 'absolute', 1, 1],
171                        ['n_vcl', None, 'incremental', 1, 1],
172                        ['n_vcl_avail', None, 'incremental', 1, 1],
173                        ['n_vcl_discard', None, 'incremental', 1, 1]],
174               'options': [None, 'VCL', 'units', 'Extra charts', 'varnish.vcl', 'line']}}
175
176 DIRECTORIES = ['/bin/', '/usr/bin/', '/sbin/', '/usr/sbin/']
177
178
179 class Service(SimpleService):
180     def __init__(self, configuration=None, name=None):
181         SimpleService.__init__(self, configuration=configuration, name=name)
182         try:
183             self.varnish = [''.join([directory, 'varnishstat']) for directory in DIRECTORIES
184                          if is_executable(''.join([directory, 'varnishstat']), X_OK)][0]
185         except IndexError:
186             self.varnish = False
187         self.rgx_all = compile(r'([A-Z]+\.)([\d\w_.]+)\s+(\d+)')
188         # Could be
189         # VBE.boot.super_backend.pipe_hdrbyte (new)
190         # or
191         # VBE.default2(127.0.0.2,,81).bereq_bodybytes (old)
192         # Regex result: [('super_backend', 'beresp_hdrbytes', '0'), ('super_backend', 'beresp_bodybytes', '0')]
193         self.rgx_bck = (compile(r'VBE.([\d\w_.]+)\(.*?\).(beresp[\w_]+)\s+(\d+)'),
194                         compile(r'VBE.boot.([\w\d_]+).(beresp[\w_]+)\s+(\d+)'))
195         self.extra_charts = self.configuration.get('extra_charts', [])
196         self.cache_prev = list()
197
198     def check(self):
199         # Cant start without 'varnishstat' command
200         if not self.varnish:
201             self.error('\'varnishstat\' command was not found in %s or not executable by netdata' % DIRECTORIES)
202             return False
203
204         # If command is present and we can execute it we need to make sure..
205         # 1. STDOUT is not empty
206         reply = self._get_raw_data()
207         if not reply:
208             self.error('No output from \'varnishstat\' (not enough privileges?)')
209             return False
210
211         # 2. Output is parsable (list is not empty after regex findall)
212         is_parsable = self.rgx_all.findall(reply)
213         if not is_parsable:
214             self.error('Cant parse output (only varnish version 4+ supported)')
215             return False
216
217         # We need to find the right regex for backend parse
218         self.backend_list = self.rgx_bck[0].findall(reply)[::2]
219         if self.backend_list:
220             self.rgx_bck = self.rgx_bck[0]
221         else:
222             self.backend_list = self.rgx_bck[1].findall(reply)[::2]
223             self.rgx_bck = self.rgx_back[1]
224
225         # We are about to start!
226         self.create_charts()
227
228         self.info('Active charts: %s' % self.order)
229         self.info('Plugin was started successfully')
230         return True
231      
232     def _get_raw_data(self):
233         try:
234             reply = Popen([self.varnish, '-1'], stdout=PIPE, stderr=PIPE, shell=False)
235         except OSError:
236             return None
237
238         raw_data = reply.communicate()[0]
239
240         if not raw_data:
241             return None
242
243         return raw_data
244
245     def _get_data(self):
246         """
247         Format data received from shell command
248         :return: dict
249         """
250         raw_data = self._get_raw_data()
251         data_all = self.rgx_all.findall(raw_data)
252         data_backend = self.rgx_bck.findall(raw_data)
253
254         if not data_all:
255             return None
256
257         # 1. ALL data from 'varnishstat -1'. t - type(MAIN, MEMPOOL etc)
258         to_netdata = {k: int(v) for t, k, v in data_all}
259         
260         # 2. ADD backend statistics
261         to_netdata.update({'_'.join([n, k]): int(v) for n, k, v in data_backend})
262
263         # 3. ADD additional keys to dict
264         # 3.1 Cache hit/miss/hitpass OVERALL in percent
265         cache_summary = sum([to_netdata.get('cache_hit', 0), to_netdata.get('cache_miss', 0),
266                              to_netdata.get('cache_hitpass', 0)])
267         to_netdata['cache_hit_perc'] = find_percent(to_netdata.get('cache_hit', 0), cache_summary, 10000)
268         to_netdata['cache_miss_perc'] = find_percent(to_netdata.get('cache_miss', 0), cache_summary, 10000)
269         to_netdata['cache_hitpass_perc'] = find_percent(to_netdata.get('cache_hitpass', 0), cache_summary, 10000)
270
271         # 3.2 Cache hit/miss/hitpass CURRENT in percent
272         if self.cache_prev:
273             cache_summary = sum([to_netdata.get('cache_hit', 0), to_netdata.get('cache_miss', 0),
274                                  to_netdata.get('cache_hitpass', 0)]) - sum(self.cache_prev)
275             to_netdata['cache_hit_cperc'] = find_percent(to_netdata.get('cache_hit', 0) - self.cache_prev[0], cache_summary, 10000)
276             to_netdata['cache_miss_cperc'] = find_percent(to_netdata.get('cache_miss', 0) - self.cache_prev[1], cache_summary, 10000)
277             to_netdata['cache_hitpass_cperc'] = find_percent(to_netdata.get('cache_hitpass', 0) - self.cache_prev[2], cache_summary, 10000)
278         else:
279             to_netdata['cache_hit_cperc'] = 0
280             to_netdata['cache_miss_cperc'] = 0
281             to_netdata['cache_hitpass_cperc'] = 0
282
283         self.cache_prev = [to_netdata.get('cache_hit', 0), to_netdata.get('cache_miss', 0), to_netdata.get('cache_hitpass', 0)]
284
285         # 3.2 Copy random stuff to new keys (do we need this?)
286         to_netdata['obj_per_objhead'] = find_percent(to_netdata.get('n_object', 0),
287                                                      to_netdata.get('n_objecthead', 0), 100)
288         to_netdata['backend_conn_bt'] = to_netdata.get('backend_conn', 0)
289         to_netdata['sess_conn_rr'] = to_netdata.get('sess_conn', 0)
290         to_netdata['n_lru_nuked_e'] = to_netdata.get('n_lru_nuked', 0)
291
292         for elem in ['backend_busy', 'backend_unhealthy', 'esi_errors', 'esi_warnings', 'losthdr', 'sess_drop',
293                      'sess_fail', 'sess_pipe_overflow', 'threads_destroyed', 'threads_failed', 'threads_limited']:
294             to_netdata[''.join([elem, '_b'])] = to_netdata.get(elem, 0)
295
296         # Ready steady go!
297         return to_netdata
298
299     def create_charts(self):
300         # If 'all_charts' is true...ALL charts are displayed. If no only default + 'extra_charts'
301         if self.configuration.get('all_charts'):
302             self.order = EXTRA_ORDER
303         else:
304             try:
305                 extra_charts = list(filter(lambda chart: chart in EXTRA_ORDER, self.extra_charts.split()))
306             except (AttributeError, NameError, ValueError):
307                 self.error('Extra charts disabled.')
308                 extra_charts = []
309     
310             self.order = ORDER[:]
311             self.order.extend(extra_charts)
312
313         # Create static charts
314         self.definitions = {chart: values for chart, values in CHARTS.items() if chart in self.order}
315  
316         # Create dynamic backend charts
317         if self.backend_list:
318             for backend in self.backend_list:
319                 self.order.insert(0, ''.join([backend[0], '_resp_stats']))
320                 self.definitions.update({''.join([backend[0], '_resp_stats']): {
321                     'options': [None,
322                                 '%s response statistics' % backend[0].capitalize(),
323                                 "kilobit/s",
324                                 'Backend response',
325                                 'varnish.backend',
326                                 'area'],
327                     'lines': [[''.join([backend[0], '_beresp_hdrbytes']),
328                                'header', 'incremental', 8, 1000],
329                               [''.join([backend[0], '_beresp_bodybytes']),
330                                'body', 'incremental', -8, 1000]]}})
331
332
333 def find_percent(value1, value2, multiply):
334     # If value2 is 0 return 0
335     if not value2:
336         return 0
337     else:
338         return round(float(value1) / float(value2) * multiply)