mr.bent 1.0a1
Mr. Bent knows his numbers.
License: BSD
Keywords: statistics profiling

*A fussy little man in impeccable black jacket and pinstripe trousers.*

Introduction
------------

Mr Bent is a framework for allowing profile data to be collected in a Python
application and viewed at different logical levels. The three concepts
involved are a *plugin*, a piece of code that profiles an application, a
*context*, a logical block of code for which you want reporting data, and a
*filter*, a way of getting fine-grained information on where the results for a
context came from.

Plugins
-------

Plugins are callables that are given to the '''mkwrapper''' function which
applies it to a function in your application. 

This looks like::

    mr.bent.wrapper.mkwrapper(foo.bar, plugincallable, "myplugin")

Which will cause '''plugincallable''' to be called on every invocation of
'''foo.bar''' and add the results of the plugin to the current context as
'''myplugin'''.

Plugins can return either a number or an iterable. If it returns an iterable
it must contain **either** strings or numbers. The case of returning a number
is considered equivalent to returning an iterable of length 1 of numbers.

Contexts
--------

A context stores data generated by plugins. At any point a new context can be
started which will be a "sub-context" of the currently active context. If
there is no currently active context a new top-level one will be created.

Contexts are named with the dotted name of the function that they are created
around, and return their data to a callback.

This looks like::

    def mycallback(context, result, stats):
        return "%s <!-- %s -->" % (result, `stats`)
    
    mr.bent.wrapper.mkcontext(bar.foo, mycallback)

This example would cause invocations of bar.foo, a function that returns XML,
to return the XML with a repr of the context dict in a following comment.

When a context ends it returns a mapping of the data it collected. As contexts
are nested each time parent contexts include the data of their sub-contexts.
Hence, the top level context returns the overall profiling; there is no need
to manually aggregate data.

Filters
-------

A filter is, like most things in Mr. Bent, a wrapper around a function. This
will default to the dotted name of the callable, but an alternative,
application specific name can be used instead. This is especially useful for a
function that is used to render multiple different logical blocks of content.

This looks like::

    mr.bent.wrapper.mkfilter(take.me.to.the.foo.bar)

Concrete example
----------------

In this example we have an application that renders a page of HTML including
fragments that are logically different files which are then included into the
main page.

Example 1::

     .-------------.
     |  Top level  |
     `-------------'  
            |
            |         .--------------------.
            |---------|  Left hand column  |
            |         `--------------------'
            |                   |
            |                   |              .-------------.
            |                   |--------------|  Login box  |
            |                   |              `-------------'       
            |                   |
            |                   |
            |                   |              .------------------. 
            |                   `--------------|  Navigation box  | 
            |                                  `------------------'      
            |
            |         .-----------------.
            |---------|  Content block  |
            |         `-----------------'            
            |
            |            
            |         .---------------------.
            `---------|  Right hand column  |  
                      `---------------------'
                                |
                                |              .----------------.
                                `--------------|  Calendar box  |
                                               `----------------'

In this system we have the following notional plugins (with short names for
brevity):

:t:
    **A timing plugin**  This plugin returns the number of milliseconds 
    between it being invoked and it being stopped

:d:
    **A database access counting plugin** This plugin returns how many times
    data was retrieved from a database.

The return values may look something like this::   

    {'t': [5, 15, 85, 25], 'd': [0, 1, 2, 8]}
    .-------------.
    |  Top level  |
    `-------------'  
           |         {'t': [5, 15], 'd': [0,1]} 
           |         .--------------------.
           |---------|  Left hand column  |
           |         `--------------------'
           |                   |              {'t': [5], 'd': [0]} 
           |                   |              .-------------.
           |                   |--------------|  Login box  |
           |                   |              `-------------'       
           |                   |
           |                   |              {'t': [15], 'd': [1]} 
           |                   |              .------------------. 
           |                   `--------------|  Navigation box  | 
           |                                  `------------------'      
           |         {'t': [85], 'd': [2]} 
           |         .-----------------.
           |---------|  Content block  |
           |         `-----------------'            
           |
           |         {'t': [25], 'd': [8]}
           |         .---------------------.
           `---------|  Right hand column  |  
                     `---------------------'
                               |              {'t': [25], 'd': [8]} 
                               |              .----------------.
                               `--------------|  Calendar box  |
                                              `----------------'    
   

Hence, the user has data at each level he has defined which he can then
process as he likes.

Lets see that again as a doctest (sorry Florian!)::

    >>> from mr.bent.mavolio import create, destroy, current
    >>> create("top")           # Create the top level context
    
    
    >>> create("lefthand")      # Create the left hand column
    >>> create("login")         # and the login portlet
    >>> current()               # show that it's an empty context
    {}
    >>> current()['t'] = [5]    # Simulate plugin results being added to context
    >>> current()['d'] = [0]
    >>> destroy()               # Leave context
    {'t': [5], 'd': [0]}
    >>> create("nav")           # Create nav
    >>> current()['t']=[15]
    >>> current()['d']=[1]
    >>> destroy()               # Leave nav
    {'t': [15], 'd': [1]}
    >>> destroy()               # Leave left hand column
    {'t': [5, 15], 'd': [0, 1]}
    
    
    >>> create("content")       # Enter content block
    >>> current()['t'] = [85]
    >>> current()['d'] = [2]
    >>> destroy()               # Leave content block
    {'t': [85], 'd': [2]}
    
    
    >>> create("righthand")     # Enter right hand column
    >>> create("cal")           # Enter calendar box
    >>> current()['t']=[25]
    >>> current()['d']=[8]
    >>> destroy()               # Leave calendar
    {'t': [25], 'd': [8]}
    >>> destroy()               # Leave right hand column
    {'t': [25], 'd': [8]}
    
    
    >>> destroy()               # Leave the top level context, get totals
    {'t': [5, 15, 85, 25], 'd': [0, 1, 2, 8]}


Method reference
----------------

**Utility Methods**

:mr.bent.wrapper.mkwrapper(function, plugin, name): 
    Wraps a **function** with a **plugin** which writes its data to the 
    current context as **name**
:mr.bent.wrapper.mkcontext(function, callback): 
    Wraps a **function** to create a new context on invocation, and close it 
    when it finishes, and give the data to **callback** to handle reporting. 
:mr.bent.wrapper.mkfilter(function): 
    Wraps a **function** to be a key that context reporting data can be 
    filtered on.

**Low level methods**

:mr.bent.mavolio.create(name): 
    Creates a new context called **name**.
:mr.bent.mavolio.destroy(): 
    Ends the current context and returns the statistics.
:mr.bent.mavolio.current(): 
    Returns the current, in progress, context dict.


Changelog
=========

1.0a1 - Unreleased
------------------

* Initial release
  [matthewwilkes, fschulze, witsch]