Add MAP mutator
This mutator can map arbitrary values to new values. This is useful with metrics reporting resource status as their value, but multiple statuses are billable. Change-Id: I8fcb9f2aa4ef23432089bfd6351a9c03ce3cf941
This commit is contained in:
@@ -93,7 +93,9 @@ METRIC_BASE_SCHEMA = {
|
|||||||
# (NONE, NUMBOOL, NOTNUMBOOL, FLOOR, CEIL).
|
# (NONE, NUMBOOL, NOTNUMBOOL, FLOOR, CEIL).
|
||||||
# Defaults to NONE
|
# Defaults to NONE
|
||||||
Required('mutate', default='NONE'):
|
Required('mutate', default='NONE'):
|
||||||
In(['NONE', 'NUMBOOL', 'NOTNUMBOOL', 'FLOOR', 'CEIL']),
|
In(['NONE', 'NUMBOOL', 'NOTNUMBOOL', 'FLOOR', 'CEIL', 'MAP']),
|
||||||
|
# Map dict used if mutate == 'MAP'
|
||||||
|
Optional('mutate_map'): dict,
|
||||||
# Collector-specific args. Should be overriden by schema provided for
|
# Collector-specific args. Should be overriden by schema provided for
|
||||||
# the given collector
|
# the given collector
|
||||||
Optional('extra_args'): dict,
|
Optional('extra_args'): dict,
|
||||||
@@ -270,6 +272,22 @@ def check_duplicates(metric_name, metric):
|
|||||||
return metric
|
return metric
|
||||||
|
|
||||||
|
|
||||||
|
def validate_map_mutator(metric_name, metric):
|
||||||
|
"""Validates MAP mutator"""
|
||||||
|
mutate = metric.get('mutate')
|
||||||
|
mutate_map = metric.get('mutate_map')
|
||||||
|
|
||||||
|
if mutate == 'MAP' and mutate_map is None:
|
||||||
|
raise InvalidConfiguration(
|
||||||
|
'Metric {} uses MAP mutator but mutate_map is missing: {}'.format(
|
||||||
|
metric_name, metric))
|
||||||
|
|
||||||
|
if mutate != 'MAP' and mutate_map is not None:
|
||||||
|
raise InvalidConfiguration(
|
||||||
|
'Metric {} not using MAP mutator but mutate_map is present: '
|
||||||
|
'{}'.format(metric_name, metric))
|
||||||
|
|
||||||
|
|
||||||
def validate_conf(conf):
|
def validate_conf(conf):
|
||||||
"""Validates the provided configuration."""
|
"""Validates the provided configuration."""
|
||||||
collector = get_collector_without_invoke()
|
collector = get_collector_without_invoke()
|
||||||
@@ -278,4 +296,5 @@ def validate_conf(conf):
|
|||||||
if 'alt_name' not in metric.keys():
|
if 'alt_name' not in metric.keys():
|
||||||
metric['alt_name'] = metric_name
|
metric['alt_name'] = metric_name
|
||||||
check_duplicates(metric_name, metric)
|
check_duplicates(metric_name, metric)
|
||||||
|
validate_map_mutator(metric_name, metric)
|
||||||
return output
|
return output
|
||||||
|
|||||||
@@ -434,7 +434,9 @@ class GnocchiCollector(collector.BaseCollector):
|
|||||||
qty = data['measures']['measures']['aggregated'][0][2]
|
qty = data['measures']['measures']['aggregated'][0][2]
|
||||||
converted_qty = ck_utils.convert_unit(
|
converted_qty = ck_utils.convert_unit(
|
||||||
qty, metconf['factor'], metconf['offset'])
|
qty, metconf['factor'], metconf['offset'])
|
||||||
mutated_qty = ck_utils.mutate(converted_qty, metconf['mutate'])
|
mutate_map = metconf.get('mutate_map')
|
||||||
|
mutated_qty = ck_utils.mutate(converted_qty, metconf['mutate'],
|
||||||
|
mutate_map=mutate_map)
|
||||||
return metadata, groupby, mutated_qty
|
return metadata, groupby, mutated_qty
|
||||||
|
|
||||||
def fetch_all(self, metric_name, start, end,
|
def fetch_all(self, metric_name, start, end,
|
||||||
|
|||||||
@@ -199,7 +199,9 @@ class MonascaCollector(collector.BaseCollector):
|
|||||||
qty = data['statistics'][0][1]
|
qty = data['statistics'][0][1]
|
||||||
converted_qty = ck_utils.convert_unit(
|
converted_qty = ck_utils.convert_unit(
|
||||||
qty, metconf['factor'], metconf['offset'])
|
qty, metconf['factor'], metconf['offset'])
|
||||||
mutated_qty = ck_utils.mutate(converted_qty, metconf['mutate'])
|
mutate_map = metconf.get('mutate_map')
|
||||||
|
mutated_qty = ck_utils.mutate(converted_qty, metconf['mutate'],
|
||||||
|
mutate_map=mutate_map)
|
||||||
return metadata, groupby, mutated_qty
|
return metadata, groupby, mutated_qty
|
||||||
|
|
||||||
def fetch_all(self, metric_name, start, end,
|
def fetch_all(self, metric_name, start, end,
|
||||||
|
|||||||
@@ -148,7 +148,9 @@ class PrometheusCollector(collector.BaseCollector):
|
|||||||
self.conf[metric_name]['factor'],
|
self.conf[metric_name]['factor'],
|
||||||
self.conf[metric_name]['offset'],
|
self.conf[metric_name]['offset'],
|
||||||
)
|
)
|
||||||
qty = ck_utils.mutate(qty, self.conf[metric_name]['mutate'])
|
mutate_map = self.conf[metric_name].get('mutate_map')
|
||||||
|
qty = ck_utils.mutate(qty, self.conf[metric_name]['mutate'],
|
||||||
|
mutate_map=mutate_map)
|
||||||
|
|
||||||
return metadata, groupby, qty
|
return metadata, groupby, qty
|
||||||
|
|
||||||
|
|||||||
@@ -188,3 +188,26 @@ class MetricConfigValidationTest(tests.TestCase):
|
|||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
collector.InvalidConfiguration,
|
collector.InvalidConfiguration,
|
||||||
collector.check_duplicates, metric_name, metric)
|
collector.check_duplicates, metric_name, metric)
|
||||||
|
|
||||||
|
def test_validate_map_mutator(self):
|
||||||
|
data = copy.deepcopy(self.base_data)
|
||||||
|
|
||||||
|
# Check that validation succeeds when MAP mutator is not used
|
||||||
|
for metric_name, metric in data['metrics'].items():
|
||||||
|
collector.validate_map_mutator(metric_name, metric)
|
||||||
|
|
||||||
|
# Check that validation raises an exception when mutate_map is missing
|
||||||
|
for metric_name, metric in data['metrics'].items():
|
||||||
|
metric['mutate'] = 'MAP'
|
||||||
|
self.assertRaises(
|
||||||
|
collector.InvalidConfiguration,
|
||||||
|
collector.validate_map_mutator, metric_name, metric)
|
||||||
|
|
||||||
|
data = copy.deepcopy(self.base_data)
|
||||||
|
# Check that validation raises an exception when mutate_map is present
|
||||||
|
# but MAP mutator is not used
|
||||||
|
for metric_name, metric in data['metrics'].items():
|
||||||
|
metric['mutate_map'] = {}
|
||||||
|
self.assertRaises(
|
||||||
|
collector.InvalidConfiguration,
|
||||||
|
collector.validate_map_mutator, metric_name, metric)
|
||||||
|
|||||||
@@ -251,8 +251,8 @@ def tempdir(**kwargs):
|
|||||||
LOG.debug('Could not remove tmpdir: %s', e)
|
LOG.debug('Could not remove tmpdir: %s', e)
|
||||||
|
|
||||||
|
|
||||||
def mutate(value, mode='NONE'):
|
def mutate(value, mode='NONE', mutate_map=None):
|
||||||
"""Mutate value according provided mode."""
|
"""Mutate value according to provided mode."""
|
||||||
|
|
||||||
if mode == 'NUMBOOL':
|
if mode == 'NUMBOOL':
|
||||||
return float(value != 0.0)
|
return float(value != 0.0)
|
||||||
@@ -266,6 +266,12 @@ def mutate(value, mode='NONE'):
|
|||||||
if mode == 'CEIL':
|
if mode == 'CEIL':
|
||||||
return math.ceil(value)
|
return math.ceil(value)
|
||||||
|
|
||||||
|
if mode == 'MAP':
|
||||||
|
ret = 0.0
|
||||||
|
if mutate_map is not None:
|
||||||
|
ret = mutate_map.get(value, 0.0)
|
||||||
|
return ret
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ Quantity mutation
|
|||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
It is also possible to mutate the collected qty with the ``mutate`` option.
|
It is also possible to mutate the collected qty with the ``mutate`` option.
|
||||||
Four values are accepted for this parameter:
|
Five values are accepted for this parameter:
|
||||||
|
|
||||||
* ``NONE``: This is the default. The collected data is not modifed.
|
* ``NONE``: This is the default. The collected data is not modifed.
|
||||||
|
|
||||||
@@ -190,6 +190,11 @@ Four values are accepted for this parameter:
|
|||||||
* ``NOTNUMBOOL``: If the collected qty equals 0, set it to 1. Else, set it to
|
* ``NOTNUMBOOL``: If the collected qty equals 0, set it to 1. Else, set it to
|
||||||
0.
|
0.
|
||||||
|
|
||||||
|
* ``MAP``: Map arbritrary values to new values as defined through the
|
||||||
|
``mutate_map`` option (dictionary). If the value is not found in
|
||||||
|
``mutate_map``, set it to 0. If ``mutate_map`` is not defined or is empty,
|
||||||
|
all values are set to 0.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
Quantity mutation is done **after** conversion. Example::
|
Quantity mutation is done **after** conversion. Example::
|
||||||
@@ -233,6 +238,26 @@ when the instance is in ACTIVE state but 4 if the instance is in ERROR state:
|
|||||||
metadata:
|
metadata:
|
||||||
- flavor_id
|
- flavor_id
|
||||||
|
|
||||||
|
The ``MAP`` mutator is useful when multiple statuses should be billabled. For
|
||||||
|
example, the following Prometheus metric has a value of 0 when the instance is
|
||||||
|
in ACTIVE state, but operators may want to rate other non-zero states:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
openstack_nova_server_status:
|
||||||
|
unit: instance
|
||||||
|
mutate: MAP
|
||||||
|
mutate_map:
|
||||||
|
0.0: 1.0 # ACTIVE
|
||||||
|
11.0: 1.0 # SHUTOFF
|
||||||
|
12.0: 1.0 # SUSPENDED
|
||||||
|
16.0: 1.0 # PAUSED
|
||||||
|
groupby:
|
||||||
|
- id
|
||||||
|
metadata:
|
||||||
|
- flavor_id
|
||||||
|
|
||||||
Display name
|
Display name
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
6
releasenotes/notes/map-mutator-632b8629c0482e94.yaml
Normal file
6
releasenotes/notes/map-mutator-632b8629c0482e94.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds a ``MAP`` mutator to map arbitrary values to new values. This is
|
||||||
|
useful with metrics reporting resource status as their value, but multiple
|
||||||
|
statuses are billable.
|
||||||
Reference in New Issue
Block a user