monit: obsolete service in runtime (#6340)
* obsolete service
Ilya Mashchenko committed
Jun 29, 2019 at 21:36 UTC
54149c562c6b11459e5c8cb42a96a01d22d87a64
1 file changed
+241
-75
collectors/python.d.plugin/monit/monit.chart.py
+241
-75
@@ -4,23 +4,49 @@
4
# SPDX-License-Identifier: GPL-3.0-or-later
5
6
import xml.etree.ElementTree as ET
7
+
8
+from collections import namedtuple
9
+
10
from bases.FrameworkServices.UrlService import UrlService
11
12
10
-# see enum State_Type from monit.h (https://bitbucket.org/tildeslash/monit/src/master/src/monit.h)
11
-MONIT_SERVICE_NAMES = [
12
- 'Filesystem',
13
- 'Directory',
14
- 'File',
15
- 'Process',
16
- 'Host',
17
- 'System',
18
- 'Fifo',
19
- 'Program',
20
- 'Net',
21
-]
13
+MonitType = namedtuple('MonitType', ('index', 'name'))
14
+
15
+# see enum Service_Type from monit.h (https://bitbucket.org/tildeslash/monit/src/master/src/monit.h)
16
+# typedef enum {
17
+# Service_Filesystem = 0,
18
+# Service_Directory,
19
+# Service_File,
20
+# Service_Process,
21
+# Service_Host,
22
+# Service_System,
23
+# Service_Fifo,
24
+# Service_Program,
25
+# Service_Net,
26
+# Service_Last = Service_Net
27
+# } __attribute__((__packed__)) Service_Type;
28
23
-DEFAULT_SERVICES_IDS = [0, 1, 2, 3, 4, 6, 7, 8]
29
+TYPE_FILESYSTEM = MonitType(0, 'filesystem')
30
+TYPE_DIRECTORY = MonitType(1, 'directory')
31
+TYPE_FILE = MonitType(2, 'file')
32
+TYPE_PROCESS = MonitType(3, 'process')
33
+TYPE_HOST = MonitType(4, 'host')
34
+TYPE_SYSTEM = MonitType(5, 'system')
35
+TYPE_FIFO = MonitType(6, 'fifo')
36
+TYPE_PROGRAM = MonitType(7, 'program')
37
+TYPE_NET = MonitType(8, 'net')
38
+
39
+TYPES = (
40
+ TYPE_FILESYSTEM,
41
+ TYPE_DIRECTORY,
42
+ TYPE_FILE,
43
+ TYPE_PROCESS,
44
+ TYPE_HOST,
45
+ TYPE_SYSTEM,
46
+ TYPE_FIFO,
47
+ TYPE_PROGRAM,
48
+ TYPE_NET,
49
+)
50
51
# charts order (can be overridden if you want less charts, or different order)
52
ORDER = [
@@ -38,6 +64,7 @@ ORDER = [
64
'program',
65
'net'
66
]
67
+
68
CHARTS = {
69
'filesystem': {
70
'options': ['filesystems', 'Filesystems', 'filesystems', 'filesystem', 'monit.filesystems', 'line'],
@@ -83,7 +110,7 @@ CHARTS = {
110
'lines': []
111
},
112
'host_latency': {
86
- 'options': ['hosts latency', 'Hosts latency', 'milliseconds/s', 'network', 'monit.host_latency', 'line'],
113
+ 'options': ['hosts latency', 'Hosts latency', 'milliseconds', 'network', 'monit.host_latency', 'line'],
114
'lines': []
115
},
116
'net': {
@@ -94,85 +121,224 @@ CHARTS = {
121
}
122
123
124
+class BaseMonitService(object):
125
+ def __init__(self, typ, name, status, monitor):
126
+ self.type = typ
127
+ self.name = name
128
+ self.status = status
129
+ self.monitor = monitor
130
+
131
+ def __repr__(self):
132
+ return 'MonitService({0}:{1})'.format(self.type.name, self.name)
133
+
134
+ def __eq__(self, other):
135
+ if not isinstance(other, BaseMonitService):
136
+ return False
137
+ return self.type == other.type and self.name == other.name
138
+
139
+ def __ne__(self, other):
140
+ return not self == other
141
+
142
+ def __hash__(self):
143
+ return hash(repr(self))
144
+
145
+ def is_running(self):
146
+ return self.status == '0' and self.monitor == '1'
147
+
148
+ def key(self):
149
+ return '{0}_{1}'.format(self.type.name, self.name)
150
+
151
+ def data(self):
152
+ return {self.key(): int(self.is_running())}
153
+
154
+
155
+class ProcessMonitService(BaseMonitService):
156
+ def __init__(self, typ, name, status, monitor):
157
+ super(ProcessMonitService, self).__init__(typ, name, status, monitor)
158
+ self.uptime = None
159
+ self.threads = None
160
+ self.children = None
161
+
162
+ def uptime_key(self):
163
+ return 'process_uptime_{0}'.format(self.name)
164
+
165
+ def threads_key(self):
166
+ return 'process_threads_{0}'.format(self.name)
167
+
168
+ def children_key(self):
169
+ return 'process_children_{0}'.format(self.name)
170
+
171
+ def data(self):
172
+ base_data = super(ProcessMonitService, self).data()
173
+ # skipping bugged metrics with negative uptime (monit before v5.16)
174
+ uptime = self.uptime if self.uptime and int(self.uptime) >= 0 else None
175
+ data = {
176
+ self.uptime_key(): uptime,
177
+ self.threads_key(): self.threads,
178
+ self.children_key(): self.children,
179
+ }
180
+ data.update(base_data)
181
+
182
+ return data
183
+
184
+
185
+class HostMonitService(BaseMonitService):
186
+ def __init__(self, typ, name, status, monitor):
187
+ super(HostMonitService, self).__init__(typ, name, status, monitor)
188
+ self.latency = None
189
+
190
+ def latency_key(self):
191
+ return 'host_latency_{0}'.format(self.name)
192
+
193
+ def data(self):
194
+ base_data = super(HostMonitService, self).data()
195
+ latency = float(self.latency) * 1000000 if self.latency else None
196
+ data = {self.latency_key(): latency}
197
+ data.update(base_data)
198
+
199
+ return data
200
+
201
+
202
class Service(UrlService):
203
def __init__(self, configuration=None, name=None):
204
UrlService.__init__(self, configuration=configuration, name=name)
205
self.order = ORDER
206
self.definitions = CHARTS
102
- base_url = self.configuration.get('url', 'http://localhost:2812')
207
+ base_url = self.configuration.get('url', "http://localhost:2812")
208
self.url = '{0}/_status?format=xml&level=full'.format(base_url)
209
+ self.active_services = list()
210
105
- def parse(self, data):
211
+ def parse(self, raw):
212
try:
107
- xml = ET.fromstring(data)
213
+ root = ET.fromstring(raw)
214
except ET.ParseError:
109
- self.error("URL {0} didn't return a vaild XML page. Please check your settings.".format(self.url))
215
+ self.error("URL {0} didn't return a valid XML page. Please check your settings.".format(self.url))
216
+ return None
217
+ return root
218
+
219
+ def _get_data(self):
220
+ raw = self._get_raw_data()
221
+ if not raw:
222
return None
111
- return xml
223
113
- def check(self):
114
- self._manager = self._build_manager()
224
+ root = self.parse(raw)
225
+ if root is None:
226
+ return None
227
116
- raw_data = self._get_raw_data()
117
- if not raw_data:
228
+ services = self.get_services(root)
229
+ if not services:
230
return None
231
120
- return bool(self.parse(raw_data))
232
+ if len(self.charts) > 0:
233
+ self.update_charts(services)
234
122
- def _get_data(self):
123
- raw_data = self._get_raw_data()
235
+ data = dict()
236
125
- if not raw_data:
126
- return None
237
+ for svc in services:
238
+ data.update(svc.data())
239
128
- xml = self.parse(raw_data)
129
- if not xml:
130
- return None
240
+ return data
241
132
- data = {}
133
- for service_id in DEFAULT_SERVICES_IDS:
134
- service_category = MONIT_SERVICE_NAMES[service_id].lower()
242
+ def get_services(self, root):
243
+ services = list()
244
136
- if service_category == 'system':
137
- self.debug("Skipping service from 'System' category, because it's useless in graphs")
245
+ for typ in TYPES:
246
+ if typ == TYPE_SYSTEM:
247
+ self.debug("skipping service from '{0}' category, it's useless in graphs".format(TYPE_SYSTEM.name))
248
continue
249
140
- xpath_query = "./service[@type='{0}']".format(service_id)
141
- self.debug('Searching for {0} as {1}'.format(service_category, xpath_query))
142
- for service_node in xml.findall(xpath_query):
143
-
144
- service_name = service_node.find('name').text
145
- service_status = service_node.find('status').text
146
- service_monitoring = service_node.find('monitor').text
147
- self.debug('=> found {0} with type={1}, status={2}, monitoring={3}'.format(service_name,
148
- service_id, service_status, service_monitoring))
149
-
150
- dimension_key = service_category + '_' + service_name
151
- if dimension_key not in self.charts[service_category]:
152
- self.charts[service_category].add_dimension([dimension_key, service_name, 'absolute'])
153
- data[dimension_key] = 1 if service_status == '0' and service_monitoring == '1' else 0
154
-
155
- if service_category == 'process':
156
- for subnode in ('uptime', 'threads', 'children'):
157
- subnode_value = service_node.find(subnode)
158
- if subnode_value is None:
159
- continue
160
- if subnode == 'uptime' and int(subnode_value.text) < 0:
161
- self.debug('Skipping bugged metrics with negative uptime (monit before v5.16')
162
- continue
163
- dimension_key = 'process_{0}_{1}'.format(subnode, service_name)
164
- if dimension_key not in self.charts['process_' + subnode]:
165
- self.charts['process_' + subnode].add_dimension([dimension_key, service_name, 'absolute'])
166
- data[dimension_key] = int(subnode_value.text)
167
-
168
- if service_category == 'host':
169
- subnode_value = service_node.find('./icmp/responsetime')
170
- if subnode_value is None:
171
- continue
172
- dimension_key = 'host_latency_{0}'.format(service_name)
173
- if dimension_key not in self.charts['host_latency']:
174
- self.charts['host_latency'].add_dimension([dimension_key, service_name,
175
- 'absolute', 1000, 1000000])
176
- data[dimension_key] = float(subnode_value.text) * 1000000
177
-
178
- return data or None
250
+ xpath_query = "./service[@type='{0}']".format(typ.index)
251
+ self.debug('Searching for {0} as {1}'.format(typ.name, xpath_query))
252
+
253
+ for svc_root in root.findall(xpath_query):
254
+ svc = create_service(svc_root, typ)
255
+ self.debug('=> found {0} with type={1}, status={2}, monitoring={3}'.format(
256
+ svc.name, svc.type.name, svc.status, svc.monitor))
257
+
258
+ services.append(svc)
259
+
260
+ return services
261
+
262
+ def update_charts(self, services):
263
+ remove = [svc for svc in self.active_services if svc not in services]
264
+ add = [svc for svc in services if svc not in self.active_services]
265
+
266
+ self.remove_services_from_charts(remove)
267
+ self.add_services_to_charts(add)
268
+
269
+ self.active_services = services
270
+
271
+ def add_services_to_charts(self, services):
272
+ for svc in services:
273
+ if svc.type == TYPE_HOST:
274
+ self.charts['host_latency'].add_dimension([svc.latency_key(), svc.name, 'absolute', 1000, 1000000])
275
+ if svc.type == TYPE_PROCESS:
276
+ self.charts['process_uptime'].add_dimension([svc.uptime_key(), svc.name])
277
+ self.charts['process_threads'].add_dimension([svc.threads_key(), svc.name])
278
+ self.charts['process_children'].add_dimension([svc.children_key(), svc.name])
279
+ self.charts[svc.type.name].add_dimension([svc.key(), svc.name])
280
+
281
+ def remove_services_from_charts(self, services):
282
+ for svc in services:
283
+ if svc.type == TYPE_HOST:
284
+ self.charts['host_latency'].del_dimension(svc.latency_key(), False)
285
+ if svc.type == TYPE_PROCESS:
286
+ self.charts['process_uptime'].del_dimension(svc.uptime_key(), False)
287
+ self.charts['process_threads'].del_dimension(svc.threads_key(), False)
288
+ self.charts['process_children'].del_dimension(svc.children_key(), False)
289
+ self.charts[svc.type.name].del_dimension(svc.key(), False)
290
+
291
+
292
+def create_service(root, typ):
293
+ if typ == TYPE_HOST:
294
+ return create_host_service(root)
295
+ elif typ == TYPE_PROCESS:
296
+ return create_process_service(root)
297
+ return create_base_service(root, typ)
298
+
299
+
300
+def create_host_service(root):
301
+ svc = HostMonitService(
302
+ TYPE_HOST,
303
+ root.find('name').text,
304
+ root.find('status').text,
305
+ root.find('monitor').text,
306
+ )
307
+
308
+ latency = root.find('./icmp/responsetime')
309
+ if latency is not None:
310
+ svc.latency = latency.text
311
+
312
+ return svc
313
+
314
+
315
+def create_process_service(root):
316
+ svc = ProcessMonitService(
317
+ TYPE_PROCESS,
318
+ root.find('name').text,
319
+ root.find('status').text,
320
+ root.find('monitor').text,
321
+ )
322
+
323
+ uptime = root.find('uptime')
324
+ if uptime is not None:
325
+ svc.uptime = uptime.text
326
+
327
+ threads = root.find('threads')
328
+ if threads is not None:
329
+ svc.threads = threads.text
330
+
331
+ children = root.find('children')
332
+ if children is not None:
333
+ svc.children = children.text
334
+
335
+ return svc
336
+
337
+
338
+def create_base_service(root, typ):
339
+ return BaseMonitService(
340
+ typ,
341
+ root.find('name').text,
342
+ root.find('status').text,
343
+ root.find('monitor').text,
344
+ )