Stefan Scherfke avatar Stefan Scherfke committed 0d6c38e

Implemented a ListBackend for the Collector and wrote a small benchmark to compare it with the original Collector

Comments (0)

Files changed (1)

collectors/core.py

 """
 
 
+class ListBackend(object):
+    container_class = list
+
+    def append(self, series, value):
+        series.append(value)
+
+
 class Collector(tuple):
     """This class can monitor the values of a given set of variables.
 
     ...     b = 2
     ...
     >>> spam = Spam()
-    >>> m = Monitor(('a', lambda: getattr(spam, 'a')), ('b', lambda x: x))
-    >>> m.a == m[0], m.b == m[1]
+    >>> c = Collector(('a', lambda: getattr(spam, 'a')), ('b', lambda x: x))
+    >>> c.a == c[0], c.b == c[1]
     (True, True)
     >>>
-    >>> m(b=spam.b + 2)
-    >>> m # Monitor is a tuple with nested lists by itself.
+    >>> c(b=spam.b + 2)
+    >>> c # Monitor is a tuple with nested lists by itself.
     ([1], [4])
-    >>> m.a, m.b # You can also access it's elements by their name.
+    >>> c.a, c.b # You can also access it's elements by their name.
     ([1], [4])
 
     In this example, ``spam`` is the object to be monitored. The monitor is
         if not args:
             raise AttributeError('Nothing to be monitored.')
 
+        backend = kwargs['backend'] \
+                if 'backend' in kwargs else ListBackend()
+
+        # Parse arguments using a helper function. This method will populate the
+        # following lists.
+        names, collectors, series = [], [], []
+
+        def parse_arg(arg):
+            """Method for parsing collector descriptions.
+
+            ``arg`` can be ('name', func) or (('name1', func),
+            ('name2', func), ...)
+            """
+            if type(arg) != tuple:
+                raise TypeError('%s must be an instance of '
+                        '"collections.Iterable", but %s is not.' %
+                        (arg, type(arg)))
+
+            # Check if this is a collector description. These look like this:
+            # ('name', func). Otherwise treat arg as a nested list of collector
+            # descriptions.
+            if len(arg) == 2 and isinstance(arg[0], basestring) and \
+                    callable(arg[1]):
+                if arg[0] in names:
+                    raise ValueError(
+                            'There\'s already a series named "%s".' % arg[0])
+                names.append(arg[0])
+                collectors.append(arg[1])
+                series.append(backend.container_class())
+            else:
+                # This is a nested list of collector descriptions. Recurse.
+                for child in arg:
+                    parse_arg(child)
+
+        # Parse all arguments.
+        for arg in args:
+            parse_arg(arg)
+
+        # Allocate the tuple instance and set the empty series lists as data.
+        instance = tuple.__new__(cls, series)
+
+        # Set the names and collectors of each series.
+        instance.__backend = backend
+        instance.__names = tuple(names)
+        instance.__collectors = tuple(collectors)
+
+        # Link attributes to series.
+        for name, data in zip(names, series):
+            setattr(instance, name, data)
+
+        return instance
+
+    def __call__(self, **kwargs):
+        backend = self.__backend
+        for series, name, col in zip(self, self.__names, self.__collectors):
+            if name in kwargs:
+                backend.append(series, col(kwargs[name]))
+            else:
+                backend.append(series, col())
+
+    def collect(self, **kwargs):
+        """This is just an alias to :meth:`__call__`."""
+        return self(**kwargs)
+
+
+class OrigCollector(tuple):
+    """This class can monitor the values of a given set of variables.
+
+    Each variable is described by a *name* and a *collector function*. The
+    list of all variable values is accessible as attribute of a monitor
+    instance as well as via an index (defined by the order you passed them to
+    ``__init__``). Besides *(name, func)* tuples you can also pass nested
+    lists of those tuples, which is helpful for some convenience functions.
+
+    All collectors will be called (in the same order as they were specified)
+    each time a ``Monitor`` instance is called. The collector functions may
+    either grab the desired values by themselves or let them be passed
+    manually with each monitor call.
+
+    Here is an example how this works:
+
+    #>>> class Spam(object):
+    #...     a = 1
+    #...     b = 2
+    #...
+    #>>> spam = Spam()
+    #>>> m = Monitor(('a', lambda: getattr(spam, 'a')), ('b', lambda x: x))
+    #>>> m.a == m[0], m.b == m[1]
+    #(True, True)
+    #>>>
+    #>>> m(b=spam.b + 2)
+    #>>> m # Monitor is a tuple with nested lists by itself.
+    #([1], [4])
+    #>>> m.a, m.b # You can also access it's elements by their name.
+    #([1], [4])
+
+    In this example, ``spam`` is the object to be monitored. The monitor is
+    configured to observe two variables named “a” and “b”. The collector for
+    “a” automatically retrieves it’s value via ``getattr()`` while the value
+    for “b” needs to be passed to the monitor manually as keyword argument.
+    For these common cases, there are the shortcuts :func:`get` and
+    :func:`manual`.
+
+    Note that names for data series need to be unique. ``Monitor``
+    instanciation will raise a ``ValueError`` if there's a duplicate name.
+    """
+    def __new__(cls, *args, **kwargs):
+        # We need to override __new__ instead of __init__ because tuples are
+        # immutable. This means it's data is set during the allocation phase in
+        # __new__.
+        if not args:
+            raise AttributeError('Nothing to be monitored.')
+
         # Parse arguments using a helper function. This method will populate the
         # following lists.
         names, collectors, series = [], [], []
         """This is just an alias to :meth:`__call__`."""
         return self(**kwargs)
 
