]> arthur.barton.de Git - netdata.git/blob - python.d/varnish.chart.py
ab-debian 0.20170327.01-0ab1, upstream v1.6.0-42-gaa6b96fc
[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 subprocess import Popen, PIPE
8
9 # default module values (can be overridden per job in `config`)
10 # update_every = 2
11 priority = 60000
12 retries = 60
13
14 ORDER = ['session', 'hit_rate', 'chit_rate', 'expunge', 'threads', 'backend_health', 'memory_usage', 'bad', 'uptime']
15
16 CHARTS = {'backend_health': 
17              {'lines': [['backend_conn', 'conn', 'incremental', 1, 1],
18                        ['backend_unhealthy', 'unhealthy', 'incremental', 1, 1],
19                        ['backend_busy', 'busy', 'incremental', 1, 1],
20                        ['backend_fail', 'fail', 'incremental', 1, 1],
21                        ['backend_reuse', 'reuse', 'incremental', 1, 1],
22                        ['backend_recycle', 'resycle', 'incremental', 1, 1],
23                        ['backend_toolate', 'toolate', 'incremental', 1, 1],
24                        ['backend_retry', 'retry', 'incremental', 1, 1],
25                        ['backend_req', 'req', 'incremental', 1, 1]],
26               'options': [None, 'Backend health', 'connections', 'Backend health', 'varnish.backend_traf', 'line']},
27           'bad': 
28              {'lines': [['sess_drop_b', None, 'incremental', 1, 1],
29                        ['backend_unhealthy_b', None, 'incremental', 1, 1],
30                        ['fetch_failed', None, 'incremental', 1, 1],
31                        ['backend_busy_b', None, 'incremental', 1, 1],
32                        ['threads_failed_b', None, 'incremental', 1, 1],
33                        ['threads_limited_b', None, 'incremental', 1, 1],
34                        ['threads_destroyed_b', None, 'incremental', 1, 1],
35                        ['thread_queue_len_b', 'queue_len', 'absolute', 1, 1],
36                        ['losthdr_b', None, 'incremental', 1, 1],
37                        ['esi_errors_b', None, 'incremental', 1, 1],
38                        ['esi_warnings_b', None, 'incremental', 1, 1],
39                        ['sess_fail_b', None, 'incremental', 1, 1],
40                        ['sc_pipe_overflow_b', None, 'incremental', 1, 1],
41                        ['sess_pipe_overflow_b', None, 'incremental', 1, 1]],
42               'options': [None, 'Misbehavior', 'problems', 'Problems summary', 'varnish.bad', 'line']},
43           'expunge':
44              {'lines': [['n_expired', 'expired', 'incremental', 1, 1],
45                        ['n_lru_nuked', 'lru_nuked', 'incremental', 1, 1]],
46               'options': [None, 'Object expunging', 'objects', 'Cache performance', 'varnish.expunge', 'line']},
47           'hit_rate': 
48              {'lines': [['cache_hit_perc', 'hit', 'absolute', 1, 100],
49                        ['cache_miss_perc', 'miss', 'absolute', 1, 100],
50                        ['cache_hitpass_perc', 'hitpass', 'absolute', 1, 100]],
51               'options': [None, 'All history hit rate ratio','percent', 'Cache performance', 'varnish.hit_rate', 'stacked']},
52           'chit_rate': 
53              {'lines': [['cache_hit_cperc', 'hit', 'absolute', 1, 100],
54                        ['cache_miss_cperc', 'miss', 'absolute', 1, 100],
55                        ['cache_hitpass_cperc', 'hitpass', 'absolute', 1, 100]],
56               'options': [None, 'Current poll hit rate ratio','percent', 'Cache performance', 'varnish.chit_rate', 'stacked']},
57           'memory_usage': 
58              {'lines': [['s0.g_space', 'available', 'absolute', 1, 1048576],
59                        ['s0.g_bytes', 'allocated', 'absolute', -1, 1048576]],
60               'options': [None, 'Memory usage', 'megabytes', 'Memory usage', 'varnish.memory_usage', 'stacked']},
61           'session': 
62              {'lines': [['sess_conn', 'sess_conn', 'incremental', 1, 1],
63                        ['client_req', 'client_requests', 'incremental', 1, 1],
64                        ['client_conn', 'client_conn', 'incremental', 1, 1],
65                        ['client_drop', 'client_drop', 'incremental', 1, 1],
66                        ['sess_dropped', 'sess_dropped', 'incremental', 1, 1]],
67               'options': [None, 'Sessions', 'units', 'Client metrics', 'varnish.session', 'line']},
68           'threads': 
69              {'lines': [['threads', None, 'absolute', 1, 1],
70                        ['threads_created', 'created', 'incremental', 1, 1],
71                        ['threads_failed', 'failed', 'incremental', 1, 1],
72                        ['threads_limited', 'limited', 'incremental', 1, 1],
73                        ['thread_queue_len', 'queue_len', 'incremental', 1, 1],
74                        ['sess_queued', 'sess_queued', 'incremental', 1, 1]],
75               'options': [None, 'Thread status', 'threads', 'Thread-related metrics', 'varnish.threads', 'line']},
76           'uptime': 
77              {'lines': [['uptime', None, 'absolute', 1, 1]],
78               'options': [None, 'Varnish uptime', 'seconds', 'Uptime', 'varnish.uptime', 'line']}
79 }
80
81
82 class Service(SimpleService):
83     def __init__(self, configuration=None, name=None):
84         SimpleService.__init__(self, configuration=configuration, name=name)
85         self.varnish = self.find_binary('varnishstat')
86         self.rgx_all = compile(r'([A-Z]+\.)?([\d\w_.]+)\s+(\d+)')
87         # Could be
88         # VBE.boot.super_backend.pipe_hdrbyte (new)
89         # or
90         # VBE.default2(127.0.0.2,,81).bereq_bodybytes (old)
91         # Regex result: [('super_backend', 'beresp_hdrbytes', '0'), ('super_backend', 'beresp_bodybytes', '0')]
92         self.rgx_bck = (compile(r'VBE.([\d\w_.]+)\(.*?\).(beresp[\w_]+)\s+(\d+)'),
93                         compile(r'VBE\.[\d\w-]+\.([\w\d_]+).(beresp[\w_]+)\s+(\d+)'))
94         self.cache_prev = list()
95
96     def check(self):
97         # Cant start without 'varnishstat' command
98         if not self.varnish:
99             self.error('Can\'t locate \'varnishstat\' binary or binary is not executable by netdata')
100             return False
101
102         # If command is present and we can execute it we need to make sure..
103         # 1. STDOUT is not empty
104         reply = self._get_raw_data()
105         if not reply:
106             self.error('No output from \'varnishstat\' (not enough privileges?)')
107             return False
108
109         # 2. Output is parsable (list is not empty after regex findall)
110         is_parsable = self.rgx_all.findall(reply)
111         if not is_parsable:
112             self.error('Cant parse output...')
113             return False
114
115         # We need to find the right regex for backend parse
116         self.backend_list = self.rgx_bck[0].findall(reply)[::2]
117         if self.backend_list:
118             self.rgx_bck = self.rgx_bck[0]
119         else:
120             self.backend_list = self.rgx_bck[1].findall(reply)[::2]
121             self.rgx_bck = self.rgx_bck[1]
122
123         # We are about to start!
124         self.create_charts()
125
126         self.info('Plugin was started successfully')
127         return True
128      
129     def _get_raw_data(self):
130         try:
131             reply = Popen([self.varnish, '-1'], stdout=PIPE, stderr=PIPE, shell=False)
132         except OSError:
133             return None
134
135         raw_data = reply.communicate()[0]
136
137         if not raw_data:
138             return None
139
140         return raw_data.decode()
141
142     def _get_data(self):
143         """
144         Format data received from shell command
145         :return: dict
146         """
147         raw_data = self._get_raw_data()
148         data_all = self.rgx_all.findall(raw_data)
149         data_backend = self.rgx_bck.findall(raw_data)
150
151         if not data_all:
152             return None
153
154         # 1. ALL data from 'varnishstat -1'. t - type(MAIN, MEMPOOL etc)
155         to_netdata = dict([(k, int(v)) for t, k, v in data_all])
156         
157         # 2. ADD backend statistics
158         to_netdata.update(dict([('_'.join([n, k]), int(v)) for n, k, v in data_backend]))
159
160         # 3. ADD additional keys to dict
161         # 3.1 Cache hit/miss/hitpass OVERALL in percent
162         cache_summary = sum([to_netdata.get('cache_hit', 0), to_netdata.get('cache_miss', 0),
163                              to_netdata.get('cache_hitpass', 0)])
164         to_netdata['cache_hit_perc'] = find_percent(to_netdata.get('cache_hit', 0), cache_summary, 10000)
165         to_netdata['cache_miss_perc'] = find_percent(to_netdata.get('cache_miss', 0), cache_summary, 10000)
166         to_netdata['cache_hitpass_perc'] = find_percent(to_netdata.get('cache_hitpass', 0), cache_summary, 10000)
167
168         # 3.2 Cache hit/miss/hitpass CURRENT in percent
169         if self.cache_prev:
170             cache_summary = sum([to_netdata.get('cache_hit', 0), to_netdata.get('cache_miss', 0),
171                                  to_netdata.get('cache_hitpass', 0)]) - sum(self.cache_prev)
172             to_netdata['cache_hit_cperc'] = find_percent(to_netdata.get('cache_hit', 0) - self.cache_prev[0], cache_summary, 10000)
173             to_netdata['cache_miss_cperc'] = find_percent(to_netdata.get('cache_miss', 0) - self.cache_prev[1], cache_summary, 10000)
174             to_netdata['cache_hitpass_cperc'] = find_percent(to_netdata.get('cache_hitpass', 0) - self.cache_prev[2], cache_summary, 10000)
175         else:
176             to_netdata['cache_hit_cperc'] = 0
177             to_netdata['cache_miss_cperc'] = 0
178             to_netdata['cache_hitpass_cperc'] = 0
179
180         self.cache_prev = [to_netdata.get('cache_hit', 0), to_netdata.get('cache_miss', 0), to_netdata.get('cache_hitpass', 0)]
181
182         # 3.3 Problems summary chart
183         for elem in ['backend_busy', 'backend_unhealthy', 'esi_errors', 'esi_warnings', 'losthdr', 'sess_drop', 'sc_pipe_overflow',
184                      'sess_fail', 'sess_pipe_overflow', 'threads_destroyed', 'threads_failed', 'threads_limited', 'thread_queue_len']:
185             if to_netdata.get(elem) is not None:
186                 to_netdata[''.join([elem, '_b'])] = to_netdata.get(elem)
187
188         # Ready steady go!
189         return to_netdata
190
191     def create_charts(self):
192         # If 'all_charts' is true...ALL charts are displayed. If no only default + 'extra_charts'
193         #if self.configuration.get('all_charts'):
194         #    self.order = EXTRA_ORDER
195         #else:
196         #    try:
197         #        extra_charts = list(filter(lambda chart: chart in EXTRA_ORDER, self.extra_charts.split()))
198         #    except (AttributeError, NameError, ValueError):
199         #        self.error('Extra charts disabled.')
200         #        extra_charts = []
201     
202         self.order = ORDER[:]
203         #self.order.extend(extra_charts)
204
205         # Create static charts
206         #self.definitions = {chart: values for chart, values in CHARTS.items() if chart in self.order}
207         self.definitions = CHARTS
208  
209         # Create dynamic backend charts
210         if self.backend_list:
211             for backend in self.backend_list:
212                 self.order.insert(0, ''.join([backend[0], '_resp_stats']))
213                 self.definitions.update({''.join([backend[0], '_resp_stats']): {
214                     'options': [None,
215                                 '%s response statistics' % backend[0].capitalize(),
216                                 "kilobit/s",
217                                 'Backend response',
218                                 'varnish.backend',
219                                 'area'],
220                     'lines': [[''.join([backend[0], '_beresp_hdrbytes']),
221                                'header', 'incremental', 8, 1000],
222                               [''.join([backend[0], '_beresp_bodybytes']),
223                                'body', 'incremental', -8, 1000]]}})
224
225
226 def find_percent(value1, value2, multiply):
227     # If value2 is 0 return 0
228     if not value2:
229         return 0
230     else:
231         return round(float(value1) / float(value2) * multiply)