extend the prometheus query runner to support the range query

This commit is contained in:
jubel
2018-01-26 13:16:28 +08:00
parent 060fe1dd9c
commit 8a2676701b
3 changed files with 187 additions and 36 deletions

View File

@@ -1,10 +1,59 @@
import requests
import datetime
from datetime import datetime
from urlparse import parse_qs
from redash.query_runner import BaseQueryRunner, register, TYPE_DATETIME, TYPE_STRING
from redash.utils import json_dumps
def get_instant_rows(metrics_data):
rows = []
for metric in metrics_data:
row_data = metric['metric']
timestamp, value = metric['value']
date_time = datetime.fromtimestamp(timestamp)
row_data.update({"timestamp": date_time, "value": value})
rows.append(row_data)
return rows
def get_range_rows(metrics_data):
rows = []
for metric in metrics_data:
ts_values = metric['values']
metric_labels = metric['metric']
for values in ts_values:
row_data = metric_labels.copy()
timestamp, value = values
date_time = datetime.fromtimestamp(timestamp)
row_data.update({'timestamp': date_time, 'value': value})
rows.append(row_data)
return rows
class Prometheus(BaseQueryRunner):
def __init__(self, configuration):
self.columns = [
{
'friendly_name': 'timestamp',
'type': TYPE_DATETIME,
'name': 'timestamp'
},
{
'friendly_name': 'value',
'type': TYPE_STRING,
'name': 'value'
},
]
super(Prometheus, self).__init__(configuration)
@classmethod
def configuration_schema(cls):
return {
@@ -40,52 +89,56 @@ class Prometheus(BaseQueryRunner):
return schema.values()
def run_query(self, query, user):
"""
query syntax, it is the actual url query string.
check the prometheus http API for the detail of supported query string.
https://prometheus.io/docs/prometheus/latest/querying/api/
example: instant query
query=http_requests_total
example: range query
query=http_requests_total&start=2018-01-20T00:00:00.000Z&end=2018-01-25T00:00:00.000Z&step=60s
"""
base_url = self.configuration["url"]
try:
error = None
query = query.strip()
# for backward compatibility
query = 'query={}'.format(query) if not query.startswith('query=') else query
local_query = '/api/v1/query'
url = base_url + local_query
payload = {'query': query}
response = requests.get(url, params=payload)
payload = parse_qs(query)
query_type = 'query_range' if 'step' in payload.keys() else 'query'
api_endpoint = base_url + '/api/v1/{}'.format(query_type)
response = requests.get(api_endpoint, params=payload)
response.raise_for_status()
raw_data = response.json()['data']['result']
columns = [
{
'friendly_name': 'timestamp',
'type': TYPE_DATETIME,
'name': 'timestamp'
},
{
'friendly_name': 'value',
'type': TYPE_STRING,
'name': 'value'
},
]
columns_name = raw_data[0]['metric'].keys()
for column_name in columns_name:
columns.append({
'friendly_name': column_name,
'type': TYPE_STRING,
'name': column_name
})
rows = []
for row in raw_data:
h = {}
for r in row['metric']:
h[r] = row['metric'][r]
h['value'] = row['value'][1]
h['timestamp'] = datetime.datetime.fromtimestamp(row['value'][0])
rows.append(h)
metrics = response.json()['data']['result']
if len(metrics) == 0:
return None, 'query result is empty.'
metric_labels = metrics[0]['metric'].keys()
for label_name in metric_labels:
self.columns.append({
'friendly_name': label_name,
'type': TYPE_STRING,
'name': label_name
})
rows = get_range_rows(metrics) if query_type == 'query_range' else get_instant_rows(metrics)
json_data = json_dumps(
{
'rows': rows,
'columns': columns
'columns': self.columns
}
)
except requests.RequestException as e:
return None, str(e)
except KeyboardInterrupt:

View File

@@ -55,8 +55,8 @@ class Python(BaseQueryRunner):
'type': 'string',
'title': 'Modules to import prior to running the script'
},
'additionalModulesPaths' : {
'type' : 'string'
'additionalModulesPaths': {
'type': 'string'
}
},
}

View File

@@ -0,0 +1,98 @@
import datetime
import json
from unittest import TestCase
from redash.query_runner.prometheus import get_instant_rows, get_range_rows
class TestPrometheus(TestCase):
def setUp(self):
self.instant_query_result = [
{
"metric": {
"name": "example_metric_name",
"foo_bar": "foo",
},
"value": [1516937400.781, "7400_foo"]
},
{
"metric": {
"name": "example_metric_name",
"foo_bar": "bar",
},
"value": [1516937400.781, "7400_bar"]
}
]
self.range_query_result = [
{
"metric": {
"name": "example_metric_name",
"foo_bar": "foo",
},
"values": [
[1516937400.781, "7400_foo"],
[1516938000.781, "8000_foo"],
]
},
{
"metric": {
"name": "example_metric_name",
"foo_bar": "bar",
},
"values": [
[1516937400.781, "7400_bar"],
[1516938000.781, "8000_bar"],
]
}
]
def test_get_instant_rows(self):
instant_rows = [
{
"name": "example_metric_name",
"foo_bar": "foo",
"timestamp": datetime.datetime.fromtimestamp(1516937400.781),
"value": "7400_foo"
},
{
"name": "example_metric_name",
"foo_bar": "bar",
"timestamp": datetime.datetime.fromtimestamp(1516937400.781),
"value": "7400_bar"
},
]
rows = get_instant_rows(self.instant_query_result)
self.assertEqual(instant_rows, rows)
def test_get_range_rows(self):
range_rows = [
{
"name": "example_metric_name",
"foo_bar": "foo",
"timestamp": datetime.datetime.fromtimestamp(1516937400.781),
"value": "7400_foo"
},
{
"name": "example_metric_name",
"foo_bar": "foo",
"timestamp": datetime.datetime.fromtimestamp(1516938000.781),
"value": "8000_foo"
},
{
"name": "example_metric_name",
"foo_bar": "bar",
"timestamp": datetime.datetime.fromtimestamp(1516937400.781),
"value": "7400_bar"
},
{
"name": "example_metric_name",
"foo_bar": "bar",
"timestamp": datetime.datetime.fromtimestamp(1516938000.781),
"value": "8000_bar"
},
]
rows = get_range_rows(self.range_query_result)
self.assertEqual(range_rows, rows)