1 # -*- coding: utf-8 -*-
2 # Description: varnish netdata python.d module
5 from base import SimpleService
7 from os import access as is_executable, X_OK
8 from subprocess import Popen, PIPE
11 # default module values (can be overridden per job in `config`)
16 ORDER = ['request_rate', 'hit_rate', 'transfer_rates', 'session', 'backend_traffic', 'bad', 'uptime']
17 EXTRA_ORDER = ['request_rate', 'hit_rate', 'backend_traffic', 'objects', 'transfer_rates', 'threads', 'memory_usage',
18 'objects_per_objhead', 'losthdr', 'hcb', 'esi', 'session', 'session_herd', 'shm_writes',
19 'shm', 'allocations', 'vcl', 'bans', 'bans_lurker', 'expunge', 'lru', 'bad', 'gzip', 'uptime']
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', 'allocations', 'varnish.alloc','line']},
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 traffic', 'units', 'backend_traffic', 'varnish.backend_traf', 'line']},
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', 'bad', 'varnish.bad', 'line']},
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', 'bans', 'varnish.bans', 'line']},
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', 'bans_lurker', 'varnish.bans_lurker', 'line']},
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', 'esi', 'varnish.esi', 'line']},
78 {'lines': [['n_expired', None, 'incremental', 1, 1],
79 ['n_lru_nuked_e', None, 'incremental', 1, 1]],
80 'options': [None, 'Object expunging', 'units', 'expunge', 'varnish.expunge', 'line']},
82 {'lines': [['n_gzip', None, 'incremental', 1, 1],
83 ['n_gunzip', None, 'incremental', 1, 1]],
84 'options': [None, 'GZIP activity', 'units', 'gzip', 'varnish.gzip', 'line']},
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', 'hcb', 'varnish.hcb', 'line']},
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, 'Hit rates','percent', 'hit_rate', 'varnish.hit_rate', 'line']},
96 {'lines': [['losthdr', None, 'incremental', 1, 1]],
97 'options': [None, 'HTTP Header overflows', 'units', 'losthdr', 'varnish.losthdr', 'line']},
99 {'lines': [['n_lru_nuked', 'nuked', 'incremental', 1, 1],
100 ['n_lru_moved', 'moved', 'incremental', 1, 1]],
101 'options': [None, 'LRU activity', 'units', 'lru', 'varnish.lru', 'line']},
103 {'lines': [['sms_balloc', None, 'absolute', 1, 1],
104 ['sms_nbytes', None, 'absolute', 1, 1]],
105 'options': [None, 'Memory usage', 'units', 'memory_usage', 'varnish.memory_usage', 'line']},
107 {'lines': [['n_object', 'object', 'absolute', 1, 1],
108 ['n_objectcore', 'objectcore', 'absolute', 1, 1],
109 ['n_vampireobject', 'vampireobject, ''absolute', 1, 1],
110 ['n_objecthead', 'objecthead', 'absolute', 1, 1]],
111 'options': [None, 'Number of objects', 'units', 'objects', 'varnish.objects', 'line']},
112 'objects_per_objhead':
113 {'lines': [['obj_per_objhead', 'per_objhead', 'absolute', 1, 100]],
114 'options': [None, 'Objects per objecthead', 'units', 'objects_per_objhead', 'varnish.objects_per_objhead', 'line']},
116 {'lines': [['sess_conn_rr', None, 'incremental', 1, 1],
117 ['client_req', None, 'incremental', 1, 1],
118 ['cache_hit', None, 'incremental', 1, 1],
119 ['cache_hitpass', None, 'incremental', 1, 1],
120 ['cache_miss', None, 'incremental', 1, 1],
121 ['backend_conn', None, 'incremental', 1, 1],
122 ['backend_unhealthy', None, 'incremental', 1, 1],
123 ['s_pipe', None, 'incremental', 1, 1],
124 ['s_pass', None, 'incremental', 1, 1]],
125 'options': [None, 'Request rates', 'units', 'request_rate', 'varnish.request_rate', 'line']},
127 {'lines': [['sess_conn', 'conn', 'incremental', 1, 1],
128 ['sess_drop', 'drop', 'incremental', 1, 1],
129 ['sess_fail', 'fail', 'incremental', 1, 1],
130 ['sess_pipe_overflow', 'pipe_overflow', 'incremental', 1, 1],
131 ['sess_queued', 'queued', 'incremental', 1, 1],
132 ['sess_dropped', 'dropped', 'incremental', 1, 1],
133 ['sess_closed', 'closed', 'incremental', 1, 1],
134 ['sess_pipeline', 'pipeline', 'incremental', 1, 1],
135 ['sess_readahead' , 'readhead', 'incremental', 1, 1]],
137 'options': [None, 'Sessions', 'units', 'session', 'varnish.session', 'line']},
139 {'lines': [['sess_herd', None, 'incremental', 1, 1]],
140 'options': [None, 'Session herd', 'units', 'session_herd', 'varnish.session_herd', 'line']},
142 {'lines': [['shm_flushes', 'flushes', 'incremental', 1, 1],
143 ['shm_cont', 'cont', 'incremental', 1, 1],
144 ['shm_cycles', 'cycles', 'incremental', 1, 1]],
145 'options': [None, 'SHM writes and records', 'units', 'shm', 'varnish.shm', 'line']},
147 {'lines': [['shm_records', 'records', 'incremental', 1, 1],
148 ['shm_writes', 'writes', 'incremental', 1, 1]],
149 'options': [None, 'SHM writes and records', 'units', 'shm_writes', 'varnish.shm_writes', 'line']},
151 {'lines': [['threads', None, 'absolute', 1, 1],
152 ['threads_created', 'created', 'incremental', 1, 1],
153 ['threads_failed', 'failed', 'incremental', 1, 1],
154 ['threads_limited', 'limited', 'incremental', 1, 1],
155 ['threads_destroyed', 'destroyed', 'incremental', 1, 1]],
156 'options': [None, 'Thread status', 'units', 'threads', 'varnish.threads', 'line']},
158 {'lines': [['s_resp_hdrbytes', 'resp_hdrbytes', 'incremental', 8, 1],
159 ['s_resp_bodybytes', 'resp_bodybytes', 'incremental', 8, 1]],
160 'options': [None, 'Transfer rates', 'bits/s', 'transfer_rates', 'varnish.transfer_rates', 'line']},
162 {'lines': [['uptime', None, 'absolute', 1, 1]],
163 'options': [None, 'Varnish uptime', 'seconds', 'uptime', 'varnish.uptime', 'line']},
165 {'lines': [['n_backend', None, 'absolute', 1, 1],
166 ['n_vcl', None, 'incremental', 1, 1],
167 ['n_vcl_avail', None, 'incremental', 1, 1],
168 ['n_vcl_discard', None, 'incremental', 1, 1]],
169 'options': [None, 'VCL', 'units', 'vcl', 'varnish.vcl', 'line']}}
171 DIRECTORIES = ['/bin/', '/usr/bin/', '/sbin/', '/usr/sbin/']
174 class Service(SimpleService):
175 def __init__(self, configuration=None, name=None):
176 SimpleService.__init__(self, configuration=configuration, name=name)
178 self.varnish = [''.join([directory, 'varnishstat']) for directory in DIRECTORIES
179 if is_executable(''.join([directory, 'varnishstat']), X_OK)][0]
182 self.rgx_all = compile(r'([A-Z]+\.)([\d\w_.]+)\s+(\d+)')
184 # VBE.boot.super_backend.pipe_hdrbyte (new)
186 # VBE.default2(127.0.0.2,,81).bereq_bodybytes (old)
187 # Regex result: [('super_backend', 'beresp_hdrbytes', '0'), ('super_backend', 'beresp_bodybytes', '0')]
188 self.rgx_bck = (compile(r'VBE.([\d\w_.]+)\(.*?\).(beresp[\w_]+)\s+(\d+)'),
189 compile(r'VBE.boot.([\w\d_]+).(beresp[\w_]+)\s+(\d+)'))
190 self.extra_charts = self.configuration.get('extra_charts', [])
193 # Cant start without 'varnishstat' command
195 self.error('\'varnishstat\' command was not found in %s or not executable by netdata' % DIRECTORIES)
198 # If command is present and we can execute it we need to make sure..
199 # 1. STDOUT is not empty
200 reply = self._get_raw_data()
202 self.error('No output from \'varnishstat\' (not enough privileges?)')
205 # 2. Output is parsable (list is not empty after regex findall)
206 is_parsable = self.rgx_all.findall(reply)
208 self.error('Cant parse output (only varnish version 4+ supported)')
211 # We need to find the right regex for backend parse
212 self.backend_list = self.rgx_bck[0].findall(reply)[::2]
213 if self.backend_list:
214 self.rgx_bck = self.rgx_bck[0]
216 self.backend_list = self.rgx_bck[1].findall(reply)[::2]
217 self.rgx_bck = self.rgx_back[1]
219 # We are about to start!
222 self.info('Active charts: %s' % self.order)
223 self.info('Plugin was started successfully')
226 def _get_raw_data(self):
228 reply = Popen([self.varnish, '-1'], stdout=PIPE, stderr=PIPE, shell=False)
232 raw_data = reply.stdout.read()
241 Format data received from shell command
244 raw_data = self._get_raw_data()
245 data_all = self.rgx_all.findall(raw_data)
246 data_backend = self.rgx_bck.findall(raw_data)
251 # 1. ALL data from 'varnishstat -1'. t - type(MAIN, MEMPOOL etc)
252 to_netdata = {k: int(v) for t, k, v in data_all}
254 # 2. ADD backend statistics
255 to_netdata.update({'_'.join([n, k]): int(v) for n, k, v in data_backend})
257 # 3. ADD additional keys to dict
258 # 3.1 Cache hit/miss/hitpass overall in percent
259 cache_summary = sum([to_netdata.get('cache_hit', 0), to_netdata.get('cache_miss', 0),
260 to_netdata.get('cache_hitpass', 0)])
261 to_netdata['cache_hit_perc'] = find_percent(to_netdata.get('cache_hit', 0), cache_summary, 10000)
262 to_netdata['cache_miss_perc'] = find_percent(to_netdata.get('cache_miss', 0), cache_summary, 10000)
263 to_netdata['cache_hitpass_perc'] = find_percent(to_netdata.get('cache_hitpass', 0), cache_summary, 10000)
265 # 3.2 Copy random stuff to new keys (do we need this?)
266 to_netdata['obj_per_objhead'] = find_percent(to_netdata.get('n_object', 0),
267 to_netdata.get('n_objecthead', 0), 100)
268 to_netdata['backend_conn_bt'] = to_netdata.get('backend_conn', 0)
269 to_netdata['sess_conn_rr'] = to_netdata.get('sess_conn', 0)
270 to_netdata['n_lru_nuked_e'] = to_netdata.get('n_lru_nuked', 0)
272 for elem in ['backend_busy', 'backend_unhealthy', 'esi_errors', 'esi_warnings', 'losthdr', 'sess_drop',
273 'sess_fail', 'sess_pipe_overflow', 'threads_destroyed', 'threads_failed', 'threads_limited']:
274 to_netdata[''.join([elem, '_b'])] = to_netdata.get(elem, 0)
279 def create_charts(self):
280 # If 'all_charts' is true...ALL charts are displayed. If no only default + 'extra_charts'
281 if self.configuration.get('all_charts'):
282 self.order = EXTRA_ORDER
285 extra_charts = list(filter(lambda chart: chart in EXTRA_ORDER, self.extra_charts.split()))
286 except (AttributeError, NameError, ValueError):
287 self.error('Extra charts disabled.')
290 self.order = ORDER[:]
291 self.order.extend(extra_charts)
293 # Create static charts
294 self.definitions = {chart: values for chart, values in CHARTS.items() if chart in self.order}
296 # Create dynamic backend charts
297 if self.backend_list:
298 for backend in self.backend_list:
299 self.order.insert(0, ''.join([backend[0], '_resp_stats']))
300 self.definitions.update({''.join([backend[0], '_resp_stats']): {
302 '%s response statistics' % backend[0].capitalize(),
307 'lines': [[''.join([backend[0], '_beresp_hdrbytes']),
308 'header', 'incremental', 8, 1],
309 [''.join([backend[0], '_beresp_bodybytes']),
310 'body', 'incremental', -8, 1]]}})
313 def find_percent(value1, value2, multiply):
314 # If value 2 is 0 return 0
318 return round(float(value1) / float(value2) * multiply)