view MoinMoin/web/profile.py @ 4313:0ab9e5a38822

Code review: added doc strings, log exceptions
author Florian Krupicka <florian.krupicka@googlemail.com>
date Fri, 15 Aug 2008 14:56:42 +0200
parents fa648504e1cf
children 9f41f41891aa
line wrap: on
line source

# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - WSGI middlewares for profiling

    These have been ported from server_standalone to provide application
    profiling for a WSGI application.

    TODO: put them back in place, where the old profilers used to be

    @copyright: 2008 MoinMoin:FlorianKrupicka,
    @license: GNU GPL, see COPYING for details.
"""
from werkzeug.utils import get_current_url

from MoinMoin import log
logging = log.getLogger(__name__)

class ProfilerMiddleware(object):
    """ Abstract base class for profiling middlewares.

    Concrete implementations of this class should provide implementations
    of `run_profile` and `shutdown`, the latter which should be called by
    the code utilizing the profiler.
    """
    def __init__(self, app):
        self.app = app

    def profile(self, environ, start_response):
        """
        Profile the request. Exceptions encountered during the profile are
        logged before being propagated for further handling.
        """
        method = environ.get('REQUEST_METHOD', 'GET')
        url = get_current_url(environ)
        logging.debug("Profiling call for '%s %s'", method, url)
        try:
            res = self.run_profile(self.app, (environ, start_response))
        except Exception, e:
            logging.exception("Exception while profiling '%s %s'", method, url)
            raise
        return res

    __call__ = profile

    def run_profile(self, app, *args, **kwargs):
        """ Override in subclasses.

        Several profilers available for python use the same call signature,
        therefore simplifying the implementation.
        """
        raise NotImplementedError()

    def shutdown(self):
        """ Override in subclasses to clean up when server/script shuts down. """
        pass

class CProfileMiddleware(ProfilerMiddleware):
    """ A profiler based on the the cProfile module from the standard lib. """
    def __init__(self, app, filename):
        super(CProfileMiddleware, self).__init__(app)
        import cProfile
        self._profile = cProfile.Profile()
        self._filename = filename
        self.run_profile = self._profile.runcall

    def shutdown(self):
        self._profile.dump_stats(self._filename)

class HotshotMiddleware(ProfilerMiddleware):
    """ A profiler based on the more recent hotshot module from the stdlib. """
    def __init__(self, app, *args, **kwargs):
        super(HotshotMiddleware, self).__init__(app)
        import hotshot
        self._profile = hotshot.Profile(*args, **kwargs)
        self.run_profile = self._profile.runcall

    def shutdown(self):
        self._profile.close()

class PycallgraphMiddleware(ProfilerMiddleware):
    """ A call graphing middleware utilizing the pycallgraph 3rd party
    module (available at http://pycallgraph.slowchop.com/). """
    def __init__(self, app, filename):
        super(PycallgraphMiddleware, self).__init__(app)
        import pycallgraph
        pycallgraph.settings['include_stdlib'] = False
        self._filename = filename
        globs = ['pycallgraph.*', 'unknown.*']
        f = pycallgraph.GlobbingFilter(exclude=globs, max_depth=9999)
        self._filter = f
        self.pycallgraph = pycallgraph

    def run_profile(self, app, *args, **kwargs):
        pycallgraph = self.pycallgraph
        pycallgraph.start_trace(reset=True, filter_func=self._filter)
        try:
            return app(*args, **kwargs)
        finally:
            pycallgraph.stop_trace()

    def shutdown(self):
        fname = self._filename
        pycallgraph = self.pycallgraph
        if fname.endswith('.png'):
            logging.info("Saving the rendered callgraph to '%s'", fname)
            pycallgraph.make_dot_graph(fname)
        elif fname.endswith('.dot'):
            logging.info("Saving the raw callgraph to '%s'", fname)
            pycallgraph.save_dot(fname)