@cryptotaxi247 / netdata-1 / commits / 5496a1e11

haproxy plugin: "idle percent" chart added; #2189

Ilya committed May 21, 2017 at 00:08 UTC 5496a1e1111c84fc1d455c3b8a74f491c5fae735
1 file changed +110 -86
python.d/haproxy.chart.py
+110 -86
@@ -3,6 +3,12 @@
3 # Author: l2isbad
4
5 from base import UrlService, SocketService
6 +from collections import defaultdict
7 +from re import compile as re_compile
8 +try:
9 + from urlparse import urlparse
10 +except ImportError:
11 + from urllib.parse import urlparse
12
13 # default module values (can be overridden per job in `config`)
14 # update_every = 2
@@ -10,7 +16,8 @@ priority = 60000
16 retries = 60
17
18 # charts order (can be overridden if you want less charts, or different order)
13 -ORDER = ['fbin', 'fbout', 'fscur', 'fqcur', 'bbin', 'bbout', 'bscur', 'bqcur', 'health_sdown', 'health_bdown']
19 +ORDER = ['fbin', 'fbout', 'fscur', 'fqcur', 'bbin', 'bbout', 'bscur', 'bqcur',
20 + 'health_sdown', 'health_bdown', 'health_idle']
21 CHARTS = {
22 'fbin': {
23 'options': [None, "Kilobytes In", "KB/s", 'frontend', 'haproxy_f.bin', 'line'],
@@ -50,105 +57,92 @@ CHARTS = {
57 'lines': [
58 ]},
59 'health_bdown': {
53 - 'options': [None, "Is backend alive? 1 = DOWN", "failed backend", 'health', 'haproxy_hb.down', 'line'],
60 + 'options': [None, "Is Backend Alive? 1 = DOWN", "failed backend", 'health', 'haproxy_hb.down', 'line'],
61 'lines': [
62 + ]},
63 + 'health_idle': {
64 + 'options': [None, "The Ratio Of Polling Time Vs Total Time", "percent", 'health', 'haproxy.idle', 'line'],
65 + 'lines': [
66 + ['idle', None, 'absolute']
67 ]}
68 }
69
70 +METRICS = ('bin', 'bout', 'scur', 'qcur')
71 +REGEX = dict(url=re_compile(r'idle = (?P<idle>[0-9]+)'), socket=re_compile(r'Idle_pct: (?P<idle>[0-9]+)'))
72 +
73
74 class Service(UrlService, SocketService):
75 def __init__(self, configuration=None, name=None):
76 if 'socket' in configuration:
77 SocketService.__init__(self, configuration=configuration, name=name)
63 - self.poll_method = SocketService
64 - self.request = 'show stat\n'
78 + self.poll = SocketService
79 + self.options_ = dict(regex=REGEX['socket'], stat='show stat\n', info='show info\n')
80 else:
81 UrlService.__init__(self, configuration=configuration, name=name)
67 - self.poll_method = UrlService
82 + self.poll = UrlService
83 + self.options_ = dict(regex=REGEX['url'], stat=self.url, info=url_remove_params(self.url))
84 self.order = ORDER
85 self.definitions = CHARTS
70 - self.order_front = [_ for _ in ORDER if _.startswith('f')]
71 - self.order_back = [_ for _ in ORDER if _.startswith('b')]
72 - self.charts = True
86
87 def check(self):
75 -
76 - if self.poll_method.check(self):
77 - self.info('Plugin was started successfully. We are using %s.' % self.poll_method.__name__)
88 + if self.poll.check(self):
89 + self.create_charts()
90 + self.info('We are using %s.' % self.poll.__name__)
91 return True
79 - else:
80 - return False
81 -
82 - def create_charts(self, front_ends, back_ends):
83 - for _ in enumerate(front_ends):
84 - idx = _[0]
85 - self.definitions['fbin']['lines'].append(['_'.join(['fbin', front_ends[idx]['# pxname']]),
86 - front_ends[idx]['# pxname'], 'incremental', 1, 1024])
87 - self.definitions['fbout']['lines'].append(['_'.join(['fbout', front_ends[idx]['# pxname']]),
88 - front_ends[idx]['# pxname'], 'incremental', 1, 1024])
89 - self.definitions['fscur']['lines'].append(['_'.join(['fscur', front_ends[idx]['# pxname']]),
90 - front_ends[idx]['# pxname'], 'absolute'])
91 - self.definitions['fqcur']['lines'].append(['_'.join(['fqcur', front_ends[idx]['# pxname']]),
92 - front_ends[idx]['# pxname'], 'absolute'])
93 -
94 - for _ in enumerate(back_ends):
95 - idx = _[0]
96 - self.definitions['bbin']['lines'].append(['_'.join(['bbin', back_ends[idx]['# pxname']]),
97 - back_ends[idx]['# pxname'], 'incremental', 1, 1024])
98 - self.definitions['bbout']['lines'].append(['_'.join(['bbout', back_ends[idx]['# pxname']]),
99 - back_ends[idx]['# pxname'], 'incremental', 1, 1024])
100 - self.definitions['bscur']['lines'].append(['_'.join(['bscur', back_ends[idx]['# pxname']]),
101 - back_ends[idx]['# pxname'], 'absolute'])
102 - self.definitions['bqcur']['lines'].append(['_'.join(['bqcur', back_ends[idx]['# pxname']]),
103 - back_ends[idx]['# pxname'], 'absolute'])
104 - self.definitions['health_sdown']['lines'].append(['_'.join(['hsdown', back_ends[idx]['# pxname']]),
105 - back_ends[idx]['# pxname'], 'absolute'])
106 - self.definitions['health_bdown']['lines'].append(['_'.join(['hbdown', back_ends[idx]['# pxname']]),
107 - back_ends[idx]['# pxname'], 'absolute'])
92 + return False
93
94 def _get_data(self):
95 + to_netdata = dict()
96 + self.request, self.url = self.options_['stat'], self.options_['stat']
97 + stat_data = self._get_stat_data()
98 + self.request, self.url = self.options_['info'], self.options_['info']
99 + info_data = self._get_info_data(regex=self.options_['regex'])
100 +
101 + to_netdata.update(stat_data)
102 + to_netdata.update(info_data)
103 + return to_netdata or None
104 +
105 + def _get_stat_data(self):
106 """
111 - Format data received from http request
107 :return: dict
108 """
114 - raw_data = self.poll_method._get_raw_data(self)
109 + raw_data = self.poll._get_raw_data(self)
110
111 if not raw_data:
117 - return None
118 - else:
119 - raw_data = raw_data.splitlines()
120 -
121 - all_instances = [dict(zip(raw_data[0].split(','),
122 - raw_data[_].split(','))) for _ in range(1, len(raw_data))]
123 -
124 - back_ends = list(filter(is_backend, all_instances))
125 - front_ends = list(filter(is_frontend, all_instances))
126 - servers = list(filter(is_server, all_instances))
127 -
128 - if self.charts:
129 - self.create_charts(front_ends, back_ends)
130 - self.charts = False
131 -
132 - to_netdata = dict()
133 -
134 - for frontend in front_ends:
135 - for idx in self.order_front:
136 - to_netdata.update({'_'.join([idx, frontend['# pxname']]):
137 - int(frontend[idx[1:]]) if frontend.get(idx[1:]) else 0})
138 -
139 - for backend in back_ends:
140 - for idx in self.order_back:
141 - to_netdata.update({'_'.join([idx, backend['# pxname']]):
142 - int(backend[idx[1:]]) if backend.get(idx[1:]) else 0})
143 -
144 - for _ in enumerate(back_ends):
145 - idx = _[0]
146 - to_netdata.update({'_'.join(['hsdown', back_ends[idx]['# pxname']]):
147 - len([server for server in servers if is_server_down(server, back_ends, idx)])})
148 - to_netdata.update({'_'.join(['hbdown', back_ends[idx]['# pxname']]):
149 - 1 if is_backend_down(back_ends, idx) else 0})
112 + return dict()
113 +
114 + raw_data = raw_data.splitlines()
115 + self.data = parse_data_([dict(zip(raw_data[0].split(','), raw_data[_].split(',')))
116 + for _ in range(1, len(raw_data))])
117 + if not self.data:
118 + return dict()
119 +
120 + stat_data = dict()
121 +
122 + for frontend in self.data['frontend']:
123 + for metric in METRICS:
124 + idx = frontend['# pxname'].replace('.', '_')
125 + stat_data['_'.join(['frontend', metric, idx])] = frontend.get(metric) or 0
126 +
127 + for backend in self.data['backend']:
128 + name, idx = backend['# pxname'], backend['# pxname'].replace('.', '_')
129 + stat_data['hsdown_' + idx] = len([server for server in self.data['servers']
130 + if server_down(server, name)])
131 + stat_data['hbdown_' + idx] = 1 if backend.get('status') == 'DOWN' else 0
132 + for metric in METRICS:
133 + stat_data['_'.join(['backend', metric, idx])] = backend.get(metric) or 0
134 + return stat_data
135 +
136 + def _get_info_data(self, regex):
137 + """
138 + :return: dict
139 + """
140 + raw_data = self.poll._get_raw_data(self)
141 + if not raw_data:
142 + return dict()
143
151 - return to_netdata
144 + match = regex.search(raw_data)
145 + return match.groupdict() if match else dict()
146
147 @staticmethod
148 def _check_raw_data(data):
@@ -159,22 +153,52 @@ class Service(UrlService, SocketService):
153 """
154 return not bool(data)
155
162 -
163 -def is_backend(backend):
156 + def create_charts(self):
157 + for front in self.data['frontend']:
158 + name, idx = front['# pxname'], front['# pxname'].replace('.', '_')
159 + for metric in METRICS:
160 + self.definitions['f' + metric]['lines'].append(['_'.join(['frontend', metric, idx]),
161 + name, 'incremental', 1, 1024])
162 + for back in self.data['backend']:
163 + name, idx = back['# pxname'], back['# pxname'].replace('.', '_')
164 + for metric in METRICS:
165 + self.definitions['b' + metric]['lines'].append(['_'.join(['backend', metric, idx]),
166 + name, 'incremental', 1, 1024])
167 + self.definitions['health_sdown']['lines'].append(['hsdown_' + idx, name, 'absolute'])
168 + self.definitions['health_bdown']['lines'].append(['hbdown_' + idx, name, 'absolute'])
169 +
170 +
171 +def parse_data_(data):
172 + def is_backend(backend):
173 return backend.get('svname') == 'BACKEND' and backend.get('# pxname') != 'stats'
174
166 -
167 -def is_frontend(frontend):
175 + def is_frontend(frontend):
176 return frontend.get('svname') == 'FRONTEND' and frontend.get('# pxname') != 'stats'
177
170 -
171 -def is_server(server):
178 + def is_server(server):
179 return not server.get('svname', '').startswith(('FRONTEND', 'BACKEND'))
180
181 + if not data:
182 + return None
183 +
184 + result = defaultdict(list)
185 + for elem in data:
186 + if is_backend(elem):
187 + result['backend'].append(elem)
188 + continue
189 + elif is_frontend(elem):
190 + result['frontend'].append(elem)
191 + continue
192 + elif is_server(elem):
193 + result['servers'].append(elem)
194 +
195 + return result or None
196 +
197
175 -def is_server_down(server, back_ends, idx):
176 - return server.get('# pxname') == back_ends[idx].get('# pxname') and server.get('status') == 'DOWN'
198 +def server_down(server, backend_name):
199 + return server.get('# pxname') == backend_name and server.get('status') == 'DOWN'
200
201
179 -def is_backend_down(back_ends, idx):
180 - return back_ends[idx].get('status') == 'DOWN'
202 +def url_remove_params(url):
203 + parsed = urlparse(url or str())
204 + return '%s://%s%s' % (parsed.scheme, parsed.netloc, parsed.path)