Merge "Change configuration schema and query process for Prometheus collector"
This commit is contained in:
commit
55ca0e2983
@ -22,8 +22,7 @@ from decimal import ROUND_HALF_UP
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import requests
|
import requests
|
||||||
from voluptuous import All
|
from voluptuous import In
|
||||||
from voluptuous import Length
|
|
||||||
from voluptuous import Required
|
from voluptuous import Required
|
||||||
from voluptuous import Schema
|
from voluptuous import Schema
|
||||||
|
|
||||||
@ -66,15 +65,16 @@ CONF = cfg.CONF
|
|||||||
|
|
||||||
PROMETHEUS_EXTRA_SCHEMA = {
|
PROMETHEUS_EXTRA_SCHEMA = {
|
||||||
Required('extra_args'): {
|
Required('extra_args'): {
|
||||||
Required('query'): All(str, Length(min=1)),
|
Required('aggregation_method', default='max'):
|
||||||
|
In([
|
||||||
|
'avg', 'count', 'max',
|
||||||
|
'min', 'stddev', 'stdvar',
|
||||||
|
'sum'
|
||||||
|
]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PrometheusConfigError(collect_exceptions.CollectError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PrometheusResponseError(collect_exceptions.CollectError):
|
class PrometheusResponseError(collect_exceptions.CollectError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ class PrometheusCollector(collector.BaseCollector):
|
|||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def _format_data(self, metric_name, project_id, start, end, data):
|
def _format_data(self, metric_name, scope_key, scope_id, start, end, data):
|
||||||
"""Formats Prometheus data format to Cloudkitty data format.
|
"""Formats Prometheus data format to Cloudkitty data format.
|
||||||
|
|
||||||
Returns metadata, groupby, qty
|
Returns metadata, groupby, qty
|
||||||
@ -173,7 +173,7 @@ class PrometheusCollector(collector.BaseCollector):
|
|||||||
for meta in self.conf[metric_name]['metadata']:
|
for meta in self.conf[metric_name]['metadata']:
|
||||||
metadata[meta] = data['metric'][meta]
|
metadata[meta] = data['metric'][meta]
|
||||||
|
|
||||||
groupby = {}
|
groupby = {scope_key: scope_id}
|
||||||
for meta in self.conf[metric_name]['groupby']:
|
for meta in self.conf[metric_name]['groupby']:
|
||||||
groupby[meta] = data['metric'].get(meta, '')
|
groupby[meta] = data['metric'].get(meta, '')
|
||||||
|
|
||||||
@ -189,23 +189,26 @@ class PrometheusCollector(collector.BaseCollector):
|
|||||||
|
|
||||||
return metadata, groupby, qty
|
return metadata, groupby, qty
|
||||||
|
|
||||||
def fetch_all(self, metric_name, start, end, project_id, q_filter=None):
|
def fetch_all(self, metric_name, start, end, scope_id, q_filter=None):
|
||||||
"""Returns metrics to be valorized."""
|
"""Returns metrics to be valorized."""
|
||||||
query = self.conf[metric_name]['extra_args']['query']
|
scope_key = CONF.collect.scope_key
|
||||||
period = CONF.collect.period
|
method = self.conf[metric_name]['extra_args']['aggregation_method']
|
||||||
|
groupby = self.conf[metric_name].get('groupby', [])
|
||||||
|
metadata = self.conf[metric_name].get('metadata', [])
|
||||||
|
period = end - start
|
||||||
|
time = end
|
||||||
|
|
||||||
if '$period' in query:
|
query = '{0}({0}_over_time({1}{{{2}="{3}"}}[{4}s])) by ({5})'.format(
|
||||||
try:
|
method,
|
||||||
query = ck_utils.template_str_substitute(
|
metric_name,
|
||||||
query, {'period': str(period) + 's'},
|
scope_key,
|
||||||
|
scope_id,
|
||||||
|
period,
|
||||||
|
', '.join(groupby + metadata),
|
||||||
)
|
)
|
||||||
except (KeyError, ValueError):
|
|
||||||
raise PrometheusConfigError(
|
|
||||||
'Invalid prometheus query: {}'.format(query))
|
|
||||||
|
|
||||||
res = self._conn.get_instant(
|
res = self._conn.get_instant(
|
||||||
query,
|
query,
|
||||||
end,
|
time,
|
||||||
)
|
)
|
||||||
|
|
||||||
# If the query returns an empty dataset,
|
# If the query returns an empty dataset,
|
||||||
@ -218,7 +221,8 @@ class PrometheusCollector(collector.BaseCollector):
|
|||||||
for item in res['data']['result']:
|
for item in res['data']['result']:
|
||||||
metadata, groupby, qty = self._format_data(
|
metadata, groupby, qty = self._format_data(
|
||||||
metric_name,
|
metric_name,
|
||||||
project_id,
|
scope_key,
|
||||||
|
scope_id,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
item,
|
item,
|
||||||
|
@ -33,12 +33,21 @@ class PrometheusCollectorTest(tests.TestCase):
|
|||||||
self._tenant_id = samples.TENANT
|
self._tenant_id = samples.TENANT
|
||||||
args = {
|
args = {
|
||||||
'period': 3600,
|
'period': 3600,
|
||||||
|
'scope_key': 'namespace',
|
||||||
'conf': {
|
'conf': {
|
||||||
'metrics': {
|
'metrics': {
|
||||||
'http_requests_total': {
|
'http_requests_total': {
|
||||||
'unit': 'instance',
|
'unit': 'instance',
|
||||||
|
'groupby': [
|
||||||
|
'foo',
|
||||||
|
'bar',
|
||||||
|
],
|
||||||
|
'metadata': [
|
||||||
|
'code',
|
||||||
|
'instance',
|
||||||
|
],
|
||||||
'extra_args': {
|
'extra_args': {
|
||||||
'query': 'http_request_total[$period]',
|
'aggregation_method': 'avg',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -47,12 +56,41 @@ class PrometheusCollectorTest(tests.TestCase):
|
|||||||
transformers = transformer.get_transformers()
|
transformers = transformer.get_transformers()
|
||||||
self.collector = prometheus.PrometheusCollector(transformers, **args)
|
self.collector = prometheus.PrometheusCollector(transformers, **args)
|
||||||
|
|
||||||
|
def test_fetch_all_build_query(self):
|
||||||
|
query = (
|
||||||
|
'avg(avg_over_time(http_requests_total'
|
||||||
|
'{project_id="f266f30b11f246b589fd266f85eeec39"}[3600s]'
|
||||||
|
')) by (foo, bar, project_id, code, instance)'
|
||||||
|
)
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
prometheus.PrometheusClient, 'get_instant',
|
||||||
|
) as mock_get:
|
||||||
|
self.collector.fetch_all(
|
||||||
|
'http_requests_total',
|
||||||
|
samples.FIRST_PERIOD_BEGIN,
|
||||||
|
samples.FIRST_PERIOD_END,
|
||||||
|
self._tenant_id,
|
||||||
|
)
|
||||||
|
mock_get.assert_called_once_with(
|
||||||
|
query,
|
||||||
|
samples.FIRST_PERIOD_END,
|
||||||
|
)
|
||||||
|
|
||||||
def test_format_data_instant_query(self):
|
def test_format_data_instant_query(self):
|
||||||
expected = ({}, {'project_id': ''}, Decimal('7'))
|
expected = ({
|
||||||
|
'code': '200',
|
||||||
|
'instance': 'localhost:9090',
|
||||||
|
}, {
|
||||||
|
'bar': '',
|
||||||
|
'foo': '',
|
||||||
|
'project_id': ''
|
||||||
|
}, Decimal('7'))
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'metric_name': 'http_requests_total',
|
'metric_name': 'http_requests_total',
|
||||||
'project_id': self._tenant_id,
|
'scope_key': 'project_id',
|
||||||
|
'scope_id': self._tenant_id,
|
||||||
'start': samples.FIRST_PERIOD_BEGIN,
|
'start': samples.FIRST_PERIOD_BEGIN,
|
||||||
'end': samples.FIRST_PERIOD_END,
|
'end': samples.FIRST_PERIOD_END,
|
||||||
'data': samples.PROMETHEUS_RESP_INSTANT_QUERY['data']['result'][0],
|
'data': samples.PROMETHEUS_RESP_INSTANT_QUERY['data']['result'][0],
|
||||||
@ -61,11 +99,19 @@ class PrometheusCollectorTest(tests.TestCase):
|
|||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
def test_format_data_instant_query_2(self):
|
def test_format_data_instant_query_2(self):
|
||||||
expected = ({}, {'project_id': ''}, Decimal('42'))
|
expected = ({
|
||||||
|
'code': '200',
|
||||||
|
'instance': 'localhost:9090',
|
||||||
|
}, {
|
||||||
|
'bar': '',
|
||||||
|
'foo': '',
|
||||||
|
'project_id': ''
|
||||||
|
}, Decimal('42'))
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'metric_name': 'http_requests_total',
|
'metric_name': 'http_requests_total',
|
||||||
'project_id': self._tenant_id,
|
'scope_key': 'project_id',
|
||||||
|
'scope_id': self._tenant_id,
|
||||||
'start': samples.FIRST_PERIOD_BEGIN,
|
'start': samples.FIRST_PERIOD_BEGIN,
|
||||||
'end': samples.FIRST_PERIOD_END,
|
'end': samples.FIRST_PERIOD_END,
|
||||||
'data': samples.PROMETHEUS_RESP_INSTANT_QUERY['data']['result'][1],
|
'data': samples.PROMETHEUS_RESP_INSTANT_QUERY['data']['result'][1],
|
||||||
@ -77,18 +123,24 @@ class PrometheusCollectorTest(tests.TestCase):
|
|||||||
expected = {
|
expected = {
|
||||||
'http_requests_total': [
|
'http_requests_total': [
|
||||||
{
|
{
|
||||||
'desc': {'project_id': ''},
|
'desc': {
|
||||||
'groupby': {'project_id': ''},
|
'bar': '', 'foo': '', 'project_id': '',
|
||||||
'metadata': {},
|
'code': '200', 'instance': 'localhost:9090',
|
||||||
|
},
|
||||||
|
'groupby': {'bar': '', 'foo': '', 'project_id': ''},
|
||||||
|
'metadata': {'code': '200', 'instance': 'localhost:9090'},
|
||||||
'vol': {
|
'vol': {
|
||||||
'qty': Decimal('7'),
|
'qty': Decimal('7'),
|
||||||
'unit': 'instance'
|
'unit': 'instance'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'desc': {'project_id': ''},
|
'desc': {
|
||||||
'groupby': {'project_id': ''},
|
'bar': '', 'foo': '', 'project_id': '',
|
||||||
'metadata': {},
|
'code': '200', 'instance': 'localhost:9090',
|
||||||
|
},
|
||||||
|
'groupby': {'bar': '', 'foo': '', 'project_id': ''},
|
||||||
|
'metadata': {'code': '200', 'instance': 'localhost:9090'},
|
||||||
'vol': {
|
'vol': {
|
||||||
'qty': Decimal('42'),
|
'qty': Decimal('42'),
|
||||||
'unit': 'instance'
|
'unit': 'instance'
|
||||||
|
@ -116,10 +116,14 @@ class MetricConfigValidationTest(tests.TestCase):
|
|||||||
|
|
||||||
def test_prometheus_minimal_config_minimal_extra_args(self):
|
def test_prometheus_minimal_config_minimal_extra_args(self):
|
||||||
data = copy.deepcopy(self.base_data)
|
data = copy.deepcopy(self.base_data)
|
||||||
data['metrics']['metric_one']['extra_args'] = {'query': 'query'}
|
data['metrics']['metric_one']['extra_args'] = {
|
||||||
|
'aggregation_method': 'max',
|
||||||
|
}
|
||||||
expected_output = copy.deepcopy(self.base_output)
|
expected_output = copy.deepcopy(self.base_output)
|
||||||
expected_output['metric_one']['groupby'].append('project_id')
|
expected_output['metric_one']['groupby'].append('project_id')
|
||||||
expected_output['metric_one']['extra_args'] = {'query': 'query'}
|
expected_output['metric_one']['extra_args'] = {
|
||||||
|
'aggregation_method': 'max',
|
||||||
|
}
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
collector.prometheus.PrometheusCollector.check_configuration(data),
|
collector.prometheus.PrometheusCollector.check_configuration(data),
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Prometheus collector now supports, under extra_args section,
|
||||||
|
an aggregation_method option to decide which aggregation
|
||||||
|
method is to be performed over collected metrics.
|
Loading…
x
Reference in New Issue
Block a user