# $SnapHashLicense:
#
# SnapLogic - Open source data services
#
# Copyright (C) 2009, SnapLogic, Inc. All rights reserved.
#
# See http://www.snaplogic.org for more information about
# the SnapLogic project.
#
# This program is free software, distributed under the terms of
# the GNU General Public License Version 2. See the LEGAL file
# at the top of the source tree.
#
# "SnapLogic" is a trademark of SnapLogic, Inc.
#
# $
#
# $Id: snap_stats.py 7363 2009-04-24 17:35:55Z pamor $
from snaplogic.common.snap_exceptions import *
"""
The SnapLogic stats module.
"""
import time
from snaplogic.cc.prop import SimpleProp
enforce_singleton = True
_instance = None
# The possible types for statistics
_STAT_TYPE_COUNTER = 1
_STAT_TYPE_FIELD = 2
# The known types that we support for stats
_known_types = [ 'number', 'boolean', 'string' ]
def get_instance():
"""
Return the single instance of the stats object in this process.
If no instance exists yet then it returns None.
"""
return _instance
class SnapStatObject(object):
"""
A class to store the definition, value and some
additional information for an individual stat.
Specifically, we store the SimpleProp of the stat, the current
value, the type of stat and the permissable data types for the
value.
There can be more than one permissable data type. For example,
while the SimpleProp may say only "number", the permissable types
are 'int' and 'float'. We start out with an int, but if a float
value is assigned or added to the counter then we will cast to
a float. For example.
"""
def __init__(self, prop, stat_type, permissable_types, initial_value):
"""
Initialize stats object.
@param prop: The SimpleProp that defines the stat.
@type prop: L{SimpleProp}
@param stat_type: The type of stat (counter or field) indicated
by _STAT_TYPE_COUNTER or _STAT_TYPE_FIELD.
@type stat_type: integer
@param permissable_types: A list of permissable types for this stat.
@type permissable_types: list
@param initial_value: The initial value of the statistic.
@type initial_value: int, float, str or bool
"""
self._prop = prop
self._stat_type = stat_type
self._permissable_types = permissable_types
self._value = initial_value
def set(self, value):
"""
Set the stat to a specific value.
@param value: The value to which the stat should be set.
@type value: int, float, string or bool
"""
if type(value) not in self._permissable_types:
raise SnapObjTypeError("Trying to set stat to type '%s', but only types '%s' are allowed." % (type(value), self._permissable_types))
self._value = value
def get(self):
"""
Return the current value of the stat.
@return: The current stat value.
@rtype: int, float, string or bool
"""
return self._value
def __repr__(self):
return str(self.get())
class SnapStatCounterObject(SnapStatObject):
"""
Specialization of the stat object for counters.
Counters also have functions for increasing and decreasing
of the stat value.
"""
def inc(self, inc_value=1):
"""
Increase the counter value by the specified amount.
If no increase-value is specified, the counter will be
increased by one.
@param inc_value: The amount by which the counter should
be increased. Default: 1.
@type inc_value: int or float
"""
if type(inc_value) not in self._permissable_types:
raise SnapObjTypeError("Trying to increment counter with type '%s', but only types '%s' are allowed." % (type(inc_value), self._permissable_types))
self._value += inc_value
def dec(self, dec_value=1):
"""
Decrease the counter value by the specified amount.
If no decrease-value is specified, the counter will be
decreased by one.
@param dec_value: The amount by which the counter should
be decreased. Default: 1.
@type dec_value: int or float
"""
if type(dec_value) not in self._permissable_types:
raise SnapObjTypeError("Trying to decrement counter with type '%s', but only types '%s' are allowed." % (type(dec_value), self._permissable_types))
self._value -= dec_value
class SnapStatsGroup(object):
"""
A module specific group of stats.
Each module in the system can create a SnapStatsGroup object and
add its own particular stats to it. The group object is registered
with the SnapStats singleton. New stats may be added or old ones
removed even after it has been registered.
After registration, the module can forget about SnapStats. It only
needs a handle on its SnapStatsGroup object, which it then uses
to update the various stats, add or remove them.
"""
def __init__(self, name):
"""
Create a new stats-group object with the specified name.
The name could be a module identifier. It is visible to the user
that retrieves stats from the system. The stats in this stats
group will be listed under the name of the stats group that is
specified here.
@param name: The name of the stats group. Most commonly, this
should be a module identifier. For example
"Repository", which will appear as 'headline' of
the repository-related stats when the stats are
retrieved and displayed.
@type name: string
"""
self.name = name
self._stats = {}
def _add_stat(self, prop, stat_type, start_value=None):
"""
Add a new statistic to the stats group.
This is an internal utility function, which is called by more
specific public equivalents.
@param prop: A SimpleProp definition of a new statistic.
@type prop: L{SimpleProp}
@param stat_type: The type of stat, either a counter or a field.
@type stat_type: Integer, either _STAT_TYPE_COUNTER or
_STAT_TYPE_FIELD as value.
@param start_value: A data item that represent the initial value
of the stat. This data item has to have an acceptable
type, as implied by the simple_type field of SimpleProp.
Specifically, for "number" it can be 'int' or 'float',
for "boolean" it has to be 'bool' and for "string" it
has to be 'str'. A "datetime" type is not currently
allowed.
@type start_value: int, float, str or bool
@return: Reference to the newly created stats object.
@rtype: L{SnapStatObject}
"""
# A given name can only exist once in a stat group
if prop.label_str in self._stats:
raise SnapObjExistsError("A statistic with the name '%s' already exists in the stats-group '%s'." % (prop.label_str, self.name))
# Determine the allowable types and possible default values, based on the
# type definition in the SimpleProp
if prop.simple_type == "number":
permissable_types = [ int, float ]
default = 0
elif prop.simple_type == "boolean":
permissable_types = [ bool ]
default = False
elif prop.simple_type == "string":
permissable_types = [ str ]
default = ""
else:
raise SnapObjTypeError("Type '%s' not allowed for SimpleProps used as stats." % (prop.simple_type))
# If a start-value for the stat was specified, check that the type
# of the start-value and the type defined in the SimpleProp agree.
if start_value:
if type(start_value) not in permissable_types:
raise SnapObjTypeError("You specified a default value with type '%s', but only types '%s' are allowed." % (type(start_value), permissable_types))
else:
start_value = default
# Create and store the stats object
if stat_type == _STAT_TYPE_COUNTER:
self._stats[prop.label_str] = SnapStatCounterObject(prop, stat_type, permissable_types, start_value)
else:
self._stats[prop.label_str] = SnapStatObject(prop, stat_type, permissable_types, start_value)
return self._stats[prop.label_str]
def add_counter_stat_with_prop(self, prop, start_value=None):
"""
Add a new counter statistic to the stats group.
A statistic is defined via a SimpleProp, which for a counter
statistic needs to be of type "number".
This function needs a user-defined SimpleProp definition. If
the calling program doesn't want to bother with the SimpleProps,
it can use add_counter_stat() as a
convenience function.
Counters can be increased and decreased, and also set.
@param prop: The SimpleProp definition of the statistic.
@type prop: L{SimpleProp}
@param start_value: The initial value of the counter.
Can be left blank in which case the
counter is initialized to 0.
@type start_value int or float
@return: Reference to the newly created stats object.
@rtype: L{SnapStatCounterObject}
"""
if prop.simple_type != "number":
raise SnapObjTypeError("A counter statistic requires a SimpleProp of type 'number'. Instead '%s' was specified as type." % prop.simple_type)
return self._add_stat(prop, _STAT_TYPE_COUNTER, start_value)
def add_counter_stat(self, name, start_value=None):
"""
Add a new counter statistic to the stats group.
A statistic is defined via a SimpleProp, which for a counter
statistic needs to be of type "number".
Counters can be increased and decreased, and also set.
@param name: The name of the new stat.
@type name: string
@param start_value: The initial value of the counter.
Can be left blank in which case the
counter is initialized to 0.
@type start_value int or float
@return: Reference to the newly created stats object.
@rtype: L{SnapStatCounterObject}
"""
tprop = SimpleProp(name, "number", "")
return self._add_stat(tprop, _STAT_TYPE_COUNTER, start_value)
def add_field_stat_with_prop(self, prop, start_value=None):
"""
Add a new field statistic to the stats group.
A field statistic can only be set, but not increased
or decreased. This function needs a user-defined SimpleProp
definition. If the calling program doesn't want to bother
with the SimpleProps, it can use add_field_stat() as a
convenience function.
@param prop: The SimpleProp definition of the statistic.
@type prop: L{SimpleProp}
@param start_value: The initial value of the stat. Can be left
blank in which case it will be initialized
to a type-appropriate 'empty' or zero value.
@type start_value int, float, str or bool
@return: Reference to the newly created stats object.
@rtype: L{SnapStatObject}
"""
return self._add_stat(prop, _STAT_TYPE_FIELD, start_value)
def add_field_stat(self, name, type, start_value=None):
"""
Add a new field statistic to the stats group.
A field statistic can only be set, but not increased
or decreased.
@param name: The name of the new stat.
@type name: string
@param type: The type of the stat, which is either
"number", "boolean" or "string"
@type type: string
@param start_value: The initial value of the stat. Can be left
blank in which case it will be initialized
to a type-appropriate 'empty' or zero value.
@type start_value int, float, str or bool
@return: Reference to the newly created stats object.
@rtype: L{SnapStatObject}
"""
if type not in _known_types:
raise SnapObjTypeError("Only '%s' are allowable types. You specified '%s'." % (_known_types, type))
tprop = SimpleProp(name, type, "")
return self.add_field_stat_with_prop(tprop, start_value)
def delete_stat(self, name):
"""
Delete the stat with the specified name.
@param name: Name of stat.
@ptype name: string
"""
try:
del self._stats[name]
except:
raise SnapObjNotFoundError("Cannot delete non-existing stat '%s' from stat group '%s'." % (name, self.name))
def get_stat(self, name):
"""
Return reference to the stat object of the given name.
Any operations on the stat value should be done on the
returned stats object itself.
@param name: Name of the stat, as originally specified in
the SimpleProp label field.
@type name: string
"""
try:
return self._stats[name]
except:
raise SnapObjNotFoundError("A stat with name '%s' cannot be found in stats group '%s'." % (name, self.name))
def get_stat_names(self):
"""
Return list of the names of all stats in this group.
@return: List of stat names.
@rtype: list
"""
return self._stats.keys()
def get_complete_group(self):
"""
Return a dictionary of name/value pairs for each stat in this group.
@return: Dictionary with stat names and values.
@rtype dict
"""
d = {}
for e in self._stats:
d[e] = self._stats[e].get()
return d
class SnapStats(object):
"""
The stats object.
One instance of this needs to be created by each process.
"""
def __init__(self):
"""
Initialize stats object.
Enforces singleton.
"""
global _instance
if _instance and enforce_singleton:
raise SnapObjExistsError("Attempting creation of duplicate stats singleton.")
self._stat_groups = {}
_instance = self
def add_stats_group(self, group):
"""
Add a stats group to the stats object.
Each system module can define its own group of stats. Stats can
be either counters (which can be set, increased and decreased) or
they can be fields (which can only be set).
"""
if group.name in self._stat_groups:
raise SnapObjExistsError("A stats-group with the name '%s' has already been registered." % (group.name))
self._stat_groups[group.name] = group
def get_stats_group(self, name):
"""
Return reference to the stat group with specified name.
@return: Reference to stats group object.
@rtype: L{SnapStatsGroup}
"""
try:
return self._stat_groups[name]
except:
raise SnapObjNotFoundError("A stat-group with name '%s' cannot be found." % (name))
def get_stats_group_names(self):
"""
Return list with all stat group names.
@return: List of stat group names.
@rtype: list
"""
return self._stat_groups.keys()
def get_all_groups(self):
"""
Return dictionary with stats dictionaries from all groups.
@return: Dictionary with group names and stat dictionaries
for each group.
@rtype dict
"""
d = {}
for e in self._stat_groups:
d[e] = self._stat_groups[e].get_complete_group()
return d
|