-
-class OrigCollector(tuple):
-    """This class can monitor the values of a given set of variables.
-
-    Each variable is described by a *name* and a *collector function*. The
-    list of all variable values is accessible as attribute of a monitor
-    instance as well as via an index (defined by the order you passed them to
-    ``__init__``). Besides *(name, func)* tuples you can also pass nested
-    lists of those tuples, which is helpful for some convenience functions.
-
-    All collectors will be called (in the same order as they were specified)
-    each time a ``Monitor`` instance is called. The collector functions may
-    either grab the desired values by themselves or let them be passed
-    manually with each monitor call.
-
-    Here is an example how this works:
-
-    >>> class Spam(object):
-    ...     a = 1
-    ...     b = 2
-    ...
-    >>> spam = Spam()
-    >>> m = Monitor(('a', lambda: getattr(spam, 'a')), ('b', lambda x: x))
-    >>> m.a == m[0], m.b == m[1]
-    (True, True)
-    >>>
-    >>> m(b=spam.b + 2)
-    >>> m # Monitor is a tuple with nested lists by itself.
-    ([1], [4])
-    >>> m.a, m.b # You can also access it's elements by their name.
-    ([1], [4])
-
-    In this example, ``spam`` is the object to be monitored. The monitor is
-    configured to observe two variables named “a” and “b”. The collector for
-    “a” automatically retrieves it’s value via ``getattr()`` while the value
-    for “b” needs to be passed to the monitor manually as keyword argument.
-    For these common cases, there are the shortcuts :func:`get` and
-    :func:`manual`.
-
-    Note that names for data series need to be unique. ``Monitor``
-    instanciation will raise a ``ValueError`` if there's a duplicate name.
-    """
-    def __new__(cls, *args, **kwargs):
-        # We need to override __new__ instead of __init__ because tuples are
-        # immutable. This means it's data is set during the allocation phase in
-        # __new__.
-        if not args:
-            raise AttributeError('Nothing to be monitored.')
-
-        # Parse arguments using a helper function. This method will populate the
-        # following lists.
-        names, collectors, series = [], [], []
-
-        def parse_arg(arg):
-            """Method for parsing collector descriptions.
-
-            ``arg`` can be ('name', func) or (('name1', func),
-            ('name2', func), ...)
-            """
-            if type(arg) != tuple:
-                raise TypeError('%s must be an instance of '
-                        '"collections.Iterable", but %s is not.' %
-                        (arg, type(arg)))
-
-            # Check if this is a collector description. These look like this:
-            # ('name', func). Otherwise treat arg as a nested list of collector
-            # descriptions.
-            if len(arg) == 2 and isinstance(arg[0], basestring) and \
-                    callable(arg[1]):
-                if arg[0] in names:
-                    raise ValueError(
-                            'There\'s already a series named "%s".' % arg[0])
-                names.append(arg[0])
-                collectors.append(arg[1])
-                series.append([])
-            else:
-                # This is a nested list of collector descriptions. Recurse.
-                for child in arg:
-                    parse_arg(child)
-
-        # Parse all arguments.
-        for arg in args:
-            parse_arg(arg)
-
-        # Allocate the tuple instance and set the empty series lists as data.
-        instance = tuple.__new__(cls, series)
-
-        # Set the names and collectors of each series.
-        instance.__names = tuple(names)
-        instance.__collectors = tuple(collectors)
-
-        # Link attributes to series.
-        for name, data in zip(names, series):
-            setattr(instance, name, data)
-
-        return instance
-
-    def __call__(self, **kwargs):
-        for series, name, col in zip(self, self.__names, self.__collectors):
-            if name in kwargs:
-                series.append(col(kwargs[name]))
-            else:
-                series.append(col())
-
-    def collect(self, **kwargs):
-        """This is just an alias to :meth:`__call__`."""
-        return self(**kwargs)
-
+def benchmark(cls):
+    sim = Simulation()
+    procs = [MyProc(sim, cls) for i in range(100000)]
+    for proc in procs:
+        sim.activate(proc, proc.run())
+    sim.simulate(until=50)
 
 if __name__ == '__main__':
     import random
 
