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)