distil/artifice/transformers.py
Chris Forbes fcd472c647 add clipping window to transformer::transform_usage
Change-Id: I6edc93646bd8f6f24e62dae67bdb0d52bf2210e4
2014-04-01 17:24:31 +13:00

153 lines
4.8 KiB
Python

import datetime
import constants
import helpers
class TransformerValidationError(Exception):
pass
class Transformer(object):
meter_type = None
required_meters = []
def transform_usage(self, meters, start, end):
self.validate_meters(meters)
return self._transform_usage(meters, start, end)
def validate_meters(self, meters):
if self.meter_type is None:
for meter in self.required_meters:
if meter not in meters:
raise TransformerValidationError(
"Required meters: " +
str(self.required_meters))
else:
for meter in meters.values():
if meter.type != self.meter_type:
raise TransformerValidationError(
"Meters must all be of type: " +
self.meter_type)
def _transform_usage(self, meters, start, end):
raise NotImplementedError
class Uptime(Transformer):
"""
Transformer to calculate uptime based on states,
which is broken apart into flavor at point in time.
"""
required_meters = ['state', 'flavor']
def _transform_usage(self, meters, start, end):
# this NEEDS to be moved to a config file
tracked_states = [constants.active, constants.building,
constants.paused, constants.rescued,
constants.resized]
usage_dict = {}
state = meters['state']
flavor = meters['flavor']
state = sorted(state.usage(), key=lambda x: x["timestamp"])
flavor = sorted(flavor.usage(), key=lambda x: x["timestamp"])
if not len(state) or not len(flavor):
# there was no data for this period.
return usage_dict
last_state = state[0]
self.parse_timestamp(last_state)
last_flavor = flavor[0]
self.parse_timestamp(last_flavor)
count = 1
for val in state[1:]:
self.parse_timestamp(val)
if last_state["counter_volume"] in tracked_states:
diff = val["timestamp"] - last_state["timestamp"]
try:
flav = helpers.flavor_name(last_flavor['counter_volume'])
usage_dict[flav] = usage_dict[flav] + diff.seconds
except KeyError:
usage_dict[flav] = diff.seconds
last_state = val
try:
new_flavor = flavor[count]
self.parse_timestamp(new_flavor)
if new_flavor["timestamp"] <= last_state["timestamp"]:
count += 1
last_flavor = new_flavor
except IndexError:
# means this is the last flavor value, so no need to worry
# about new_flavor or count
pass
return usage_dict
def parse_timestamp(self, entry):
try:
entry["timestamp"] = datetime.datetime.\
strptime(entry["timestamp"],
constants.date_format)
except ValueError:
entry["timestamp"] = datetime.datetime.\
strptime(entry["timestamp"],
constants.other_date_format)
except TypeError:
pass
class GaugeMax(Transformer):
"""
Transformer that simply returns the highest value
in the given range.
"""
meter_type = 'gauge'
def _transform_usage(self, meters, start, end):
usage_dict = {}
for name, meter in meters.iteritems():
usage = meter.usage()
max_vol = max([v["counter_volume"] for v in usage])
usage_dict[name] = max_vol
return usage_dict
class CumulativeRange(Transformer):
"""
Transformer to get the usage over a given range in a cumulative
metric, while taking into account that the metric can reset.
"""
meter_type = 'cumulative'
def _transform_usage(self, meters, start, end):
usage_dict = {}
for name, meter in meters.iteritems():
measurements = meter.usage()
measurements = sorted(measurements, key=lambda x: x["timestamp"])
count = 0
usage = 0
last_measure = None
for measure in measurements:
if (last_measure is not None and
(measure["counter_volume"] <
last_measure["counter_volume"])):
usage = usage + last_measure["counter_volume"]
count = count + 1
last_measure = measure
usage = usage + measurements[-1]["counter_volume"]
if count > 1:
total_usage = usage - measurements[0]["counter_volume"]
usage_dict[name] = total_usage
return usage_dict