# -*- coding: utf-8 -*- """ werkzeug.contrib.profiler ~~~~~~~~~~~~~~~~~~~~~~~~~ This module provides a simple WSGI profiler middleware for finding bottlenecks in web application. It uses the :mod:`profile` or :mod:`cProfile` module to do the profiling and writes the stats to the stream provided (defaults to stderr). Example usage:: from werkzeug.contrib.profiler import ProfilerMiddleware app = ProfilerMiddleware(app) :copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import sys import time import os.path try: try: from cProfile import Profile except ImportError: from profile import Profile from pstats import Stats available = True except ImportError: available = False class MergeStream(object): """An object that redirects `write` calls to multiple streams. Use this to log to both `sys.stdout` and a file:: f = open('profiler.log', 'w') stream = MergeStream(sys.stdout, f) profiler = ProfilerMiddleware(app, stream) """ def __init__(self, *streams): if not streams: raise TypeError('at least one stream must be given') self.streams = streams def write(self, data): for stream in self.streams: stream.write(data) class ProfilerMiddleware(object): """Simple profiler middleware. Wraps a WSGI application and profiles a request. This intentionally buffers the response so that timings are more exact. By giving the `profile_dir` argument, pstat.Stats files are saved to that directory, one file per request. Without it, a summary is printed to `stream` instead. For the exact meaning of `sort_by` and `restrictions` consult the :mod:`profile` documentation. .. versionadded:: 0.9 Added support for `restrictions` and `profile_dir`. :param app: the WSGI application to profile. :param stream: the stream for the profiled stats. defaults to stderr. :param sort_by: a tuple of columns to sort the result by. :param restrictions: a tuple of profiling strictions, not used if dumping to `profile_dir`. :param profile_dir: directory name to save pstat files """ def __init__(self, app, stream=None, sort_by=('time', 'calls'), restrictions=(), profile_dir=None): if not available: raise RuntimeError('the profiler is not available because ' 'profile or pstat is not installed.') self._app = app self._stream = stream or sys.stdout self._sort_by = sort_by self._restrictions = restrictions self._profile_dir = profile_dir def __call__(self, environ, start_response): response_body = [] def catching_start_response(status, headers, exc_info=None): start_response(status, headers, exc_info) return response_body.append def runapp(): appiter = self._app(environ, catching_start_response) response_body.extend(appiter) if hasattr(appiter, 'close'): appiter.close() p = Profile() start = time.time() p.runcall(runapp) body = b''.join(response_body) elapsed = time.time() - start if self._profile_dir is not None: prof_filename = os.path.join(self._profile_dir, '%s.%s.%06dms.%d.prof' % ( environ['REQUEST_METHOD'], environ.get('PATH_INFO').strip( '/').replace('/', '.') or 'root', elapsed * 1000.0, time.time() )) p.dump_stats(prof_filename) else: stats = Stats(p, stream=self._stream) stats.sort_stats(*self._sort_by) self._stream.write('-' * 80) self._stream.write('\nPATH: %r\n' % environ.get('PATH_INFO')) stats.print_stats(*self._restrictions) self._stream.write('-' * 80 + '\n\n') return [body] def make_action(app_factory, hostname='localhost', port=5000, threaded=False, processes=1, stream=None, sort_by=('time', 'calls'), restrictions=()): """Return a new callback for :mod:`werkzeug.script` that starts a local server with the profiler enabled. :: from werkzeug.contrib import profiler action_profile = profiler.make_action(make_app) """ def action(hostname=('h', hostname), port=('p', port), threaded=threaded, processes=processes): """Start a new development server.""" from werkzeug.serving import run_simple app = ProfilerMiddleware(app_factory(), stream, sort_by, restrictions) run_simple(hostname, port, app, False, None, threaded, processes) return action