~ubuntu-branches/ubuntu/saucy/ceilometer/saucy

« back to all changes in this revision

Viewing changes to ceilometer/transformer/conversions.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Yolanda Robla, Chuck Short, James Page
  • Date: 2013-07-19 10:04:36 UTC
  • mfrom: (1.1.5)
  • Revision ID: package-import@ubuntu.com-20130719100436-8ppgb9laoyr60zyr
Tags: 2013.2~b2-0ubuntu1
[ Yolanda Robla ]
* debian/patches/default-dbconnection-sqlite.patch: updated db section

[ Chuck Short ]
* New upstream version.
* debian/patches/default-dbconnection-sqlite.patch: Refreshed.
* debian/control: Bump requirements for stevedore to 0.9.
* debian/control: Add python-simplejson
* debian/control: Drop python-keystoneclient hardcoded version.
* debian/control: Add python-testscenarios as a build depends.
* debian/control: Add python-cinderclient as a build depends.
* debian/control: Add python-ceilometerclient as a build depends.
* debian/control: Add python-alembic as build depends.
* debian/control: Add python-oslo.sphinx as build-depends.
* debian/control: Update runtime depends.
* debian/control: Add python-happybase.
* debian/ceilometer-common.install: Add ceilometer-alarm-singleton,
  ceilometer-alarm-notifier, and ceilometer-expirer.
* debian/patches/skip-database-tests.patch: Skip database tests
  if the database is not installed.

[ James Page ]
* d/control: Update VCS fields for new branch locations.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: utf-8 -*-
 
2
#
 
3
# Copyright © 2013 Red Hat, Inc
 
4
#
 
5
# Author: Eoghan Glynn <eglynn@redhat.com>
 
6
#
 
7
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 
8
# not use this file except in compliance with the License. You may obtain
 
9
# a copy of the License at
 
10
#
 
11
#      http://www.apache.org/licenses/LICENSE-2.0
 
12
#
 
13
# Unless required by applicable law or agreed to in writing, software
 
14
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
15
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
16
# License for the specific language governing permissions and limitations
 
17
# under the License.
 
18
 
 
19
from collections import defaultdict
 
20
 
 
21
from ceilometer import counter as ceilocounter
 
22
from ceilometer.openstack.common import log
 
23
from ceilometer.openstack.common import timeutils
 
24
from ceilometer import transformer
 
25
 
 
26
LOG = log.getLogger(__name__)
 
27
 
 
28
 
 
29
class Namespace(object):
 
30
    """Encapsulates the namespace wrapping the evaluation of the
 
31
       configured scale factor. This allows nested dicts to be
 
32
       accessed in the attribute style, and missing attributes
 
33
       to yield false when used in a boolean expression.
 
34
    """
 
35
    def __init__(self, seed):
 
36
        self.__dict__ = defaultdict(lambda: Namespace({}))
 
37
        self.__dict__.update(seed)
 
38
        for k, v in self.__dict__.iteritems():
 
39
            if isinstance(v, dict):
 
40
                self.__dict__[k] = Namespace(v)
 
41
 
 
42
    def __getattr__(self, attr):
 
43
        return self.__dict__[attr]
 
44
 
 
45
    def __getitem__(self, key):
 
46
        return self.__dict__[key]
 
47
 
 
48
    def __nonzero__(self):
 
49
        return len(self.__dict__) > 0
 
50
 
 
51
 
 
52
class ScalingTransformer(transformer.TransformerBase):
 
53
    """Transformer to apply a scaling conversion.
 
54
    """
 
55
 
 
56
    def __init__(self, source={}, target={}, replace=False, **kwargs):
 
57
        """Initialize transformer with configured parameters.
 
58
 
 
59
        :param source: dict containing source counter unit
 
60
        :param target: dict containing target counter name, type,
 
61
                       unit and scaling factor (a missing value
 
62
                       connotes no change)
 
63
        :param replace: true if source counter is to be replaced
 
64
                        (as opposed to an additive conversion)
 
65
        """
 
66
        self.source = source
 
67
        self.target = target
 
68
        self.replace = replace
 
69
        self.preserved = None
 
70
        LOG.debug(_('scaling conversion transformer with source:'
 
71
                    ' %(source)s target: %(target)s replace:'
 
72
                    ' %(replace)s') % locals())
 
73
        super(ScalingTransformer, self).__init__(**kwargs)
 
