Back to index

moin  1.9.0~rc2
profile.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 """
00003     MoinMoin - WSGI middlewares for profiling
00004 
00005     These have been ported from server_standalone to provide application
00006     profiling for a WSGI application. They are implemented as WSGI
00007     middlewares, so they can be plugged right in front of the MoinMoin
00008     WSGI application. Attention has to be payed, that at the end of
00009     profiling the `shutdown`-method has to be invoked, so that the
00010     middlewares can write the reports to the filesystem.
00011 
00012     TODO: in pre-WSGI MoinMoin those profilers where integrated in
00013           the standalone server and also some other gateway interfaces.
00014           In the near future the middlewares here could be again made
00015           configurable automatically with command line switches or
00016           wiki configuration options.
00017 
00018     @copyright: 2008 MoinMoin:FlorianKrupicka,
00019     @license: GNU GPL, see COPYING for details.
00020 """
00021 from werkzeug import get_current_url
00022 
00023 from MoinMoin import log
00024 logging = log.getLogger(__name__)
00025 
00026 class ProfilerMiddleware(object):
00027     """ Abstract base class for profiling middlewares.
00028 
00029     Concrete implementations of this class should provide implementations
00030     of `run_profile` and `shutdown`, the latter which should be called by
00031     the code utilizing the profiler.
00032     """
00033     def __init__(self, app):
00034         self.app = app
00035 
00036     def profile(self, environ, start_response):
00037         """
00038         Profile the request. Exceptions encountered during the profile are
00039         logged before being propagated for further handling.
00040         """
00041         method = environ.get('REQUEST_METHOD', 'GET')
00042         url = get_current_url(environ)
00043         logging.debug("Profiling call for '%s %s'", method, url)
00044         try:
00045             res = self.run_profile(self.app, (environ, start_response))
00046         except Exception, e:
00047             logging.exception("Exception while profiling '%s %s'", method, url)
00048             raise
00049         return res
00050 
00051     __call__ = profile
00052 
00053     def run_profile(self, app, *args, **kwargs):
00054         """ Override in subclasses.
00055 
00056         Several profilers available for python use the same call signature,
00057         therefore simplifying the implementation.
00058         """
00059         raise NotImplementedError()
00060 
00061     def shutdown(self):
00062         """ Override in subclasses to clean up when server/script shuts down. """
00063         pass
00064 
00065 class CProfileMiddleware(ProfilerMiddleware):
00066     """ A profiler based on the the cProfile module from the standard lib. """
00067     def __init__(self, app, filename):
00068         super(CProfileMiddleware, self).__init__(app)
00069         import cProfile
00070         self._profile = cProfile.Profile()
00071         self._filename = filename
00072         self.run_profile = self._profile.runcall
00073 
00074     def shutdown(self):
00075         self._profile.dump_stats(self._filename)
00076 
00077 class HotshotMiddleware(ProfilerMiddleware):
00078     """ A profiler based on the more recent hotshot module from the stdlib. """
00079     def __init__(self, app, *args, **kwargs):
00080         super(HotshotMiddleware, self).__init__(app)
00081         import hotshot
00082         self._profile = hotshot.Profile(*args, **kwargs)
00083         self.run_profile = self._profile.runcall
00084 
00085     def shutdown(self):
00086         self._profile.close()
00087 
00088 class PycallgraphMiddleware(ProfilerMiddleware):
00089     """ A call graphing middleware utilizing the pycallgraph 3rd party
00090     module (available at http://pycallgraph.slowchop.com/). """
00091     def __init__(self, app, filename):
00092         super(PycallgraphMiddleware, self).__init__(app)
00093         import pycallgraph
00094         pycallgraph.settings['include_stdlib'] = False
00095         self._filename = filename
00096         globs = ['pycallgraph.*', 'unknown.*']
00097         f = pycallgraph.GlobbingFilter(exclude=globs, max_depth=9999)
00098         self._filter = f
00099         self.pycallgraph = pycallgraph
00100 
00101     def run_profile(self, app, *args, **kwargs):
00102         pycallgraph = self.pycallgraph
00103         pycallgraph.start_trace(reset=True, filter_func=self._filter)
00104         try:
00105             return app(*args, **kwargs)
00106         finally:
00107             pycallgraph.stop_trace()
00108 
00109     def shutdown(self):
00110         fname = self._filename
00111         pycallgraph = self.pycallgraph
00112         if fname.endswith('.png'):
00113             logging.info("Saving the rendered callgraph to '%s'", fname)
00114             pycallgraph.make_dot_graph(fname)
00115         elif fname.endswith('.dot'):
00116             logging.info("Saving the raw callgraph to '%s'", fname)
00117             pycallgraph.save_dot(fname)