diff -r 99636b66dcdf -r a32b2d4dcb49 src/Doxyfile --- a/src/Doxyfile Tue Jan 29 10:09:52 2013 +0000 +++ b/src/Doxyfile Tue Jan 29 10:09:52 2013 +0000 @@ -588,7 +588,8 @@ *.cc \ *.h \ *.hh \ - *.doxygen + *.doxygen \ + */python/m5/stats/*.py # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. diff -r 99636b66dcdf -r a32b2d4dcb49 src/python/m5/stats/__init__.py --- a/src/python/m5/stats/__init__.py Tue Jan 29 10:09:52 2013 +0000 +++ b/src/python/m5/stats/__init__.py Tue Jan 29 10:09:52 2013 +0000 @@ -1,3 +1,15 @@ +# Copyright (c) 2012 ARM Limited +# All rights reserved. +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# # Copyright (c) 2007 The Regents of The University of Michigan # Copyright (c) 2010 The Hewlett-Packard Development Company # All rights reserved. @@ -26,6 +38,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Authors: Nathan Binkert +# Sascha Bischoff import m5 @@ -38,6 +51,10 @@ def initSimStats(): internal.stats.initSimStats() +## @brief Extract the info from a stat and return it +# This extracts the information about a statistic +# and returns it as a separate object. +# ~param stat The input statistic def convert_info(stat): stat_type = stat.__class__.__name__ @@ -63,18 +80,21 @@ return info +## @brief A dictionary used to store the statistics class StatsDict(SortedDict): @staticmethod + ## @brief Sort the dictionary by key, which is split by '.' + # @param seq The sequence to sort def sorted(seq): return sorted(seq, key=lambda k: k.split('.'), reverse=True) names = [] stats = StatsDict() sim_stats = [] +## @brief Enable the statistics package. Before the statistics package is +# enabled, all statistics must be created and initialized and once +# the package is enabled, no more statistics can be created. def enable(): - '''Enable the statistics package. Before the statistics package is - enabled, all statistics must be created and initialized and once - the package is enabled, no more statistics can be created.''' __dynamic_cast = [] for k, v in internal.stats.__dict__.iteritems(): if k.startswith('dynamic_'): @@ -105,13 +125,14 @@ internal.stats.enable(); +## @brief Prepare all stats for value access. This must be done before +# dumping and serialization. def prepare(): - '''Prepare all stats for value access. This must be done before - dumping and serialization.''' - for stat in sim_stats: stat.prepare() +## @brief Convert a distribution from the C++ based distribution to a python +# distribution, based on the type of distribution. def convert_dist(data): from m5.stats.info import Deviation, Distribution, Histogram @@ -143,6 +164,8 @@ return d +## @brief Convert a stat to a python stat. +# @param stat The input statistic. def convert_value(stat): stat_type = stat.__class__.__name__ if stat_type == 'ScalarInfo': @@ -158,7 +181,12 @@ return None +## @brief A proxy used to store the value of a statistic and the information +# about it. class ValueProxy(object): + ## @brief Constructor. + # @param info The information about the stat. + # @param value The value if the stat. def __init__(self, info, value): self._info = info self.value = value @@ -169,6 +197,9 @@ def __nonzero__(self): return +## @brief Get all of the stats from the C++ into the python. Loop over each +# registered stat and call stat.info which returns all information about the +# statistic. def gather_stats(): prepare() stats = [] @@ -178,6 +209,9 @@ stats.append(ValueProxy(info, value)) return stats +## @brief Convert from C++ stats to python stats, and store as a context. +# @param The input stats list. +# @return A context containing all of the stats. def make_context(stats): from m5.stats import info info_map = { @@ -207,6 +241,9 @@ return context +## @brief Write the stats to a python shelve. +# @param stats The stats to write to the file. +# @param filename The name of the file to use. def shelve(stats, filename): import pickle, shelve shelf = shelve.open(filename, protocol=pickle.HIGHEST_PROTOCOL, @@ -221,12 +258,19 @@ self.filename = filename self.desc = desc + # @brief Does the actual writing to the stats file + # For each stat that is found in the context, we write it to the output + # file. We also write a header and a footer which allow us to determine + # which stats belong to a particular stats dump. + # @param context This contains all of the statistics which are to be + # written to the output file. def __call__(self, context): from m5.stats import display save_desc = display.descriptions display.descriptions = self.desc + # Open the input file. f = m5.core.openOutputFile(self.filename, 'a') print >>f @@ -242,15 +286,18 @@ display.descriptions = save_desc outputList = [] +# @brief Add the stats file as an output and add it to outputList +# @param filename The filename to which the stats are written +# @param desc Bool which sets whether the descriptions are written to the +# output file or not. def initText(filename, desc=True): output = display_text(filename, desc) outputList.append(output) lastDump = 0 +# @brief Dump all statistics values to the registered outputs. def dump(): - '''Dump all statistics values to the registered outputs''' - curTick = m5.curTick() global lastDump @@ -266,9 +313,8 @@ for output in outputList: output(context) +## @brief Reset all statistics to the base state. def reset(): - '''Reset all statistics to the base state''' - # call reset stats on all SimObjects root = Root.getInstance() if root: diff -r 99636b66dcdf -r a32b2d4dcb49 src/python/m5/stats/context.py --- a/src/python/m5/stats/context.py Tue Jan 29 10:09:52 2013 +0000 +++ b/src/python/m5/stats/context.py Tue Jan 29 10:09:52 2013 +0000 @@ -1,3 +1,15 @@ +# Copyright (c) 2012 ARM Limited +# All rights reserved. +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# # Copyright (c) 2010 The Hewlett-Packard Development Company # All rights reserved. # @@ -25,7 +37,9 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Authors: Nathan Binkert +# Sascha Bischoff +## @brief An iterator used to iterate over a stats context. class stat_context_iter(object): def __init__(self, context): self.items = [ ('', iter(sorted(context.iteritems()))) ] @@ -33,6 +47,10 @@ def __iter__(self): return self + ## @brief Get the next item stored in the context. + # Get the next item in the context, but ensure that we only return the leaf + # nodes and not the contexts used to store them. + # @return Key and value at the current node in the context. def next(self): while True: if not self.items: @@ -52,6 +70,12 @@ else: return k,v +## @brief Dictionary used to store the statistics +# This is a dictionary which has been modified in order to generate a tree-like +# structure which stores the stats based on the key. The key is split on '.' to +# determine where the value sits in the hierarchy. For each part of the key, a +# new node is inserted by creating a new context at that node. The value is +# finally assigned to one of the leaf nodes. class stat_context(dict): def __getattr__(self, attr): try: @@ -60,6 +84,14 @@ raise AttributeError, \ "'%s' object has no attribute '%s'" % (type(self), attr) + ## @brief Insert into stat_context. + # This inserts the statistics in the correct place in the tree. This + # is done based on the key, which is split to determine the path. For + # each element of path, we check if it exists, and create it in the + # event that it does not. Finally, once we have reached the last + # level, we assign the value. + # @param key Supplies the path and the name of the statistic. + # @param value The value to be stored. def insert(self, key, value): path = key.split('.') @@ -76,9 +108,17 @@ name = path.pop(0) level[name] = value + ## @brief When iterating, return an iterator. + # @return stat_context_iter def iterate(self): return stat_context_iter(self) + ## @brief Find a statistic based on the name of the stat + # This returns the value of a statistic based on the name of the stat. + # This is done by spitting the name of the stat, and using this to + # reach the level where the statistic is stored. + # @param name The name of the statistic, which is split to get the path. + # @return node The node found. def find(self, name): path = name.split('.') diff -r 99636b66dcdf -r a32b2d4dcb49 src/python/m5/stats/display.py --- a/src/python/m5/stats/display.py Tue Jan 29 10:09:52 2013 +0000 +++ b/src/python/m5/stats/display.py Tue Jan 29 10:09:52 2013 +0000 @@ -1,3 +1,15 @@ +# Copyright (c) 2012 ARM Limited +# All rights reserved. +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# # Copyright (c) 2003-2004 The Regents of The University of Michigan # All rights reserved. # @@ -25,6 +37,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Authors: Nathan Binkert +# Sascha Bischoff from math import isnan, sqrt @@ -32,14 +45,26 @@ from m5.stats.info import * from m5.util.registry import registry +# This file provides the methods used to generate the stats.txt file. These are +# designed in such a way that the resulting stats.txt is identical to the old, +# C++ based output. + all = False descriptions = True +## @brief Stores a value and can return it as a string class Value(object): + ## @brief Initialize the stat + # @param value The value of the stat. + # @param precision The precision of the stat. + # @param percent Boolean specifying whether the stat is a + # percentage or not. def __init__(self, value, precision, percent = False): self.value = float(value) self.precision = precision self.percent = percent + + ## @brief Return the value as a correctly formatted string. def __str__(self): if self.precision >= 0: format = "%%.%df" % self.precision @@ -59,6 +84,7 @@ return value +## @brief Base for the other, type specific display methods. class DisplayBase(object): def __init__(self, stat): self._stat = stat @@ -70,11 +96,14 @@ def reset(self): pass +## @brief Display class. class Display(DisplayBase): + ## @brief Reset the pdf and cdf. def reset(self): self.pdf = float('nan') self.cdf = float('nan') + ## @brief Update the value. def update(self, val, total): self.value = val if total: @@ -82,6 +111,7 @@ self.pdf = pdf self.cdf += pdf + ## @brief Get a string which represents the value and description. def __str__(self): do_desc = descriptions and self.desc @@ -105,6 +135,7 @@ return output + ## @brief Determine if we should display this stat or not. def dodisplay(self): if all: return True @@ -120,7 +151,10 @@ if self.dodisplay(): print >>out, self +## @brief Class to display a vector class VectorDisplay(DisplayBase): + ## @brief Display the value if it is non zero + # @param out The output to which we want to print. def display(self, out): if not self.value: return @@ -129,6 +163,7 @@ p.flags = self.flags p.precision = self.precision + # If the value is not a list or a tuple, just print the value. if not isinstance(self.value, (list, tuple)): p.name = self.name p.desc = self.desc @@ -177,7 +212,12 @@ p.value = mytotal p.display(out) +## @brief Display a distribution. Begin by printing the number of samples, the +# mean and the standard deviation. This is followed by printing the +# distribution itself. class DistDisplay(DisplayBase): + ## @brief Print the data. + # @param The output to which to print the data, def display(self, out): v = self.value @@ -195,9 +235,11 @@ p.value = v.stdev() p.display(out) + # If we have a deviation, don't do anything. if type(v) == Deviation: return + # Calculate the total. Add the under- and overflows for a distribution. total = sum(v.vector) if type(v) == Distribution: if not isnan(v.underflow): @@ -209,11 +251,13 @@ p.pdf = 0.0 p.cdf = 0.0 + # If we have a distribution and have underflows, print them. if type(v) == Distribution and not isnan(v.underflow): p.name = "%s::underflows" % self.name p.update(v.underflow, total) p.display(out) + # Print the contents of each bucket. for i in xrange(len(v.vector)): low = i * v.bucket_size + v.min high = min(low + v.bucket_size - 1.0, v.max) @@ -225,6 +269,7 @@ p.update(v.vector[i], total) p.display(out) + # If we have a distribution and have overflows, print them. if type(v) == Distribution and not isnan(v.overflow): p.name = "%s::overflows" % self.name p.update(v.overflow, total) @@ -232,29 +277,40 @@ p.reset() + # If we have a distribution and have min_value, print it. if type(v) == Distribution and not isnan(v.min_val): p.name = "%s::min_value" % self.name p.value = v.min_val p.display(out) + # If we have a distribution and have max_value, print it. if type(v) == Distribution and not isnan(v.max_val): p.name = "%s::max_value" % self.name p.value = v.max_val p.display(out) + # Print the total. p.name = "%s::total" % self.name p.value = total p.display(out) +# register_display is used to link the display function to the type of +# statistic. register_display = registry() @register_display(Scalar) +## @brief Function to display a scalar. +# @param stat The stat to output. +# @param out The output to which the stats should be printed. def display_scalar(stat, out): p = Display(stat) p.value = value(stat) p.display(out) @register_display(Vector) +## @brief Function to display a vector. +# @param stat The stat to output. +# @param out The output to which the stats should be printed. def display_vector(stat, out): if len(stat) == 1: p = Display(stat) @@ -265,6 +321,9 @@ p.display(out) @register_display(Formula) +## @brief Function to display a formula. +# @param stat The stat to output. +# @param out The output to which the stats should be printed. def display_formula(stat, out): if scalar(stat): display_scalar(stat, out) @@ -272,11 +331,17 @@ display_vector(stat, out) @register_display(Dist) +## @brief Function to display a distribution. +# @param stat The stat to output. +# @param out The output to which the stats should be printed. def dist_display(stat, out): p = DistDisplay(stat) p.display(out) @register_display(VectorDist) +## @brief Function to display a vector distribution. +# @param stat The stat to output. +# @param out The output to which the stats should be printed. def vectordist_display(stat, out): for i,value in enumerate(stat.value): subnames = stat.subnames[i] @@ -304,6 +369,9 @@ p.display(out) @register_display(Vector2d) +## @brief Function to display a 2D vector. +# @param stat The stat to output. +# @param out The output to which the stats should be printed. def vector2d_display(stat, out): p = VectorDisplay(stat) @@ -337,11 +405,18 @@ p.display(out) +## @brief Display the stats +# This displays the stats and will output them to stdout if no output file is +# specified. The display function called is determined based on the type of +# statistic. +# @param stat The stat to output. +# @param out The output to which the stats should be printed. def display(stat, out=None): if out is None: import sys out = sys.stdout + # Determine display function based on the type of stat. func = register_display[type(stat)] func(stat, out) diff -r 99636b66dcdf -r a32b2d4dcb49 src/python/m5/stats/info.py --- a/src/python/m5/stats/info.py Tue Jan 29 10:09:52 2013 +0000 +++ b/src/python/m5/stats/info.py Tue Jan 29 10:09:52 2013 +0000 @@ -1,3 +1,15 @@ +# Copyright (c) 2012 ARM Limited +# All rights reserved. +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# # Copyright (c) 2003-2004 The Regents of The University of Michigan # All rights reserved. # @@ -25,6 +37,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Authors: Nathan Binkert +# Sascha Bischoff import math, operator, re, types @@ -33,26 +46,40 @@ class ProxyError(Exception): pass +## @brief Unproxy a statistic. If the statistic in not a proxy, do nothing. +# @param proxy The proxy to unproxy. +# @return The unproxied stat, unless the stat cannot be unproxied, in which case +# return the original. def unproxy(proxy): if hasattr(proxy, '__unproxy__'): return proxy.__unproxy__() return proxy +## @brief Test if a stat is a scalar. +# @param stat The statistic to check. +# @return True if stat is a scalar, False otherwise. def scalar(stat): stat = unproxy(stat) assert(stat.__scalar__() != stat.__vector__()) return stat.__scalar__() +## @brief Test if a stat is a vector. +# @param stat The statistic to check. +# @return True if stat is a vector, False otherwise. def vector(stat): stat = unproxy(stat) assert(stat.__scalar__() != stat.__vector__()) return stat.__vector__() +## @brief Get the value of a (scalar) stat. +# @return The value of the stat. def value(stat, *args): stat = unproxy(stat) return stat.__value__(*args) +## @brief Get the values for a (vector) stat. +# @return A list of values. def values(stat): stat = unproxy(stat) result = [] @@ -61,13 +88,19 @@ result.append(val) return result +## @brief Sum all of the values for a stat. +# @return The sum. def total(stat): return sum(values(stat)) +## @brief Get the length of a stat. +# @return The length. def len(stat): stat = unproxy(stat) return stat.__len__() +## @brief Defines a value. Returns a proxy based on the operation performed on +# the value. These are used to calculate combinations of statistics. class Value(object): def __getattr__(self, attr): if attr in ('__scalar__', '__vector'): @@ -109,61 +142,108 @@ def __abs__(self): return UnaryProxy(operator.__abs__, self) +## @brief Scalar value class ScalarValue(Value): + ## @brief Test if a stat is a scalar. + # @return True def __scalar__(self): return True + ## @brief Test if a stat is a vector. + # @return False def __vector__(self): return False def __value__(self): raise AttributeError, '__value__ must be defined' +## @brief A single item in a vector. Stores a proxy to the vector and an index +# at which the item is located. class VectorItemProxy(Value): + ## @brief Constructor. + # @param proxy A proxy to the stat. + # @param index The index of the value within the stat. def __init__(self, proxy, index): self.proxy = proxy self.index = index + ## @brief Test if a stat is a scalar. + # @return True. def __scalar__(self): return True + ## @brief Test if a stat is a vector. + # @return False. def __vector__(self): return False + ## @brief Get the value at a particular index in the vector. + # @return The value. def __value__(self): return value(self.proxy, self.index) +## @brief Vector value class VectorValue(Value): + ## @brief Test if a stat is a scalar. + # @return False. def __scalar__(self): return False + ## @brief Test if a stat is a vector. + # @return True. def __vector__(self): return True def __value__(self, index): raise AttributeError, '__value__ must be defined' + ## @brief Get a proxy to a specific item in the vector. def __getitem__(self, index): return VectorItemProxy(self, index) +## @brief Scalar constant, inherited from ScalarValue. class ScalarConstant(ScalarValue): + ## @brief Constructor + # @param constant The constant to store. def __init__(self, constant): self.constant = constant + + ## @brief Get the value of the constant. + # @return The value of the constant. def __value__(self): return self.constant + + ## @brief Get the constant as a string. + # @return A string representation of the constant. def __str__(self): return str(self.constant) +## @brief Vector constant, inherited from VectorValue. class VectorConstant(VectorValue): + ## @brief Constructor + # @param constant The constant vector to store. def __init__(self, constant): self.constant = constant + + ## @brief Get the value of the constant at a specific index. + # @param The index of the constant in the vector. + # @return The value of the constant at a particular index. def __value__(self, index): return self.constant[index] + + ## @brief Get the length of the vector constant. + # @return The length of the vector constant. def __len__(self): return len(self.constant) + + ## @brief Get the constant as a string. + # @return A string representation of the constant. def __str__(self): return str(self.constant) +## @brief Wraps values as either scalar or vector constants. If the value passed +# in is already a value it is returned as is. +# @param value The value to wrap. def WrapValue(value): if isinstance(value, (int, long, float)): return ScalarConstant(value) @@ -174,16 +254,24 @@ raise AttributeError, 'Only values can be wrapped' +## @brief Base class for a statistic. class Statistic(object): + ## @brief Constructor + # @param id An ID unique to this stat. + # @param db A dictionary of of all stats. + # @param context The context in which this stat sits. def __init__(self, id, db, context): self.__dict__['id'] = id self.__dict__['db'] = db self.__dict__['context'] = context @property + ## @brief Return the stat based on its location in the dictionary. + # @return The stat. def stat(self): return self.db[self.id] + ## @brief Get the attributes of the stat. def __getattr__(self, attr): return getattr(self.stat, attr) @@ -193,10 +281,15 @@ super(Statistic, self).__setattr__(attr, value) + ## @brief Get the name of the stat. + # @return The name of the stat as a string. def __str__(self): return self.name +## @brief Proxy to a value. Can be either a scalar or a vector. class ValueProxy(Value): + ## @brief Get the attributes. + # @return The values for scalars and vectors, and length for vectors. def __getattr__(self, attr): if attr == '__value__': if scalar(self): @@ -208,28 +301,40 @@ return self.__vectorlen__ return super(ValueProxy, self).__getattribute__(attr) +## @brief Proxy used for unary operations. When getting the value is returns the +# result of the operation. class UnaryProxy(ValueProxy): def __init__(self, op, arg): self.op = op self.arg = WrapValue(arg) + ## @brief Test if a stat is a scalar. def __scalar__(self): return scalar(self.arg) + ## @brief Test if a stat is a vector. def __vector__(self): return vector(self.arg) + ## @brief Get the value for a scalar. + # @return The value after the operation has been applied. def __scalarvalue__(self): val = value(self.arg) return self.op(val) + ## @brief Get the value of the vector at a specific index. + # @return The value after the operation has been applied. def __vectorvalue__(self, index): val = value(self.arg, index) return self.op(val) + ## @brief Get the length of the vector. + # @return The length of the vector. def __vectorlen__(self): return len(unproxy(self.arg)) + ## @brief Get a string representing the operation. + # @return A string representing the operation. def __str__(self): if self.op == operator.__neg__: return '-%s' % str(self.arg) @@ -238,19 +343,33 @@ if self.op == operator.__abs__: return 'abs(%s)' % self.arg +## @brief Proxy used for binary operations. When getting the value is returns +# the result of the operation. class BinaryProxy(ValueProxy): + ## @brief Constructor + # @param op The operation to perform. + # @param arg0 First argument + # @param arg1 Second argument def __init__(self, op, arg0, arg1): super(BinaryProxy, self).__init__() self.op = op self.arg0 = WrapValue(arg0) self.arg1 = WrapValue(arg1) + ## @brief Test if a stat is a scalar. + # @return True if both are scalars, False otherwise. def __scalar__(self): return scalar(self.arg0) and scalar(self.arg1) + ## @brief Test if a stat is a vector. + # @return True if either or both are vectors, False otherwise. def __vector__(self): return vector(self.arg0) or vector(self.arg1) + ## @brief Get the result. Read the value of each scalar, then try and + # perform the requested operation. + # @return The result of the operation if it was successful, 'float('nan') + # otherwise. def __scalarvalue__(self): val0 = value(self.arg0) val1 = value(self.arg1) @@ -259,6 +378,10 @@ except ZeroDivisionError: return float('nan') + ## @brief Get the result. Read the values as either scalars or vectors + # depending on the type of the operand, then perform the operation. + # @return The result of the operation if it was successful, float('nan') or + # float('inf') otherwise. def __vectorvalue__(self, index): if scalar(self.arg0): val0 = value(self.arg0) @@ -276,6 +399,8 @@ return float('inf') return float('nan') + ## @brief Check that the vectors are the same length, and return it. + # @return The length of the vectors. def __vectorlen__(self): if vector(self.arg0) and scalar(self.arg1): return len(self.arg0) @@ -292,6 +417,8 @@ return len0 + ## @brief Get a string representing the operation. + # @return A string representing the operation. def __str__(self): ops = { operator.__add__ : '+', operator.__sub__ : '-', @@ -302,39 +429,69 @@ return '(%s %s %s)' % (str(self.arg0), ops[self.op], str(self.arg1)) +## @brief A proxy to a statistic stored in a dictionary. This allows the stat to +# be directly referenced, rather than having to try and find it in the dict or +# having to keep track of the stat's index. class Proxy(Value): + ## @brief The constructor. + # @param name The name of the stat. + # @param dict The dictionary in which the stat is stored. def __init__(self, name, dict): self.name = name self.dict = dict + ## @brief Unproxy the stat by looking it up in the dictionary. + # @return The statistic. def __unproxy__(self): return unproxy(self.dict[self.name]) + ## @brief Get a proxy to an item within the stat based on the index within. + # @return A proxy to the item. def __getitem__(self, index): return ItemProxy(self, index) + ## @brief Get the attribute of the statistic. + # @return A proxy to the attribute. def __getattr__(self, attr): return AttrProxy(self, attr) + ## @brief Get a string representing the statistic. def __str__(self): return str(self.dict[self.name]) +## @brief A proxy to an item within a statistic. The statistic itself is a proxy +# as it refers to a statistic within a dictionary. class ItemProxy(Proxy): + ## @brief Constructor. + # @param proxy The proxy representing the statistic. + # @param index The index of the item within the statistic. def __init__(self, proxy, index): self.proxy = proxy self.index = index + ## @brief Get the item itself. Do this by unproxying the stat, then + # unproxying the item itself. + # @return The unproxied item. def __unproxy__(self): return unproxy(unproxy(self.proxy)[self.index]) + ## @brief Get a string containing the name of the statistic and the index of + # the item stored within. def __str__(self): return '%s[%s]' % (self.proxy, self.index) +## @brief A proxy to the attributes of a statistic. class AttrProxy(Proxy): + ## @brief Constructor. + # @param proxy The proxy representing the statistic. + # @param attr The attribute we want to get to. def __init__(self, proxy, attr): self.proxy = proxy self.attr = attr + ## @brief Get the attribute itself. Do this by unproxying the stat, then + # getting the attribute itself. + # @return The unproxied attribute. def __unproxy__(self): proxy = unproxy(self.proxy) try: @@ -343,9 +500,12 @@ raise ProxyError, e return unproxy(attr) + ## @brief Get a string containing the name of the statistic and the name of + # the attribute. def __str__(self): return '%s.%s' % (self.proxy, self.attr) +## @brief Unused. class ProxyGroup(object): def __init__(self, dict=None, **kwargs): self.__dict__['dict'] = {} @@ -362,23 +522,38 @@ def __setattr__(self, attr, value): self.dict[attr] = value +## @brief Definition of a Scalar. Inherits from Statistic and ScalarValue. class Scalar(Statistic,ScalarValue): + ## @brief Get the value. + # @return The value. def __value__(self): return self.value + ## @brief Check if the value is zero. + # @return True if non zero, False otherwise. def __nonzero__(self): return self.value != 0 +## @brief Definition of a Vector. Inherits from Statistic and VectorValue. class Vector(Statistic,VectorValue): + ## @brief Get the value of a particular item. + # @param item The item to look up, + # @return The value of the item. def __value__(self, item): return self.value[item] + ## @brief Get the length of the vector. + # @return The length. def __len__(self): return len(self.value) + ## @brief Check if the value is zero. + # @return True if non zero, False otherwise. def __nonzero__(self): return any(self.value) +## @brief A formula based on other statistics which is calculated when we get +# the value. class Formula(Statistic,Value): def __getattr__(self, attr): if attr == 'formula': @@ -386,6 +561,7 @@ self.formula = re.sub(':', '__', formula) return self.formula + # If we want to get the value,evaluate it and return the result. if attr == '_the_value': g = dict(total=total, ScalarConstant=ScalarConstant, @@ -393,20 +569,29 @@ self._the_value = eval(self.formula, g, self.context) return self._the_value + # Return the properties of the value. if attr in ('__scalar__', '__vector__', '__value__', '__len__'): return getattr(self._the_value, attr) return super(Formula, self).__getattr__(attr) + ## @brief Determine if value is non zero. + # @return True if non-zero, False otherwise. def __nonzero__(self): return any(self.value) +## @brief Base class for any type of deviation, such as Histogram. class Deviation(object): + ## @brief Reset the deviation. Samples, sum and squares are set to 0. def clear(self): self.samples = 0 self.sum = 0 self.squares = 0 + ## @brief Copy the deviation. This determines the type of object, calls its + # constructor and copies the number of samples, the sum and the squares into + # the new object. + # @return A copy of the deviation. def copy(self): cls = type(self) copy = cls() @@ -415,12 +600,18 @@ copy.squares = self.squares return copy + ## @brief Calculate and return the mean. + # @return The mean if the number of samples is greater than 0, float('nan') + # otherwise. def mean(self): if self.samples == 0: return float('nan') return self.sum / self.samples + ## @brief Calculate and return the standard deviation. + # @return The standard deviation if the number of samples is greater than 1, + # float('nan') otherwise. def stdev(self): num = (self.samples * self.squares - self.sum ** 2) den = (self.samples * (self.samples - 1)) @@ -430,12 +621,23 @@ return math.sqrt(num / den) + ## @brief Determine if it is possible to compare this deviation to another. + # This is accomplished by checking that they are both of the same type. + # @param other The object we are comparing to ourself. + # @return True if both deviations are of the same type, False otherwise. def comparable(self, other): return type(self) == type(other) + ## @brief Check that we are non-zero by determining if any samples have been + # stored. + # @return True if we have samples, False otherwise. def __nonzero__(self): return bool(self.samples) + ## @brief Determine if this deviation is equal to another deviation. + # Begin by checking if the two distributions are comparable, then check that + # the number of samples, the sum and the squares are equal. + # @return True if they are equal, False otherwise. def __eq__(self, other): if not self.comparable(other): return False @@ -444,6 +646,9 @@ self.sum == other.sum and self.squares == other.squares) + ## @brief Add two deviations. Check that they are comparable, and then + # add the samples, the sum and the squares. + # @param other The other distribution. def __iadd__(self, other): assert self.comparable(other) @@ -452,6 +657,9 @@ self.squares += other.squares return self + ## @brief Subtract one deviation from another. Check that they are + # comparable, and then subtract the samples, the sum and the squares. + # @param other The other distribution. def __sub__(self, other): assert self.comparable(other) @@ -460,6 +668,10 @@ self.squares -= other.squares return self + ## @brief Divide one deviation by another. Check that they are + # comparable, and then divide the samples, the sum and the squares by their + # counterparts. + # @param other The other distribution. def __itruediv__(self, other): if not other: return self @@ -468,10 +680,15 @@ self.squares /= other return self +## @brief A histogram which inherits from Deviation. class Histogram(Deviation): + ## @brief Reset the Histogram. Call the super class's clear method. def clear(self): super(Histogram, self).clear() + ## @brief Copy the histogram. Call the superclass copy method, then copy the + # min, max, bucket size and vector to the new histogram. + # @return The new histogram. def copy(self): copy = super(Histogram, self).copy() copy.min = self.min @@ -480,6 +697,12 @@ copy.vector = self.vector[:] return copy + ## @brief Determine if it is possible to compare this histogram to another. + # This is accomplished by checking that they are both of the same type, that + # the min, max and bucket_size match and that the length of the vectors + # matches. + # @param other The object we are comparing to ourself. + # @return True if both histograms are comparable, False otherwise. def comparable(self, other): if not super(Histogram, self).comparable(other): return False @@ -489,22 +712,27 @@ self.max == other.max and self.bucket_size == other.bucket_size) + ## @brief Add two histograms. def __iadd__(self, other): super(Histogram, self).__iadd__(other) self.vector = map(operator.__add__, self.vector, other.vector) return self + ## @brief Subtract one histogram from another. def __isub__(self, other): super(Histogram, self).__isub__(other) self.vector = map(operator.__sub__, self.vector, other.vector) return self + ## @brief Divide one histogram by another. def __itruediv__(self, other): super(Histogram, self).__itruediv__(other) self.vector = map(lambda x: x / other, self.vector) return self +## @brief Distribution which inherits from Histogram. class Distribution(Histogram): + ## @brief Clear the distribution def clear(self): super(Distribution, self).clear() self.min_val = float('Inf') @@ -512,6 +740,7 @@ self.underflow = 0.0 self.overflow = 0.0 + ## @brief Copy the distribution def copy(self): copy = super(Distribution, self).copy() copy.min_val = self.min_val @@ -520,6 +749,7 @@ copy.overflow = self.overflow return copy + ## @brief Add two distributions. def __iadd__(self, other): super(Distribution, self).__iadd__(other) self.min_val = min(self.min_val, other.min_val) @@ -528,6 +758,7 @@ self.overflow += other.overflow return self + ## @brief Subtract one distribution from another. def __isub__(self, other): super(Distribution, self).__isub__(other) self.min_val = min(self.min_val, other.min_val) @@ -536,48 +767,72 @@ self.overflow -= other.overflow return self + ## @brief Divide one distribution by another. def __itruediv__(self, other): super(Distribution, self).__itruediv__(other) self.underflow /= other self.overflow /= other return self +## @brief Dist which inherits from Statistic. class Dist(Statistic): + ## @brief Determine if the Dists are comparable based on the name and value. + # @return True if comparable, False otherwise. def comparable(self, other): return self.name == other.name and \ self.value.compareable(other.value) + ## @brief Determine if the Dist is not zero. + # @return True if non zero, False otherwise. def __nonzero__(self): return bool(self.value) + ## @brief Determine if two Dists are equal. + # @return True if non equal, False otherwise. def __eq__(self, other): return self.value == other.value + ## @brief Subtract one Dist from another. + # @param other The other Dist. def __isub__(self, other): self.value -= other.value return self + ## @brief Add one Dist to another. + # @param other The other Dist. def __iadd__(self, other): self.value += other.value return self + ## @brief Divide one Dist by another. + # @param other The other Dist. def __itruediv__(self, other): if not other: return self self.value /= other return self +## @brief VectorDist which inherits from Statistic. class VectorDist(Statistic): + ## @brief Determine if the VectorDists are comparable based on the name and + # values. + # @return True if comparable, False otherwise. def comparable(self, other): return self.name == other.name and \ all(map(lambda x, y : x.comparable(y), self.value, other.value)) + ## @brief Determine if the VectorDist is not zero. + # @return True if non zero, False otherwise. def __nonzero__(self): return any(self.value) + ## @brief Determine if two VectorDists are equal by comparing the values. + # @return True if non equal, False otherwise. def __eq__(self, other): return all(map(lambda x, y : x == y, self.value, other.value)) + ## @brief Subtract one VectorDist from another on a per element basis. + # @param other The other Dist. def __isub__(self, other): if isinstance(self.value, (list, tuple)) and \ isinstance(other.value, (list, tuple)): @@ -587,6 +842,8 @@ self.value -= other.value return self + ## @brief Add one VectorDist to another on a per element basis. + # @param other The other Dist. def __iadd__(self, other): if isinstance(self.value, (list, tuple)) and \ isinstance(other.value, (list, tuple)): @@ -596,6 +853,8 @@ self.value += other.value return self + ## @brief Divide one VectorDist by a scalar. + # @param other The other value. def __itruediv__(self, other): if not other: return self @@ -606,23 +865,33 @@ self.value /= other return self +## @brief Vector2d which inherits from Statistic. class Vector2d(Statistic): + ## @brief Determine if the Vector2d are comparable based on the name,value + # and dimensions. + # @return True if comparable, False otherwise. def comparable(self, other): return self.name == other.name and self.x == other.x and \ self.y == other.y + ## @brief Determine if the Vector2d is not zero. + # @return True if non zero, False otherwise. def __nonzero__(self): return any(self.value) + ## @brief Not implemented. def __eq__(self, other): return True + ## @brief Not implemented. def __isub__(self, other): return self + ## @brief Not implemented. def __iadd__(self, other): return self + ## @brief Not implemented. def __itruediv__(self, other): if not other: return self