1
# -*- encoding: utf-8 -*-
3
# Copyright © 2013 Red Hat, Inc
5
# Author: Eoghan Glynn <eglynn@redhat.com>
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
11
# http://www.apache.org/licenses/LICENSE-2.0
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
19
from collections import defaultdict
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
26
LOG = log.getLogger(__name__)
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.
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)
42
def __getattr__(self, attr):
43
return self.__dict__[attr]
45
def __getitem__(self, key):
46
return self.__dict__[key]
48
def __nonzero__(self):
49
return len(self.__dict__) > 0
52
class ScalingTransformer(transformer.TransformerBase):
53
"""Transformer to apply a scaling conversion.
56
def __init__(self, source={}, target={}, replace=False, **kwargs):
57
"""Initialize transformer with configured parameters.
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
63
:param replace: true if source counter is to be replaced
64
(as opposed to an additive conversion)
68
self.replace = replace
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)
76
def _scale(counter, scale):
77
"""Apply the scaling factor (either a straight multiplicative
78
factor or else a string to be eval'd).
80
ns = Namespace(counter._asdict())
82
return ((eval(scale, {}, ns) if isinstance(scale, basestring)
83
else counter.volume * scale) if scale else counter.volume)
85
def _convert(self, counter, growth=1):
86
"""Transform the appropriate counter fields.
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
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
107
counter = transformed
109
self.preserved = transformed
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)
121
def flush(self, context, source):
122
"""Emit the additional transformed counter in the non-replace
127
counters.append(self.preserved)
128
self.preserved = None
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.
139
def __init__(self, **kwargs):
140
"""Initialize transformer with configured parameters.
143
super(RateOfChangeTransformer, self).__init__(**kwargs)
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)
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)
164
rate_of_change = ((1.0 * volume_delta / time_delta)
165
if time_delta else 0.0)
167
transformed = self._convert(counter, rate_of_change)
168
LOG.debug(_('converted to: %s') % (transformed,))
169
counter = self._keep(counter, transformed)
171
LOG.warn(_('dropping counter with no predecessor: %s') % counter)