Skip to content

Commit 768bcfd

Browse files
committed
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 (cherry picked from commit c9340b3)
1 parent f8726ee commit 768bcfd

8 files changed

Lines changed: 92 additions & 7 deletions

File tree

cloudkitty/collector/__init__.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ def MetricDict(value):
9393
# (NONE, NUMBOOL, NOTNUMBOOL, FLOOR, CEIL).
9494
# Defaults to NONE
9595
Required('mutate', default='NONE'):
96-
In(['NONE', 'NUMBOOL', 'NOTNUMBOOL', 'FLOOR', 'CEIL']),
96+
In(['NONE', 'NUMBOOL', 'NOTNUMBOOL', 'FLOOR', 'CEIL', 'MAP']),
97+
# Map dict used if mutate == 'MAP'
98+
Optional('mutate_map'): dict,
9799
# Collector-specific args. Should be overriden by schema provided for
98100
# the given collector
99101
Optional('extra_args'): dict,
@@ -270,6 +272,22 @@ def check_duplicates(metric_name, metric):
270272
return metric
271273

272274

275+
def validate_map_mutator(metric_name, metric):
276+
"""Validates MAP mutator"""
277+
mutate = metric.get('mutate')
278+
mutate_map = metric.get('mutate_map')
279+
280+
if mutate == 'MAP' and mutate_map is None:
281+
raise InvalidConfiguration(
282+
'Metric {} uses MAP mutator but mutate_map is missing: {}'.format(
283+
metric_name, metric))
284+
285+
if mutate != 'MAP' and mutate_map is not None:
286+
raise InvalidConfiguration(
287+
'Metric {} not using MAP mutator but mutate_map is present: '
288+
'{}'.format(metric_name, metric))
289+
290+
273291
def validate_conf(conf):
274292
"""Validates the provided configuration."""
275293
collector = get_collector_without_invoke()
@@ -278,4 +296,5 @@ def validate_conf(conf):
278296
if 'alt_name' not in metric.keys():
279297
metric['alt_name'] = metric_name
280298
check_duplicates(metric_name, metric)
299+
validate_map_mutator(metric_name, metric)
281300
return output

cloudkitty/collector/gnocchi.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,9 @@ def _format_data(self, metconf, data, resources_info=None):
396396
qty = data['measures']['measures']['aggregated'][0][2]
397397
converted_qty = ck_utils.convert_unit(
398398
qty, metconf['factor'], metconf['offset'])
399-
mutated_qty = ck_utils.mutate(converted_qty, metconf['mutate'])
399+
mutate_map = metconf.get('mutate_map')
400+
mutated_qty = ck_utils.mutate(converted_qty, metconf['mutate'],
401+
mutate_map=mutate_map)
400402
return metadata, groupby, mutated_qty
401403

