Skip to content

Commit c9340b3

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
1 parent 3ba19ed commit c9340b3

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
@@ -434,7 +434,9 @@ def _format_data(self, metconf, data, resources_info=None):
434434
qty = data['measures']['measures']['aggregated'][0][2]
435435
converted_qty = ck_utils.convert_unit(
436436
qty, metconf['factor'], metconf['offset'])
437-
mutated_qty = ck_utils.mutate(converted_qty, metconf['mutate'])
437+
mutate_map = metconf.get('mutate_map')
438+
mutated_qty = ck_utils.mutate(converted_qty, metconf['mutate'],
439+
mutate_map=mutate_map)
438440
return metadata, groupby, mutated_qty
439441

440442
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
@@ -188,3 +188,26 @@ def test_check_duplicates(self):
188188
self.assertRaises(
189189
collector.InvalidConfiguration,
190190
collector.check_duplicates, metric_name, metric)
191+
192+
def test_validate_map_mutator(self):
193+
data = copy.deepcopy(self.base_data)
194+
195+
# Check that validation succeeds when MAP mutator is not used
196+
for metric_name, metric in data['metrics'].items():
197+
collector.validate_map_mutator(metric_name, metric)
198+
199+
# Check that validation raises an exception when mutate_map is missing
200+
for metric_name, metric in data['metrics'].items():
201+
metric['mutate'] = 'MAP'
202+
self.assertRaises(
203+
collector.InvalidConfiguration,
204+
collector.validate_map_mutator, metric_name, metric)
205+
206+
data = copy.deepcopy(self.base_data)
207+
# Check that validation raises an exception when mutate_map is present
208+
# but MAP mutator is not used
209+
for metric_name, metric in data['metrics'].items():
210+
metric['mutate_map'] = {}
211+
self.assertRaises(
212+
collector.InvalidConfiguration,
213+
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
@@ -177,7 +177,7 @@ Quantity mutation
177177
~~~~~~~~~~~~~~~~~
178178

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

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

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

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

195200
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:
233238
metadata:
234239
- flavor_id
235240
241+
The ``MAP`` mutator is useful when multiple statuses should be billabled. For
242+
example, the following Prometheus metric has a value of 0 when the instance is
243+
in ACTIVE state, but operators may want to rate other non-zero states:
244+
245+
.. code-block:: yaml
246+
247+
metrics:
248+
openstack_nova_server_status:
249+
unit: instance
250+
mutate: MAP
251+
mutate_map:
252+
0.0: 1.0 # ACTIVE
253+
11.0: 1.0 # SHUTOFF
254+
12.0: 1.0 # SUSPENDED
255+
16.0: 1.0 # PAUSED
256+
groupby:
257+
- id
258+
metadata:
259+
- flavor_id
260+
236261
Display name
237262
~~~~~~~~~~~~
238263

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)