From f183bc6f05a7260f2f4525ff3e75b9e955f2fb03 Mon Sep 17 00:00:00 2001 From: delong1 Date: Wed, 29 Jun 2016 13:35:36 +0800 Subject: [PATCH] added: method of comparison for graphite --- graphite_beacon/alerts.py | 44 +++++++++++++++++++++++++++++++++++++++ graphite_beacon/core.py | 1 + graphite_beacon/utils.py | 7 +++++-- setup.py | 0 4 files changed, 50 insertions(+), 2 deletions(-) mode change 100644 => 100755 setup.py diff --git a/graphite_beacon/alerts.py b/graphite_beacon/alerts.py index 596e591..9233bff 100644 --- a/graphite_beacon/alerts.py +++ b/graphite_beacon/alerts.py @@ -5,6 +5,7 @@ from . import _compat as _ from .graphite import GraphiteRecord from .utils import ( + COMPARISON, HISTORICAL, LOGICAL_OPERATORS, convert_to_format, @@ -13,6 +14,7 @@ parse_rule, ) import math +import time from collections import deque, defaultdict from itertools import islice @@ -80,6 +82,7 @@ def __init__(self, reactor, **options): self.waiting = False self.state = {None: "normal", "waiting": "normal", "loading": "normal"} self.history = defaultdict(lambda: sliceable_deque([], self.history_size)) + self.comparing = {} LOGGER.info("Alert '%s': has inited", self) @@ -205,6 +208,18 @@ def get_value_for_expr(self, expr, target): return None rvalue = sum(history) / float(len(history)) + if rvalue == COMPARISON: + current_time = int(time.time()) + if self.comparing.has_key(target) and self.comparing[target]['time'] == current_time: + rvalue = self.comparing[target]['value'] + else: + rvalue = self.get_graph_comparison() + if rvalue != -1: + if not self.comparing.has_key(target): + self.comparing[target] = {} + self.comparing[target]['value'] = rvalue + self.comparing[target]['time'] = current_time + rvalue = expr['mod'](rvalue) return rvalue @@ -226,6 +241,10 @@ def load(self): """Load from remote.""" raise NotImplementedError() + def get_graph_comparison(self): + """Only from graphite.""" + raise NotImplementedError() + class GraphiteAlert(BaseAlert): @@ -241,6 +260,7 @@ def configure(self, **options): self.default_nan_value = options.get( 'default_nan_value', self.reactor.options['default_nan_value']) self.ignore_nan = options.get('ignore_nan', self.reactor.options['ignore_nan']) + self.compare_size = options.get('compare_size', self.reactor.options['compare_size']) assert self.method in METHODS, "Method is invalid" self.auth_username = self.reactor.options.get('auth_username') @@ -274,6 +294,7 @@ def load(self): self.check(data) self.notify('normal', 'Metrics are loaded', target='loading', ntype='common') except Exception as e: + LOGGER.debug("ee: %s", str(e)) self.notify( self.loading_error, 'Loading error: %s' % e, target='loading', ntype='common') self.waiting = False @@ -293,6 +314,29 @@ def _graphite_url(self, query, raw_data=False, graphite_url=None): url = "{0}&rawData=true".format(url) return url + def get_graph_comparison(self): + time_shift = self.compare_size + if time_shift[0] != '-': + time_shift = '-' + time_shift + + query = 'timeShift(' + self.query + ', "' + time_shift + '")' + url = self._graphite_url( + query, graphite_url=self.reactor.options.get('graphite_url'), raw_data=True) + + http_client = hc.HTTPClient() + try: + response = http_client.fetch(url, auth_username=self.auth_username, + auth_password=self.auth_password, + request_timeout=self.request_timeout, + connect_timeout=self.connect_timeout) + record = GraphiteRecord(response.body.decode('utf-8'), self.default_nan_value, self.ignore_nan) + value = getattr(record, self.method) + LOGGER.debug("%s [%s]: %s", self.name, record.target, value) + return value + except Exception as e: + LOGGER.error('No data to compare: %s', str(e)) + return -1 + class URLAlert(BaseAlert): diff --git a/graphite_beacon/core.py b/graphite_beacon/core.py index 64a0730..a374fee 100644 --- a/graphite_beacon/core.py +++ b/graphite_beacon/core.py @@ -32,6 +32,7 @@ class Reactor(object): 'debug': False, 'format': 'short', 'graphite_url': 'http://localhost', + 'compare_size': '7d', 'history_size': '1day', 'interval': '10minute', 'logging': 'info', diff --git a/graphite_beacon/utils.py b/graphite_beacon/utils.py index cbfcb28..b756bd5 100644 --- a/graphite_beacon/utils.py +++ b/graphite_beacon/utils.py @@ -43,6 +43,7 @@ IDENTITY = lambda x: x +COMPARISON = 'comparison' HISTORICAL = 'historical' COMPARATORS = {'>': op.gt, '>=': op.ge, '<': op.lt, '<=': op.le, '==': op.eq, '!=': op.ne} OPERATORS = {'*': op.mul, '/': op.truediv, '+': op.add, '-': op.sub} @@ -51,6 +52,7 @@ RULE_TOKENIZER = make_tokenizer( [ (u'Level', (r'(critical|warning|normal)',)), + (u'Comparison', (COMPARISON,)), (u'Historical', (HISTORICAL,)), (u'Comparator', (r'({0})'.format('|'.join(sorted(COMPARATORS.keys(), reverse=True))),)), (u'LogicalOperator', (r'({0})'.format('|'.join(LOGICAL_OPERATORS.keys())),)), @@ -113,12 +115,13 @@ def _parse_rule(seq): level = toktype(u'Level') comparator = toktype(u'Comparator') >> COMPARATORS.get number = toktype(u'Number') >> float + comparison = toktype(u'Comparison') historical = toktype(u'Historical') unit = toktype(u'Unit') operator = toktype(u'Operator') logical_operator = toktype(u'LogicalOperator') >> LOGICAL_OPERATORS.get - exp = comparator + ((number + maybe(unit)) | historical) + maybe(operator + number) + exp = comparator + ((number + maybe(unit)) | comparison | historical) + maybe(operator + number) rule = ( level + s_sep(':') + exp + many(logical_operator + exp) ) @@ -130,7 +133,7 @@ def _parse_rule(seq): def _parse_expr(expr): cond, value, mod = expr - if value != HISTORICAL: + if value != HISTORICAL and value != COMPARISON: value = convert_from_format(*value) if mod: diff --git a/setup.py b/setup.py old mode 100644 new mode 100755