74
 
 
75
    @staticmethod
 
76
    def _scale(counter, scale):
 
77
        """Apply the scaling factor (either a straight multiplicative
 
78
           factor or else a string to be eval'd).
 
79
        """
 
80
        ns = Namespace(counter._asdict())
 
81
 
 
82
        return ((eval(scale, {}, ns) if isinstance(scale, basestring)
 
83
                 else counter.volume * scale) if scale else counter.volume)
 
84
 
 
85
    def _convert(self, counter, growth=1):
 
86
        """Transform the appropriate counter fields.
 
87
        """
 
88
        scale = self.target.get('scale')
 
89
        return ceilocounter.Counter(
 
90
            name=self.target.get('name', counter.name),
 
91
            unit=self.target.get('unit', counter.unit),
 
92
            type=self.target.get('type', counter.type),
 
93
            volume=self._scale(counter, scale) * growth,
 
94
            user_id=counter.user_id,
 
95
            project_id=counter.project_id,
 
96
            resource_id=counter.resource_id,
 
97
            timestamp=counter.timestamp,
 
98
            resource_metadata=counter.resource_metadata
 
99
        )
 
100
 
 
101
    def _keep(self, counter, transformed):
 
102
        """Either replace counter with the transformed version
 
103
           or preserve for flush() call to emit as an additional
 
104
           sample.
 
105
        """
 
106
        if self.replace:
 
107
            counter = transformed
 
108
        else:
 
109
            self.preserved = transformed
 
110
        return counter
 
111
 
 
112
    def handle_sample(self, context, counter, source):
 
113
        """Handle a sample, converting if necessary."""
 
114
        LOG.debug('handling counter %s', (counter,))
 
115
        if (self.source.get('unit', counter.unit) == counter.unit):
 
116
            transformed = self._convert(counter)
 
117
            LOG.debug(_('converted to: %s') % (transformed,))
 
118
            counter = self._keep(counter, transformed)
 
119
        return counter
 
120
 
 
121
    def flush(self, context, source):
 
122
        """Emit the additional transformed counter in the non-replace
 
123
           case.
 
124
        """
 
125
        counters = []
 
126
        if self.preserved:
 
127
            counters.append(self.preserved)
 
128
            self.preserved = None
 
129
        return counters
 
130
 
 
131
 
 
132
class RateOfChangeTransformer(ScalingTransformer):
 
133
    """Transformer based on the rate of change of a counter volume,
 
134
       for example taking the current and previous volumes of a
 
135
       cumulative counter and producing a gauge value based on the
 
136
       proportion of some maximum used.
 
137
    """
 
138
 
 
139
    def __init__(self, **kwargs):
 
140
        """Initialize transformer with configured parameters.
 
141
        """
 
142
        self.cache = {}
 
143
        super(RateOfChangeTransformer, self).__init__(**kwargs)
 
144
 
 
145
    def handle_sample(self, context, counter, source):
 
146
        """Handle a sample, converting if necessary."""
 
147
        LOG.debug('handling counter %s', (counter,))
 
148
        key = counter.name + counter.resource_id
 
149
        prev = self.cache.get(key)
 
150
        timestamp = timeutils.parse_isotime(counter.timestamp)
 
151
        self.cache[key] = (counter.volume, timestamp)
 
152
 
 
153
        if prev:
 
154
            prev_volume = prev[0]
 
155
            prev_timestamp = prev[1]
 
156
            time_delta = timeutils.delta_seconds(prev_timestamp, timestamp)
 
157
            # we only allow negative deltas for noncumulative counters, whereas
 
158
            # for cumulative we assume that a reset has occurred in the interim
 
159
            # so that the current volume gives a lower bound on growth
 
160
            volume_delta = (counter.volume - prev_volume
 
161
                            if (prev_volume <= counter.volume or
 
162
                                counter.type != ceilocounter.TYPE_CUMULATIVE)
 
163
                            else counter.volume)
 
164
            rate_of_change = ((1.0 * volume_delta / time_delta)
 
165
                              if time_delta else 0.0)
 
166
 
 
167
            transformed = self._convert(counter, rate_of_change)
 
168
            LOG.debug(_('converted to: %s') % (transformed,))
 
169
            counter = self._keep(counter, transformed)
 
170
        elif self.replace:
 
171
            LOG.warn(_('dropping counter with no predecessor: %s') % counter)
 
172
            counter = None
 
173
        return counter