-    import matplotlib.pyplot as plt
-    from numpy import array, float64
+    #import matplotlib.pyplot as plt
+    #from numpy import array, float64
     from SimPy.Simulation import Simulation, Process, hold
+    from collectors.shortcuts import *
 
 
     class MyProc(Process):
         """A simple example process that illustrates the usage of the new
         monitoring approach."""
-        def __init__(self, sim):
+        def __init__(self, sim, Collector):
             Process.__init__(self, sim=sim)
 
             self.a = 0
 
                 yield hold, self, 2
 
+    import time
 
-    # Run the simulation
-    sim = Simulation()
-    proc = MyProc(sim)
-    sim.activate(proc, proc.run())
-    sim.simulate(until=10)
+    s = time.clock()
+    benchmark(OrigCollector)
+    print time.clock() - s
 
-    # Just a test and a simple example how to acces the monitored values:
-    assert proc.monitor[1] == proc.monitor.a
+    s = time.clock()
+    benchmark(Collector)
+    print time.clock() - s
 
-    print proc.monitor
-    print zip(*proc.monitor)
+    ## Run the simulation
+    #sim = Simulation()
+    #proc = MyProc(sim)
+    #sim.activate(proc, proc.run())
+    #sim.simulate(until=10)
 
-    # NumPy helps you with the statistics and other calculations.
-    # Note: specification of dtype gives you a massive speed-up!
-    a = array(proc.monitor.a, dtype=float64)
-    print 'a:', a
-    # NumPy: average, std. deviation, variance
-    print 'a stats:', a.mean(), a.std(), a.var()
+    ## Just a test and a simple example how to acces the monitored values:
+    #assert proc.monitor[1] == proc.monitor.a
 
-    # This one creates a multi-dimensional array (see the output)
-    np_mon = array(proc.monitor, dtype=float64)
-    print 't, a, b, diff, square:\n', np_mon
-    # Get the average of all monitored proc.b
-    print 'b stats:', np_mon[2].mean()
-    # Get the std. deviation of all monitored proc.c
-    print 'c stats:', np_mon[3].std()
+    #print proc.monitor
+    #print zip(*proc.monitor)
 
-    # Matplotlib plots your data:
-    # Either directly from a monitor ...
-    plt.plot(proc.monitor.time, proc.monitor.a, 'b')
-    # ... or the NumPy array
-    plt.plot(np_mon[0], np_mon[2], 'r')
-    plt.show()
+    ## NumPy helps you with the statistics and other calculations.
+    ## Note: specification of dtype gives you a massive speed-up!
+    #a = array(proc.monitor.a, dtype=float64)
+    #print 'a:', a
+    ## NumPy: average, std. deviation, variance
+    #print 'a stats:', a.mean(), a.std(), a.var()
+
+    ## This one creates a multi-dimensional array (see the output)
+    #np_mon = array(proc.monitor, dtype=float64)
+    #print 't, a, b, diff, square:\n', np_mon
+    ## Get the average of all monitored proc.b
+    #print 'b stats:', np_mon[2].mean()
+    ## Get the std. deviation of all monitored proc.c
+    #print 'c stats:', np_mon[3].std()
+
+    ## Matplotlib plots your data:
+    ## Either directly from a monitor ...
+    #plt.plot(proc.monitor.time, proc.monitor.a, 'b')
+    ## ... or the NumPy array
+    #plt.plot(np_mon[0], np_mon[2], 'r')
+    #plt.show()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.