From 1f16c242ccc1d8949ef612782a1830d8e3f63dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20MOHIER?= Date: Sat, 21 Jan 2017 13:16:01 +0100 Subject: [PATCH] Fixes #256: % formatted performance data Manage min and max correctly Sanitize metrics names for Graphite/Grafana Set version as 0.8.5.1 --- alignak_backend/__init__.py | 2 +- alignak_backend/grafana.py | 2 + alignak_backend/perfdata.py | 31 +++--- alignak_backend/timeseries.py | 30 +++++- test/test_timeseries.py | 185 +++++++++++++++++++++++++++++++++- 5 files changed, 234 insertions(+), 16 deletions(-) diff --git a/alignak_backend/__init__.py b/alignak_backend/__init__.py index ed98bf09..4fe08b43 100755 --- a/alignak_backend/__init__.py +++ b/alignak_backend/__init__.py @@ -8,7 +8,7 @@ This module is an Alignak REST backend """ # Application version and manifest -VERSION = (0, 8, 4) +VERSION = (0, 8, 5, 1) __application__ = u"Alignak_Backend" __short_version__ = '.'.join((str(each) for each in VERSION[:2])) diff --git a/alignak_backend/grafana.py b/alignak_backend/grafana.py index 6749f355..927db484 100644 --- a/alignak_backend/grafana.py +++ b/alignak_backend/grafana.py @@ -152,6 +152,8 @@ def build_target(self, item, fields): my_target = my_target.replace("/", "-") # space becomes a _ my_target = my_target.replace(" ", "_") + # % becomes _pct + my_target = my_target.replace("%", "_pct") # all character not in [a-zA-Z_-0-9.] is removed my_target = re.sub(r'[^a-zA-Z_\-0-9\.\$]', '', my_target) diff --git a/alignak_backend/perfdata.py b/alignak_backend/perfdata.py index 44052899..839ea615 100644 --- a/alignak_backend/perfdata.py +++ b/alignak_backend/perfdata.py @@ -63,7 +63,6 @@ def __init__(self, string): self.name = self.value = self.uom = \ self.warning = self.critical = self.min = self.max = None string = string.strip() - # print "Analysis string", string matches = METRIC_PATTERN.match(string) if matches: # Get the name but remove all ' in it @@ -74,20 +73,28 @@ def __init__(self, string): self.critical = guess_int_or_float(matches.group(5)) self.min = guess_int_or_float(matches.group(6)) self.max = guess_int_or_float(matches.group(7)) - # print 'Name', self.name - # print "Value", self.value - # print "Res", r - # print r.groups() if self.uom == '%': self.min = 0 self.max = 100 - def __str__(self): + def __str__(self): # pragma: no cover, only for debugging purpose string = "%s=%s%s" % (self.name, self.value, self.uom) - if self.warning: + if self.warning is not None: string += ";%s" % (self.warning) - if self.critical: + else: + string += ";" + if self.critical is not None: string += ";%s" % (self.critical) + else: + string += ";" + if self.min is not None: + string += ";%s" % (self.min) + else: + string += ";" + if self.max is not None: + string += ";%s" % (self.max) + else: + string += ";" return string @@ -106,14 +113,14 @@ def __init__(self, string): if metric.name is not None: self.metrics[metric.name] = metric - def __iter__(self): + def __iter__(self): # pragma: no cover, not used internally return self.metrics.itervalues() - def __len__(self): + def __len__(self): # pragma: no cover, not used internally return len(self.metrics) - def __getitem__(self, key): + def __getitem__(self, key): # pragma: no cover, not used internally return self.metrics[key] - def __contains__(self, key): + def __contains__(self, key): # pragma: no cover, not used internally return key in self.metrics diff --git a/alignak_backend/timeseries.py b/alignak_backend/timeseries.py index 04f0ca3e..dc4f8a0e 100644 --- a/alignak_backend/timeseries.py +++ b/alignak_backend/timeseries.py @@ -77,7 +77,19 @@ def prepare_data(item): m = re.search(r'^(.*)\.[\d]{10}$', fields['name']) if m: fields['name'] = m.group(1) - fields['name'] = fields['name'].replace(" ", "_") + + # Sanitize field name for Graphite: + # + becomes a _ + my_target = fields['name'].replace("+", "_") + # / becomes a - + my_target = my_target.replace("/", "-") + # space becomes a _ + my_target = my_target.replace(" ", "_") + # % becomes _pct + my_target = my_target.replace("%", "_pct") + # all character not in [a-zA-Z_-0-9.] is removed + my_target = re.sub(r'[^a-zA-Z_\-0-9\.\$]', '', my_target) + fields['name'] = my_target if fields['value'] is not None: data_timeseries['data'].append( @@ -103,6 +115,22 @@ def prepare_data(item): 'uom': fields['uom'] } ) + if fields['min'] is not None: + data_timeseries['data'].append( + { + 'name': fields['name'] + '_min', + 'value': fields['min'], + 'uom': fields['uom'] + } + ) + if fields['max'] is not None: + data_timeseries['data'].append( + { + 'name': fields['name'] + '_max', + 'value': fields['max'], + 'uom': fields['uom'] + } + ) return data_timeseries @staticmethod diff --git a/test/test_timeseries.py b/test/test_timeseries.py index 4ef6fb0a..670847ee 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -213,6 +213,11 @@ def test_prepare_data(self): 'name': 'rta_critical', 'value': 15, 'uom': 'ms' + }, + { + 'name': 'rta_min', + 'value': 0, + 'uom': 'ms' } ] } @@ -259,6 +264,113 @@ def test_prepare_data_special(self): } self.assertItemsEqual(reference['data'], ret['data']) + def test_prepare_special_formatted(self): + """ + Prepare timeseries from a special perfdata, with + * : in name + * % in name + + :return: None + """ + item = { + 'host': 'srv001', + 'service': 'nsca_cpu', + 'state': 'OK', + 'state_type': 'HARD', + 'state_id': 0, + 'acknowledged': False, + 'last_check': int(time.time()), + 'last_state': 'OK', + 'output': 'OK All 1 drive(s) are ok', + 'long_output': '', + 'perf_data': "'C:'=13.19606GB;29.99853;33.99833;0;39.99804 " + "'C:%'=33%;75;85;0;100 " + "'C:_pct'=33%;75;85;0;100", + '_realm': 'All.Propieres' + } + + ret = Timeseries.prepare_data(item) + reference = { + 'data': [ + { + 'name': 'C', + 'value': 13.19606, + 'uom': 'GB' + }, + { + 'name': 'C_warning', + 'value': 29.99853, + 'uom': 'GB' + }, + { + 'name': 'C_critical', + 'value': 33.99833, + 'uom': 'GB' + }, + { + 'name': 'C_min', + 'value': 0, + 'uom': 'GB' + }, + { + 'name': 'C_max', + 'value': 39.99804, + 'uom': 'GB' + }, + { + 'name': 'C_pct', + 'value': 33, + 'uom': '%' + }, + { + 'name': 'C_pct_warning', + 'value': 75, + 'uom': '%' + }, + { + 'name': 'C_pct_critical', + 'value': 85, + 'uom': '%' + }, + { + 'name': 'C_pct_min', + 'value': 0, + 'uom': '%' + }, + { + 'name': 'C_pct_max', + 'value': 100, + 'uom': '%' + }, + { + 'name': 'C_pct', + 'value': 33, + 'uom': '%' + }, + { + 'name': 'C_pct_warning', + 'value': 75, + 'uom': '%' + }, + { + 'name': 'C_pct_critical', + 'value': 85, + 'uom': '%' + }, + { + 'name': 'C_pct_min', + 'value': 0, + 'uom': '%' + }, + { + 'name': 'C_pct_max', + 'value': 100, + 'uom': '%' + }, + ] + } + self.assertItemsEqual(reference['data'], ret['data']) + def test_generate_realm_prefix(self): """ Test generate realm prefix when have many levels @@ -460,6 +572,9 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All', 'name': u'rta_critical', 'service': u'', 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv001', 'value': 110}, + {'realm': u'All', 'name': u'rta_min', 'service': u'', + 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv001', + 'value': 0}, {'realm': u'All', 'name': u'pl', 'service': u'', 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv001', @@ -467,6 +582,12 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All', 'name': u'pl_warning', 'service': u'', 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv001', 'value': 10}, + {'realm': u'All', 'name': u'pl_min', 'service': u'', + 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv001', + 'value': 0}, + {'realm': u'All', 'name': u'pl_max', 'service': u'', + 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv001', + 'value': 100}, {'realm': u'All', 'name': u'rta', 'service': u'', 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv001', @@ -477,6 +598,9 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All', 'name': u'rta_critical', 'service': u'', 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv001', 'value': 110}, + {'realm': u'All', 'name': u'rta_min', 'service': u'', + 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv001', + 'value': 0}, {'realm': u'All', 'name': u'pl', 'service': u'', 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv001', @@ -484,6 +608,12 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All', 'name': u'pl_warning', 'service': u'', 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv001', 'value': 10}, + {'realm': u'All', 'name': u'pl_min', 'service': u'', + 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv001', + 'value': 0}, + {'realm': u'All', 'name': u'pl_max', 'service': u'', + 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv001', + 'value': 100}, {'realm': u'All', 'name': u'rta', 'service': u'', 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv001', 'value': 75}, @@ -491,11 +621,17 @@ def test_timeseries_realm_all_sub(self): 'influxdb': ObjectId(influxdb_001), 'host': u'srv001', 'value': 100}, {'realm': u'All', 'name': u'rta_critical', 'service': u'', 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv001', 'value': 110}, + {'realm': u'All', 'name': u'rta_min', 'service': u'', 'graphite': None, + 'influxdb': ObjectId(influxdb_001), 'host': u'srv001', 'value': 0}, {'realm': u'All', 'name': u'pl', 'service': u'', 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv001', 'value': 0}, {'realm': u'All', 'name': u'pl_warning', 'service': u'', 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv001', 'value': 10}, + {'realm': u'All', 'name': u'pl_min', 'service': u'', 'graphite': None, + 'influxdb': ObjectId(influxdb_001), 'host': u'srv001', 'value': 0}, + {'realm': u'All', 'name': u'pl_max', 'service': u'', 'graphite': None, + 'influxdb': ObjectId(influxdb_001), 'host': u'srv001', 'value': 100}, ] self.assertItemsEqual(ref, retention_data) timeseriesretention_db.drop() @@ -551,6 +687,9 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All.All A.All A1', 'name': u'rta_critical', 'service': u'', 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv003', 'value': 110}, + {'realm': u'All.All A.All A1', 'name': u'rta_min', 'service': u'', + 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv003', + 'value': 0}, {'realm': u'All.All A.All A1', 'name': u'pl', 'service': u'', 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv003', @@ -558,6 +697,12 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All.All A.All A1', 'name': u'pl_warning', 'service': u'', 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv003', 'value': 10}, + {'realm': u'All.All A.All A1', 'name': u'pl_min', 'service': u'', + 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv003', + 'value': 0}, + {'realm': u'All.All A.All A1', 'name': u'pl_max', 'service': u'', + 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv003', + 'value': 100}, {'realm': u'All.All A.All A1', 'name': u'rta', 'service': u'', 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv003', @@ -568,6 +713,9 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All.All A.All A1', 'name': u'rta_critical', 'service': u'', 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv003', 'value': 110}, + {'realm': u'All.All A.All A1', 'name': u'rta_min', 'service': u'', + 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv003', + 'value': 0}, {'realm': u'All.All A.All A1', 'name': u'pl', 'service': u'', 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv003', @@ -575,6 +723,12 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All.All A.All A1', 'name': u'pl_warning', 'service': u'', 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv003', 'value': 10}, + {'realm': u'All.All A.All A1', 'name': u'pl_min', 'service': u'', + 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv003', + 'value': 0}, + {'realm': u'All.All A.All A1', 'name': u'pl_max', 'service': u'', + 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv003', + 'value': 100}, {'realm': u'All.All A.All A1', 'name': u'rta', 'service': u'', 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv003', 'value': 32}, @@ -584,12 +738,21 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All.All A.All A1', 'name': u'rta_critical', 'service': u'', 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv003', 'value': 110}, + {'realm': u'All.All A.All A1', 'name': u'rta_min', 'service': u'', + 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv003', + 'value': 0}, {'realm': u'All.All A.All A1', 'name': u'pl', 'service': u'', 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv003', 'value': 0}, {'realm': u'All.All A.All A1', 'name': u'pl_warning', 'service': u'', 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv003', 'value': 10}, + {'realm': u'All.All A.All A1', 'name': u'pl_min', 'service': u'', + 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv003', + 'value': 0}, + {'realm': u'All.All A.All A1', 'name': u'pl_max', 'service': u'', + 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv003', + 'value': 100}, ] self.assertItemsEqual(ref, retention_data) timeseriesretention_db.drop() @@ -625,7 +788,7 @@ def test_timeseries_realm_all_sub(self): 'last_state': 'OK', 'output': 'PING OK - Packet loss = 0%, RTA = 0.08 ms', 'long_output': '', - 'perf_data': "rta=32.02453ms;100.000000;110.000000;0.000000 pl=0%;10;;0", + 'perf_data': "rta=32.02453ms;100.000000;110.000000 pl=0%;10;;", '_realm': ObjectId(self.realm_all_A1) } with app.test_request_context(): @@ -668,6 +831,12 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All.All A.All A1', 'name': u'pl_warning', 'service': u'', 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv003', 'value': 10}, + {'realm': u'All.All A.All A1', 'name': u'pl_min', 'service': u'', + 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv003', + 'value': 0}, + {'realm': u'All.All A.All A1', 'name': u'pl_max', 'service': u'', + 'graphite': ObjectId(graphite_001), 'influxdb': None, 'host': u'srv003', + 'value': 100}, {'realm': u'All.All A.All A1', 'name': u'rta', 'service': u'', 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv003', @@ -685,6 +854,12 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All.All A.All A1', 'name': u'pl_warning', 'service': u'', 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv003', 'value': 10}, + {'realm': u'All.All A.All A1', 'name': u'pl_min', 'service': u'', + 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv003', + 'value': 0}, + {'realm': u'All.All A.All A1', 'name': u'pl_max', 'service': u'', + 'graphite': ObjectId(graphite_002), 'influxdb': None, 'host': u'srv003', + 'value': 100}, {'realm': u'All.All A.All A1', 'name': u'rta', 'service': u'', 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv003', 'value': 32}, @@ -700,6 +875,12 @@ def test_timeseries_realm_all_sub(self): {'realm': u'All.All A.All A1', 'name': u'pl_warning', 'service': u'', 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv003', 'value': 10}, + {'realm': u'All.All A.All A1', 'name': u'pl_min', 'service': u'', + 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv003', + 'value': 0}, + {'realm': u'All.All A.All A1', 'name': u'pl_max', 'service': u'', + 'graphite': None, 'influxdb': ObjectId(influxdb_001), 'host': u'srv003', + 'value': 100}, ] self.assertItemsEqual(ref, retention_data) timeseriesretention_db.drop() @@ -719,7 +900,7 @@ def test_timeseries_realm_all_sub(self): 'last_state': 'OK', 'output': 'PING OK - Packet loss = 0%, RTA = 0.08 ms', 'long_output': '', - 'perf_data': "rta=32.02453ms;100.000000;110.000000;0.000000 pl=0%;10;;0", + 'perf_data': "rta=32.02453ms;100.000000;110.000000 pl=0;10", '_realm': ObjectId(self.realm_all_A) } with app.test_request_context():