Skip to content

Commit 3d0b32c

Browse files
authored
Merge pull request #23 from stackhpc/map-mutator-wallaby
Add MAP mutator
2 parents f8726ee + 768bcfd commit 3d0b32c

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)