blob: 67599a626a533e7ef1a0ac0d4dbe01293a4f34b7 [file] [log] [blame]
#!/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