diff --git a/collectd_ceilometer/meters/base.py b/collectd_ceilometer/meters/base.py index 3986d94..b9e953b 100644 --- a/collectd_ceilometer/meters/base.py +++ b/collectd_ceilometer/meters/base.py @@ -16,6 +16,9 @@ from __future__ import unicode_literals from collectd_ceilometer.settings import Config +import logging + +LOGGER = logging.getLogger(__name__) class Meter(object): @@ -45,3 +48,21 @@ class Meter(object): """Get meter unit""" # pylint: disable=no-self-use return Config.instance().unit(vl.plugin, vl.type) + + def sample_type(self, vl): + """Translate from collectd counter type to Ceilometer type""" + types = {"gauge": "gauge", + "derive": "delta", + "absolute": "cumulative", + "counter": "cumulative"} + + try: + # get_dataset -> [('value', 'derive', 0.0, None)] + collectd_type = self._collectd.get_dataset(str(vl.type))[0][1] + except Exception: + LOGGER.warning( + "Cannot map counter type '%s': using type 'gauge'.", vl.type, + exc_info=1) + collectd_type = "gauge" + + return types[collectd_type] diff --git a/collectd_ceilometer/tests/test_meters_base.py b/collectd_ceilometer/tests/test_meters_base.py new file mode 100644 index 0000000..9516968 --- /dev/null +++ b/collectd_ceilometer/tests/test_meters_base.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +# Copyright 2010-2011 OpenStack Foundation +# Copyright (c) 2015 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Plugin tests""" + +from __future__ import unicode_literals + +from collectd_ceilometer.meters.base import Meter +from collectd_ceilometer.tests.base import TestCase +import mock + + +class Values(object): + """Stub class to replace collectd.Values""" + def __init__(self, plugin="my_plugin", type="my_type"): + self.plugin = plugin + self.type = type + + +class CollectdMock(object): + """Model for the collectd class to be mocked""" + def get_dataset(self, string): + pass + +collectd_class = 'collectd_ceilometer.tests.test_meters_base.CollectdMock' + + +class MetersTest(TestCase): + """Test the meters/base.py class""" + + @mock.patch(collectd_class, spec=True) + def setUp(self, collectd): + super(MetersTest, self).setUp() + self._collectd = collectd + # need this as a parameter for sample_type() + self.vl = Values() + self.meter = Meter(self._collectd) + + def test_sample_type_gauge(self): + # sample_type uses get_dataset()[0][1] + self._collectd.get_dataset.return_value = [('value', 'gauge', )] + + actual = self.meter.sample_type(self.vl) + + self._collectd.get_dataset.assert_called_once() + self.assertEqual("gauge", actual) + + def test_sample_type_derive(self): + # sample_type uses get_dataset()[0][1] + self._collectd.get_dataset.return_value = [('value', 'derive', )] + + actual = self.meter.sample_type(self.vl) + + self._collectd.get_dataset.assert_called_once() + self.assertEqual("delta", actual) + + def test_sample_type_absolute(self): + # sample_type uses get_dataset()[0][1] + self._collectd.get_dataset.return_value = [('value', 'absolute', )] + + actual = self.meter.sample_type(self.vl) + + self._collectd.get_dataset.assert_called_once() + self.assertEqual("cumulative", actual) + + def test_sample_type_counter(self): + # sample_type uses get_dataset()[0][1] + self._collectd.get_dataset.return_value = [('value', 'counter', )] + + actual = self.meter.sample_type(self.vl) + + self._collectd.get_dataset.assert_called_once() + self.assertEqual("cumulative", actual) + + @mock.patch('collectd_ceilometer.meters.base.LOGGER') + def test_sample_type_invalid(self, LOGGER): + self._collectd.get_dataset.side_effect = Exception("Boom!") + + actual = self.meter.sample_type(self.vl) + + self._collectd.get_dataset.assert_called_once() + LOGGER.warning.assert_called_once() + self.assertEqual("gauge", actual) diff --git a/collectd_ceilometer/writer.py b/collectd_ceilometer/writer.py index b2ab2e6..87d2b9e 100644 --- a/collectd_ceilometer/writer.py +++ b/collectd_ceilometer/writer.py @@ -29,14 +29,15 @@ LOGGER = logging.getLogger(__name__) class Sample(namedtuple('Sample', ['value', 'timestamp', 'meta', - 'resource_id', 'unit', 'metername'])): + 'resource_id', 'unit', + 'metername', 'sample_type'])): """Sample data""" def to_payload(self): """Return a payload dictionary""" return { 'counter_name': self.metername, - 'counter_type': 'gauge', + 'counter_type': self.sample_type, 'counter_unit': self.unit, 'counter_volume': self.value, 'timestamp': self.timestamp, @@ -106,16 +107,18 @@ class Writer(object): metername = plugin.meter_name(vl) unit = plugin.unit(vl) timestamp = time.asctime(time.gmtime(vl.time)) + sample_type = plugin.sample_type(vl) LOGGER.debug( - 'Writing: plugin="%s", metername="%s", unit="%s"', - vl.plugin, metername, unit) + 'Writing: plugin="%s", metername="%s", unit="%s", type="%s"', + vl.plugin, metername, unit, sample_type) # store sample for every value data = [ Sample( value=value, timestamp=timestamp, meta=vl.meta, - resource_id=resource_id, unit=unit, metername=metername) + resource_id=resource_id, unit=unit, + metername=metername, sample_type=sample_type) for value in vl.values ]