402404
def fetch_all(self, metric_name, start, end,

cloudkitty/collector/monasca.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,9 @@ def _format_data(self, metconf, data, resources_info=None):
199199
qty = data['statistics'][0][1]
200200
converted_qty = ck_utils.convert_unit(
201201
qty, metconf['factor'], metconf['offset'])
202-
mutated_qty = ck_utils.mutate(converted_qty, metconf['mutate'])
202+
mutate_map = metconf.get('mutate_map')
203+
mutated_qty = ck_utils.mutate(converted_qty, metconf['mutate'],
204+
mutate_map=mutate_map)
203205
return metadata, groupby, mutated_qty
204206

205207
def fetch_all(self, metric_name, start, end,

cloudkitty/collector/prometheus.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,9 @@ def _format_data(self, metric_name, scope_key, scope_id, start, end, data):
148148
self.conf[metric_name]['factor'],
149149
self.conf[metric_name]['offset'],
150150
)
151-
qty = ck_utils.mutate(qty, self.conf[metric_name]['mutate'])
151+
mutate_map = self.conf[metric_name].get('mutate_map')
152+
qty = ck_utils.mutate(qty, self.conf[metric_name]['mutate'],
153+
mutate_map=mutate_map)
152154

153155
return metadata, groupby, qty
154156

cloudkitty/tests/collectors/test_validation.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,26 @@ def test_check_duplicates(self):
184184
self.assertRaises(
185185
collector.InvalidConfiguration,
186186
collector.check_duplicates, metric_name, metric)
187+
188+
def test_validate_map_mutator(self):
189+
data = copy.deepcopy(self.base_data)
190+
191+
# Check that validation succeeds when MAP mutator is not used
192+
for metric_name, metric in data['metrics'].items():
193+
collector.validate_map_mutator(metric_name, metric)
194+
195+
# Check that validation raises an exception when mutate_map is missing
196+
for metric_name, metric in data['metrics'].items():
197+
metric['mutate'] = 'MAP'
198+
self.assertRaises(
199+
collector.InvalidConfiguration,
200+
collector.validate_map_mutator, metric_name, metric)
201+
202+
data = copy.deepcopy(self.base_data)
203+
# Check that validation raises an exception when mutate_map is present
204+
# but MAP mutator is not used
205+
for metric_name, metric in data['metrics'].items():
206+
metric['mutate_map'] = {}
207+
self.assertRaises(
208+
collector.InvalidConfiguration,
209+
collector.validate_map_mutator, metric_name, metric)

cloudkitty/utils/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,8 @@ def tempdir(**kwargs):
251251
LOG.debug('Could not remove tmpdir: %s', e)
252252

253253

254-
def mutate(value, mode='NONE'):
255-
"""Mutate value according provided mode."""
254+
def mutate(value, mode='NONE', mutate_map=None):
255+
"""Mutate value according to provided mode."""
256256

257257
if mode == 'NUMBOOL':
258258
return float(value != 0.0)
@@ -266,6 +266,12 @@ def mutate(value, mode='NONE'):
266266
if mode == 'CEIL':
267267
return math.ceil(value)
268268

269+
if mode == 'MAP':
270+
ret = 0.0
271+
if mutate_map is not None:
272+
ret = mutate_map.get(value, 0.0)
273+
return ret
274+
269275
return value
270276

271277

doc/source/admin/configuration/collector.rst

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ Quantity mutation
176176
~~~~~~~~~~~~~~~~~
177177

178178
It is also possible to mutate the collected qty with the ``mutate`` option.
179-
Four values are accepted for this parameter:
179+
Five values are accepted for this parameter:
180180

181181
* ``NONE``: This is the default. The collected data is not modifed.
182182

@@ -189,6 +189,11 @@ Four values are accepted for this parameter:
189189
* ``NOTNUMBOOL``: If the collected qty equals 0, set it to 1. Else, set it to
190190
0.
191191

192+
* ``MAP``: Map arbritrary values to new values as defined through the
193+
``mutate_map`` option (dictionary). If the value is not found in
194+
``mutate_map``, set it to 0. If ``mutate_map`` is not defined or is empty,
195+
all values are set to 0.
196+
192197
.. warning::
193198

194199
Quantity mutation is done **after** conversion. Example::
@@ -232,6 +237,26 @@ when the instance is in ACTIVE state but 4 if the instance is in ERROR state:
232237
metadata:
233238
- flavor_id
234239
240+
The ``MAP`` mutator is useful when multiple statuses should be billabled. For
241+
example, the following Prometheus metric has a value of 0 when the instance is
242+
in ACTIVE state, but operators may want to rate other non-zero states:
243+
244+
.. code-block:: yaml
245+
246+
metrics:
247+
openstack_nova_server_status:
248+
unit: instance
249+
mutate: MAP
250+
mutate_map:
251+
0.0: 1.0 # ACTIVE
252+
11.0: 1.0 # SHUTOFF
253+
12.0: 1.0 # SUSPENDED
254+
16.0: 1.0 # PAUSED
255+
groupby:
256+
- id
257+
metadata:
258+
- flavor_id
259+
235260
Display name
236261
~~~~~~~~~~~~
237262

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
features:
3+
- |
4+
Adds a ``MAP`` mutator to map arbitrary values to new values. This is
5+
useful with metrics reporting resource status as their value, but multiple
6+
statuses are billable.

0 commit comments

Comments
 (0)