"Fossies" - the Fresh Open Source Software Archive

Member "monasca-api-3.1.0/monasca_api/expression_parser/alarm_expr_parser.py" (27 Sep 2019, 11989 Bytes) of package /linux/misc/openstack/monasca-api-3.1.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "alarm_expr_parser.py" see the Fossies "Dox" file reference documentation.

    1 #!/usr/bin/python
    2 # -*- coding: utf-8 -*-
    3 # (C) Copyright 2015-2017 Hewlett Packard Enterprise LP
    4 #
    5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    6 # not use this file except in compliance with the License. You may obtain
    7 # a copy of the License at
    8 #
    9 # http://www.apache.org/licenses/LICENSE-2.0
   10 #
   11 # Unless required by applicable law or agreed to in writing, software
   12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   14 # License for the specific language governing permissions and limitations
   15 # under the License.
   16 import sys
   17 
   18 import pyparsing
   19 import six
   20 
   21 _DETERMINISTIC_ASSIGNMENT_LEN = 3
   22 _DETERMINISTIC_ASSIGNMENT_SHORT_LEN = 1
   23 _DETERMINISTIC_ASSIGNMENT_VALUE_INDEX = 2
   24 _DEFAULT_PERIOD = 60
   25 _DEFAULT_PERIODS = 1
   26 
   27 
   28 class SubExpr(object):
   29 
   30     def __init__(self, tokens):
   31 
   32         if not tokens.func:
   33             if tokens.relational_op.lower() in ['gte', 'gt', '>=', '>']:
   34                 self._func = "max"
   35             else:
   36                 self._func = "min"
   37         else:
   38             self._func = tokens.func
   39         self._metric_name = tokens.metric_name
   40         self._dimensions = tokens.dimensions_list
   41         self._operator = tokens.relational_op
   42         self._threshold = float(tokens.threshold)
   43         if tokens.period:
   44             self._period = int(tokens.period)
   45         else:
   46             self._period = _DEFAULT_PERIOD
   47         if tokens.periods:
   48             self._periods = int(tokens.periods)
   49         else:
   50             self._periods = _DEFAULT_PERIODS
   51         self._deterministic = tokens.deterministic
   52         self._id = None
   53 
   54     @property
   55     def fmtd_sub_expr_str(self):
   56         """Get the entire sub expressions as a string with spaces."""
   57         result = u"{}({}".format(self.normalized_func,
   58                                  self._metric_name)
   59 
   60         if self._dimensions is not None:
   61             result += "{" + self.dimensions_str + "}"
   62 
   63         if self._period != _DEFAULT_PERIOD:
   64             result += ", {}".format(self._period)
   65 
   66         result += ")"
   67 
   68         result += " {} {}".format(self._operator,
   69                                   self._threshold)
   70 
   71         if self._periods != _DEFAULT_PERIODS:
   72             result += " times {}".format(self._periods)
   73 
   74         return result
   75 
   76     @property
   77     def dimensions_str(self):
   78         """Get all the dimensions as a single comma delimited string."""
   79         return u",".join(self._dimensions)
   80 
   81     @property
   82     def operands_list(self):
   83         """Get this sub expression as a list."""
   84         return [self]
   85 
   86     @property
   87     def func(self):
   88         """Get the function as it appears in the orig expression."""
   89         return self._func
   90 
   91     @property
   92     def normalized_func(self):
   93         """Get the function upper-cased."""
   94         return self._func.upper()
   95 
   96     @property
   97     def metric_name(self):
   98         """Get the metric name as it appears in the orig expression."""
   99         return self._metric_name
  100 
  101     @property
  102     def normalized_metric_name(self):
  103         """Get the metric name lower-cased."""
  104         return self._metric_name.lower()
  105 
  106     @property
  107     def dimensions(self):
  108         """Get the dimensions."""
  109         return u",".join(self._dimensions)
  110 
  111     @property
  112     def dimensions_as_list(self):
  113         """Get the dimensions as a list."""
  114         if self._dimensions:
  115             return self._dimensions
  116         else:
  117             return []
  118 
  119     @property
  120     def operator(self):
  121         """Get the operator."""
  122         return self._operator
  123 
  124     @property
  125     def threshold(self):
  126         """Get the threshold value."""
  127         return self._threshold
  128 
  129     @property
  130     def period(self):
  131         """Get the period. Default is 60 seconds."""
  132         if self._period:
  133             return self._period
  134         else:
  135             return u'60'
  136 
  137     @property
  138     def periods(self):
  139         """Get the periods. Default is 1."""
  140         if self._periods:
  141             return self._periods
  142         else:
  143             return u'1'
  144 
  145     @property
  146     def deterministic(self):
  147         return True if self._deterministic else False
  148 
  149     @property
  150     def normalized_operator(self):
  151         """Get the operator as one of LT, GT, LTE, or GTE."""
  152         if self._operator.lower() == "lt" or self._operator == "<":
  153             return u"LT"
  154         elif self._operator.lower() == "gt" or self._operator == ">":
  155             return u"GT"
  156         elif self._operator.lower() == "lte" or self._operator == "<=":
  157             return u"LTE"
  158         elif self._operator.lower() == "gte" or self._operator == ">=":
  159             return u"GTE"
  160 
  161     @property
  162     def id(self):
  163         """Get the id used to identify this sub expression in the repo."""
  164         return self._id
  165 
  166     @id.setter
  167     def id(self, id):
  168         """Set the d used to identify this sub expression in the repo."""
  169         self._id = id
  170 
  171 
  172 class BinaryOp(object):
  173     def __init__(self, tokens):
  174         self.op = tokens[0][1]
  175         self.operands = tokens[0][0::2]
  176 
  177     @property
  178     def operands_list(self):
  179         return ([sub_operand for operand in self.operands for sub_operand in
  180                  operand.operands_list])
  181 
  182 
  183 class AndSubExpr(BinaryOp):
  184     """Expand later as needed."""
  185     pass
  186 
  187 
  188 class OrSubExpr(BinaryOp):
  189     """Expand later as needed."""
  190     pass
  191 
  192 
  193 COMMA = pyparsing.Suppress(pyparsing.Literal(","))
  194 LPAREN = pyparsing.Suppress(pyparsing.Literal("("))
  195 RPAREN = pyparsing.Suppress(pyparsing.Literal(")"))
  196 EQUAL = pyparsing.Literal("=")
  197 LBRACE = pyparsing.Suppress(pyparsing.Literal("{"))
  198 RBRACE = pyparsing.Suppress(pyparsing.Literal("}"))
  199 
  200 
  201 def periodValidation(instr, loc, tokens):
  202     period = int(tokens[0])
  203     if period == 0:
  204         raise pyparsing.ParseFatalException(instr, loc,
  205                                             "Period must not be 0")
  206 
  207     if (period % 60) != 0:
  208         raise pyparsing.ParseFatalException(instr, loc,
  209                                             "Period {} must be a multiple of 60"
  210                                             .format(period))
  211     # Must return the string
  212     return tokens[0]
  213 
  214 
  215 def periodsValidation(instr, loc, tokens):
  216     periods = int(tokens[0])
  217     if periods < 1:
  218         raise pyparsing.ParseFatalException(instr, loc,
  219                                             "Periods {} must be 1 or greater"
  220                                             .format(periods))
  221     # Must return the string
  222     return tokens[0]
  223 
  224 # Initialize non-ascii unicode code points in the Basic Multilingual Plane.
  225 unicode_printables = u''.join(
  226     six.unichr(c) for c in range(128, 65536) if not six.unichr(c).isspace())
  227 
  228 # Does not like comma. No Literals from above allowed.
  229 valid_identifier_chars = (
  230     (unicode_printables + pyparsing.alphanums + ".-_#!$%&'*+/:;?@[\\]^`|~"))
  231 
  232 metric_name = (
  233     pyparsing.Word(valid_identifier_chars, min=1, max=255)("metric_name"))
  234 dimension_name = pyparsing.Word(valid_identifier_chars + ' ', min=1, max=255)
  235 dimension_value = pyparsing.Word(valid_identifier_chars + ' ', min=1, max=255)
  236 
  237 MINUS = pyparsing.Literal('-')
  238 integer_number = pyparsing.Word(pyparsing.nums)
  239 decimal_number = (pyparsing.Optional(MINUS) + integer_number +
  240                   pyparsing.Optional("." + integer_number))
  241 decimal_number.setParseAction(lambda tokens: "".join(tokens))
  242 
  243 max = pyparsing.CaselessLiteral("max")
  244 min = pyparsing.CaselessLiteral("min")
  245 avg = pyparsing.CaselessLiteral("avg")
  246 count = pyparsing.CaselessLiteral("count")
  247 sum = pyparsing.CaselessLiteral("sum")
  248 last = pyparsing.CaselessLiteral("last")
  249 func = (max | min | avg | count | sum | last)("func")
  250 
  251 less_than_op = (
  252     (pyparsing.CaselessLiteral("<") | pyparsing.CaselessLiteral("lt")))
  253 less_than_eq_op = (
  254     (pyparsing.CaselessLiteral("<=") | pyparsing.CaselessLiteral("lte")))
  255 greater_than_op = (
  256     (pyparsing.CaselessLiteral(">") | pyparsing.CaselessLiteral("gt")))
  257 greater_than_eq_op = (
  258     (pyparsing.CaselessLiteral(">=") | pyparsing.CaselessLiteral("gte")))
  259 
  260 # Order is important. Put longer prefix first.
  261 relational_op = (
  262     less_than_eq_op | less_than_op | greater_than_eq_op | greater_than_op)(
  263     "relational_op")
  264 
  265 AND = pyparsing.CaselessLiteral("and") | pyparsing.CaselessLiteral("&&")
  266 OR = pyparsing.CaselessLiteral("or") | pyparsing.CaselessLiteral("||")
  267 logical_op = (AND | OR)("logical_op")
  268 
  269 times = pyparsing.CaselessLiteral("times")
  270 
  271 dimension = dimension_name + EQUAL + dimension_value
  272 dimension.setParseAction(lambda tokens: "".join(tokens))
  273 
  274 dimension_list = pyparsing.Group((LBRACE + pyparsing.Optional(
  275     pyparsing.delimitedList(dimension)) +
  276     RBRACE))("dimensions_list")
  277 
  278 metric = metric_name + pyparsing.Optional(dimension_list)
  279 period = integer_number.copy().addParseAction(periodValidation)("period")
  280 threshold = decimal_number("threshold")
  281 periods = integer_number.copy().addParseAction(periodsValidation)("periods")
  282 
  283 deterministic = (
  284     pyparsing.CaselessLiteral('deterministic')
  285 )('deterministic')
  286 
  287 function_and_metric = (
  288     func + LPAREN + metric +
  289     pyparsing.Optional(COMMA + deterministic) +
  290     pyparsing.Optional(COMMA + period) +
  291     RPAREN
  292 )
  293 
  294 expression = pyparsing.Forward()
  295 
  296 sub_expression = ((function_and_metric | metric) + relational_op + threshold +
  297                   pyparsing.Optional(times + periods) |
  298                   LPAREN + expression + RPAREN)
  299 sub_expression.setParseAction(SubExpr)
  300 
  301 expression = (
  302     pyparsing.operatorPrecedence(sub_expression,
  303                                  [(AND, 2, pyparsing.opAssoc.LEFT, AndSubExpr),
  304                                   (OR, 2, pyparsing.opAssoc.LEFT, OrSubExpr)]))
  305 
  306 
  307 class AlarmExprParser(object):
  308     def __init__(self, expr):
  309         self._expr = expr
  310 
  311     @property
  312     def sub_expr_list(self):
  313         # Remove all spaces before parsing. Simple, quick fix for whitespace
  314         # issue with dimension list not allowing whitespace after comma.
  315         parse_result = (expression + pyparsing.stringEnd).parseString(
  316             self._expr)
  317         sub_expr_list = parse_result[0].operands_list
  318         return sub_expr_list
  319 
  320 
  321 def main():
  322     """Used for development and testing."""
  323 
  324     expr_list = [
  325         "max(-_.千幸福的笑脸{घोड़ा=馬,  "
  326         "dn2=dv2,千幸福的笑脸घ=千幸福的笑脸घ}) gte 100 "
  327         "times 3 && "
  328         "(min(ເຮືອນ{dn3=dv3,家=дом}) < 10 or sum(biz{dn5=dv5}) >99 and "
  329         "count(fizzle) lt 0or count(baz) > 1)".decode('utf8'),
  330 
  331         "max(foo{hostname=mini-mon,千=千}, 120) > 100 and (max(bar)>100 "
  332         " or max(biz)>100)".decode('utf8'),
  333 
  334         "max(foo)>=100",
  335 
  336         "test_metric{this=that, that =  this} < 1",
  337 
  338         "max  (  3test_metric5  {  this  =  that  })  lt  5 times    3",
  339 
  340         "3test_metric5 lt 3",
  341 
  342         "ntp.offset > 1 or ntp.offset < -5",
  343 
  344         "max(3test_metric5{it's this=that's it}) lt 5 times 3",
  345 
  346         "count(log.error{test=1}, deterministic) > 1.0",
  347 
  348         "count(log.error{test=1}, deterministic, 120) > 1.0",
  349 
  350         "last(test_metric{hold=here}) < 13",
  351 
  352         "count(log.error{test=1}, deterministic, 130) > 1.0",
  353 
  354         "count(log.error{test=1}, deterministic) > 1.0 times 0",
  355     ]
  356 
  357     for expr in expr_list:
  358         print('orig expr: {}'.format(expr.encode('utf8')))
  359         sub_exprs = []
  360         try:
  361             alarm_expr_parser = AlarmExprParser(expr)
  362             sub_exprs = alarm_expr_parser.sub_expr_list
  363         except Exception as ex:
  364             print("Parse failed: {}".format(ex))
  365         for sub_expr in sub_exprs:
  366             print('sub expr: {}'.format(
  367                 sub_expr.fmtd_sub_expr_str.encode('utf8')))
  368             print('sub_expr dimensions: {}'.format(
  369                 sub_expr.dimensions_str.encode('utf8')))
  370             print('sub_expr deterministic: {}'.format(
  371                 sub_expr.deterministic))
  372             print('sub_expr period: {}'.format(
  373                 sub_expr.period))
  374             print("")
  375         print("")
  376 
  377 
  378 if __name__ == "__main__":
  379     sys.exit(main())