2014-10-29 16:14:39 -06:00
|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2014 Hewlett-Packard
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
|
|
# not use this file except in compliance with the License. You may obtain
|
|
|
|
# a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
# License for the specific language governing permissions and limitations
|
|
|
|
# under the License.
|
|
|
|
import itertools
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from pyparsing import CaselessLiteral
|
|
|
|
from pyparsing import alphanums
|
|
|
|
from pyparsing import delimitedList
|
|
|
|
from pyparsing import Forward
|
|
|
|
from pyparsing import Group
|
|
|
|
from pyparsing import Literal
|
|
|
|
from pyparsing import nums
|
|
|
|
from pyparsing import opAssoc
|
|
|
|
from pyparsing import operatorPrecedence
|
|
|
|
from pyparsing import Optional
|
|
|
|
from pyparsing import stringEnd
|
|
|
|
from pyparsing import Word
|
|
|
|
|
|
|
|
|
|
|
|
class SubExpr(object):
|
|
|
|
def __init__(self, tokens):
|
2014-11-03 08:41:44 -07:00
|
|
|
self._sub_expr = tokens
|
|
|
|
self._func = tokens.func
|
|
|
|
self._metric_name = tokens.metric_name
|
|
|
|
self._dimensions = tokens.dimensions.dimensions_list
|
|
|
|
self._operator = tokens.relational_op
|
|
|
|
self._threshold = tokens.threshold
|
|
|
|
self._period = tokens.period
|
|
|
|
self._periods = tokens.periods
|
|
|
|
self._id = None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def sub_expr_str(self):
|
|
|
|
"""Get the entire sub expression as a string with no spaces."""
|
|
|
|
return "".join(list(itertools.chain(*self._sub_expr)))
|
|
|
|
|
|
|
|
@property
|
|
|
|
def fmtd_sub_expr_str(self):
|
|
|
|
"""Get the entire sub expressions as a string with spaces."""
|
|
|
|
result = "{}({}".format(self._func.encode('utf8'),
|
|
|
|
self._metric_name.encode('utf8'))
|
|
|
|
|
|
|
|
if self._dimensions:
|
|
|
|
result += "{{{}}}".format(self._dimensions.encode('utf8'))
|
|
|
|
|
|
|
|
if self._period:
|
|
|
|
result += ", {}".format(self._period.encode('utf8'))
|
2014-10-29 16:14:39 -06:00
|
|
|
|
|
|
|
result += ")"
|
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
result += " {} {}".format(self._operator.encode('utf8'),
|
|
|
|
self._threshold.encode('utf8'))
|
2014-10-29 16:14:39 -06:00
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
if self._periods:
|
|
|
|
result += " times {}".format(self._periods.encode('utf8'))
|
2014-10-29 16:14:39 -06:00
|
|
|
|
|
|
|
return result.decode('utf8')
|
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
@property
|
|
|
|
def dimensions_str(self):
|
|
|
|
"""Get all the dimensions as a single comma delimited string."""
|
|
|
|
return self._dimensions
|
2014-10-29 16:14:39 -06:00
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
@property
|
|
|
|
def operands_list(self):
|
|
|
|
"""Get this sub expression as a list."""
|
2014-10-29 16:14:39 -06:00
|
|
|
return [self]
|
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
@property
|
|
|
|
def func(self):
|
|
|
|
"""Get the function as it appears in the orig expression."""
|
|
|
|
return self._func
|
|
|
|
|
|
|
|
@property
|
|
|
|
def normalized_func(self):
|
|
|
|
"""Get the function upper-cased."""
|
|
|
|
return self._func.upper()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def metric_name(self):
|
|
|
|
"""Get the metric name as it appears in the orig expression."""
|
|
|
|
return self._metric_name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def normalized_metric_name(self):
|
|
|
|
"""Get the metric name lower-cased."""
|
|
|
|
return self._metric_name.lower()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def dimensions(self):
|
|
|
|
"""Get the dimensions."""
|
|
|
|
return self._dimensions
|
|
|
|
|
|
|
|
@property
|
|
|
|
def dimensions_as_list(self):
|
|
|
|
"""Get the dimensions as a list."""
|
|
|
|
if self._dimensions:
|
|
|
|
return self._dimensions.split(",")
|
2014-10-29 16:14:39 -06:00
|
|
|
else:
|
|
|
|
return []
|
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
@property
|
|
|
|
def operator(self):
|
|
|
|
"""Get the operator."""
|
|
|
|
return self._operator
|
|
|
|
|
|
|
|
@property
|
|
|
|
def threshold(self):
|
|
|
|
"""Get the threshold value."""
|
|
|
|
return self._threshold
|
|
|
|
|
|
|
|
@property
|
|
|
|
def period(self):
|
|
|
|
"""Get the period. Default is 60 seconds."""
|
|
|
|
if self._period:
|
|
|
|
return self._period
|
2014-10-29 16:14:39 -06:00
|
|
|
else:
|
|
|
|
return u'60'
|
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
@property
|
|
|
|
def periods(self):
|
|
|
|
"""Get the periods. Default is 1."""
|
|
|
|
if self._periods:
|
|
|
|
return self._periods
|
2014-10-29 16:14:39 -06:00
|
|
|
else:
|
|
|
|
return u'1'
|
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
@property
|
|
|
|
def normalized_operator(self):
|
|
|
|
"""Get the operator as one of LT, GT, LTE, or GTE."""
|
|
|
|
if self._operator.lower() == "lt" or self._operator == "<":
|
2014-10-29 16:14:39 -06:00
|
|
|
return u"LT"
|
2014-11-03 08:41:44 -07:00
|
|
|
elif self._operator.lower() == "gt" or self._operator == ">":
|
2014-10-29 16:14:39 -06:00
|
|
|
return u"GT"
|
2014-11-03 08:41:44 -07:00
|
|
|
elif self._operator.lower() == "lte" or self._operator == "<=":
|
2014-10-29 16:14:39 -06:00
|
|
|
return u"LTE"
|
2014-11-03 08:41:44 -07:00
|
|
|
elif self._operator.lower() == "gte" or self._operator == ">=":
|
2014-10-29 16:14:39 -06:00
|
|
|
return u"GTE"
|
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
@property
|
|
|
|
def id(self):
|
|
|
|
"""Get the id used to identify this sub expression in the repo."""
|
|
|
|
return self._id
|
|
|
|
|
|
|
|
@id.setter
|
|
|
|
def id(self, id):
|
|
|
|
"""Set the d used to identify this sub expression in the repo."""
|
|
|
|
self._id = id
|
|
|
|
|
2014-10-29 16:14:39 -06:00
|
|
|
|
|
|
|
class BinaryOp(object):
|
|
|
|
def __init__(self, tokens):
|
|
|
|
self.op = tokens[0][1]
|
|
|
|
self.operands = tokens[0][0::2]
|
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
@property
|
|
|
|
def operands_list(self):
|
2014-10-29 16:14:39 -06:00
|
|
|
return ([sub_operand for operand in self.operands for sub_operand in
|
2014-11-03 08:41:44 -07:00
|
|
|
operand.operands_list])
|
2014-10-29 16:14:39 -06:00
|
|
|
|
|
|
|
|
|
|
|
class AndSubExpr(BinaryOp):
|
|
|
|
""" Expand later as needed.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class OrSubExpr(BinaryOp):
|
|
|
|
"""Expand later as needed.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
COMMA = Literal(",")
|
|
|
|
LPAREN = Literal("(")
|
|
|
|
RPAREN = Literal(")")
|
|
|
|
EQUAL = Literal("=")
|
|
|
|
LBRACE = Literal("{")
|
|
|
|
RBRACE = Literal("}")
|
|
|
|
|
|
|
|
# Initialize non-ascii unicode code points in the Basic Multilingual Plane.
|
|
|
|
unicode_printables = u''.join(
|
|
|
|
unichr(c) for c in xrange(128, 65536) if not unichr(c).isspace())
|
|
|
|
|
|
|
|
# Does not like comma. No Literals from above allowed.
|
|
|
|
valid_identifier_chars = (unicode_printables + alphanums + ".-_#!$%&'*+/:;?@["
|
|
|
|
"\\]^`|~")
|
|
|
|
|
|
|
|
metric_name = Word(valid_identifier_chars, min=1, max=255)("metric_name")
|
|
|
|
dimension_name = Word(valid_identifier_chars, min=1, max=255)
|
|
|
|
dimension_value = Word(valid_identifier_chars, min=1, max=255)
|
|
|
|
|
|
|
|
integer_number = Word(nums)
|
|
|
|
decimal_number = Word(nums + ".")
|
|
|
|
|
|
|
|
max = CaselessLiteral("max")
|
|
|
|
min = CaselessLiteral("min")
|
|
|
|
avg = CaselessLiteral("avg")
|
|
|
|
count = CaselessLiteral("count")
|
|
|
|
sum = CaselessLiteral("sum")
|
|
|
|
func = (max | min | avg | count | sum)("func")
|
|
|
|
|
|
|
|
less_than_op = (CaselessLiteral("<") | CaselessLiteral("lt"))
|
|
|
|
less_than_eq_op = (CaselessLiteral("<=") | CaselessLiteral("lte"))
|
|
|
|
greater_than_op = (CaselessLiteral(">") | CaselessLiteral("gt"))
|
|
|
|
greater_than_eq_op = (CaselessLiteral(">=") | CaselessLiteral("gte"))
|
|
|
|
|
|
|
|
# Order is important. Put longer prefix first.
|
|
|
|
relational_op = (
|
|
|
|
less_than_eq_op | less_than_op | greater_than_eq_op | greater_than_op)(
|
|
|
|
"relational_op")
|
|
|
|
|
|
|
|
AND = CaselessLiteral("and") | CaselessLiteral("&&")
|
|
|
|
OR = CaselessLiteral("or") | CaselessLiteral("||")
|
|
|
|
logical_op = (AND | OR)("logical_op")
|
|
|
|
|
|
|
|
times = CaselessLiteral("times")
|
|
|
|
|
|
|
|
dimension = Group(dimension_name + EQUAL + dimension_value)
|
2014-11-07 10:04:25 -07:00
|
|
|
|
|
|
|
# Cannot have any whitespace after the comma delimiter.
|
2014-10-29 16:14:39 -06:00
|
|
|
dimension_list = Group(Optional(
|
2014-11-07 10:04:25 -07:00
|
|
|
LBRACE + delimitedList(dimension, delim=',', combine=True)(
|
2014-10-29 16:14:39 -06:00
|
|
|
"dimensions_list") + RBRACE))
|
|
|
|
|
|
|
|
metric = metric_name + dimension_list("dimensions")
|
|
|
|
period = integer_number("period")
|
|
|
|
threshold = decimal_number("threshold")
|
|
|
|
periods = integer_number("periods")
|
|
|
|
|
|
|
|
expression = Forward()
|
|
|
|
|
|
|
|
sub_expression = (func + LPAREN + metric + Optional(
|
|
|
|
COMMA + period) + RPAREN + relational_op + threshold + Optional(
|
|
|
|
times + periods) | LPAREN + expression + RPAREN)
|
|
|
|
|
|
|
|
sub_expression.setParseAction(SubExpr)
|
|
|
|
|
|
|
|
expression = operatorPrecedence(sub_expression,
|
|
|
|
[(AND, 2, opAssoc.LEFT, AndSubExpr),
|
|
|
|
(OR, 2, opAssoc.LEFT, OrSubExpr)])
|
|
|
|
|
|
|
|
|
|
|
|
class AlarmExprParser(object):
|
|
|
|
def __init__(self, expr):
|
|
|
|
self._expr = expr
|
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
@property
|
|
|
|
def sub_expr_list(self):
|
2014-10-29 16:14:39 -06:00
|
|
|
parseResult = (expression + stringEnd).parseString(self._expr)
|
2014-11-03 08:41:44 -07:00
|
|
|
sub_expr_list = parseResult[0].operands_list
|
2014-10-29 16:14:39 -06:00
|
|
|
return sub_expr_list
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
2014-11-03 08:41:44 -07:00
|
|
|
""" Used for development and testing. """
|
2014-10-31 13:09:56 -06:00
|
|
|
expr = "max(-_.千幸福的笑脸{घोड़ा=馬,dn2=dv2}, 60) gte 100 times 3 && " \
|
2014-10-29 16:14:39 -06:00
|
|
|
"(min(ເຮືອນ{dn3=dv3,家=дом}) < 10 or sum(biz{dn5=dv5}) > 99 and " \
|
|
|
|
"count(fizzle) lt 0 or count(baz) > 1)".decode('utf8')
|
|
|
|
# expr = "max(foo{hostname=mini-mon,千=千}, 120) > 100 and (max(bar)>100 \
|
|
|
|
# or max(biz)>100)".decode('utf8')
|
|
|
|
alarmExprParser = AlarmExprParser(expr)
|
2014-11-03 08:41:44 -07:00
|
|
|
r = alarmExprParser.sub_expr_list
|
2014-10-29 16:14:39 -06:00
|
|
|
for sub_expression in r:
|
2014-11-03 08:41:44 -07:00
|
|
|
print sub_expression.sub_expr_str
|
|
|
|
print sub_expression.fmtd_sub_expr_str
|
|
|
|
print sub_expression.dimensions_str
|
2014-10-29 16:14:39 -06:00
|
|
|
print
|
|
|
|
|
2014-11-03 08:41:44 -07:00
|
|
|
|
2014-10-29 16:14:39 -06:00
|
|
|
if __name__ == "__main__":
|
|
|
|
sys.exit(main())
|