| #!/usr/bin/env python |
| # -*- encoding: utf-8 -*- |
| |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you 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. |
| |
| """ query.py """ |
| import tornado.httpclient |
| import tornado.gen |
| |
| from heron.tools.tracker.src.python.query_operators import * |
| |
| |
| #################################################################### |
| # Parsing and executing the query string. |
| #################################################################### |
| |
| # pylint: disable=no-self-use |
| class Query(object): |
| """Execute the query for metrics. Uses Tracker to get |
| individual metrics that are part of the query. |
| Example usage: |
| query = Query(tracker) |
| result = query.execute(tmaster, query_string)""" |
| # pylint: disable=undefined-variable |
| def __init__(self, tracker): |
| self.tracker = tracker |
| self.operators = { |
| 'TS':TS, |
| 'DEFAULT':Default, |
| 'MAX':Max, |
| 'SUM':Sum, |
| 'SUBTRACT':Subtract, |
| 'PERCENTILE':Percentile, |
| 'DIVIDE':Divide, |
| 'MULTIPLY':Multiply, |
| 'RATE':Rate |
| } |
| |
| |
| # pylint: disable=attribute-defined-outside-init, no-member |
| @tornado.gen.coroutine |
| def execute_query(self, tmaster, query_string, start, end): |
| """ execute query """ |
| if not tmaster: |
| raise Exception("No tmaster found") |
| self.tmaster = tmaster |
| root = self.parse_query_string(query_string) |
| metrics = yield root.execute(self.tracker, self.tmaster, start, end) |
| raise tornado.gen.Return(metrics) |
| |
| def find_closing_braces(self, query): |
| """Find the index of the closing braces for the opening braces |
| at the start of the query string. Note that first character |
| of input string must be an opening braces.""" |
| if query[0] != '(': |
| raise Exception("Trying to find closing braces for no opening braces") |
| num_open_braces = 0 |
| for i in range(len(query)): |
| c = query[i] |
| if c == '(': |
| num_open_braces += 1 |
| elif c == ')': |
| num_open_braces -= 1 |
| if num_open_braces == 0: |
| return i |
| raise Exception("No closing braces found") |
| |
| def get_sub_parts(self, query): |
| """The subparts are seperated by a comma. Make sure |
| that commas inside the part themselves are not considered.""" |
| parts = [] |
| num_open_braces = 0 |
| delimiter = ',' |
| last_starting_index = 0 |
| for i in range(len(query)): |
| if query[i] == '(': |
| num_open_braces += 1 |
| elif query[i] == ')': |
| num_open_braces -= 1 |
| elif query[i] == delimiter and num_open_braces == 0: |
| parts.append(query[last_starting_index: i].strip()) |
| last_starting_index = i + 1 |
| parts.append(query[last_starting_index:].strip()) |
| return parts |
| |
| def parse_query_string(self, query): |
| """Returns a parse tree for the query, each of the node is a |
| subclass of Operator. This is both a lexical as well as syntax analyzer step.""" |
| if not query: |
| return None |
| # Just braces do not matter |
| if query[0] == '(': |
| index = self.find_closing_braces(query) |
| # This must be the last index, since this was an NOP starting brace |
| if index != len(query) - 1: |
| raise Exception("Invalid syntax") |
| else: |
| return self.parse_query_string(query[1:-1]) |
| start_index = query.find("(") |
| # There must be a ( in the query |
| if start_index < 0: |
| # Otherwise it must be a constant |
| try: |
| constant = float(query) |
| return constant |
| except ValueError: |
| raise Exception("Invalid syntax") |
| token = query[:start_index] |
| if token not in self.operators: |
| raise Exception("Invalid token: " + token) |
| |
| # Get sub components |
| rest_of_the_query = query[start_index:] |
| braces_end_index = self.find_closing_braces(rest_of_the_query) |
| if braces_end_index != len(rest_of_the_query) - 1: |
| raise Exception("Invalid syntax") |
| parts = self.get_sub_parts(rest_of_the_query[1:-1]) |
| |
| # parts are simple strings in this case |
| if token == "TS": |
| # This will raise exception if parts are not syntactically correct |
| return self.operators[token](parts) |
| |
| children = [] |
| for part in parts: |
| children.append(self.parse_query_string(part)) |
| |
| # Make a node for the current token |
| node = self.operators[token](children) |
| return node |