You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					148 lines
				
				5.0 KiB
			
		
		
			
		
	
	
					148 lines
				
				5.0 KiB
			| 
								 
											8 years ago
										 
									 | 
							
								# -*- 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
							 |