Add counter type mappings

- Add mappings between counter type in collectd and Ceilometer.
  Previously, all counters were given "gauge" type.
  Now, the following mappings are applied:
    - gauge -> gauge
    - derive -> delta
    - absolute, counter -> cumulative
- Add unit tests

Closes-Bug: #1583301
Change-Id: I13e75fcc57ed2b44c2b9a02ee9562f70748d01c2
This commit is contained in:
Emma Foley 2016-05-30 16:05:08 +01:00
parent 77a2d9b965
commit f3e73866bd
3 changed files with 126 additions and 5 deletions

View File

@ -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]

View File

@ -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)

View File

@ -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
]