summaryrefslogtreecommitdiff
path: root/paste/profilemiddleware.py
blob: 445ffe95c11a19449f66b2f57064f935e2ed9f95 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import sys
import hotshot
import hotshot.stats
import threading
import cgi
from cStringIO import StringIO
from paste import wsgilib
from paste.docsupport import metadata

__all__ = ['ProfileMiddleware']

class ProfileMiddleware(object):

    """
    You can enable this middleware by adding this to your
    configuration::

        middleware.append('paste.profilemiddleware.ProfileMiddleware')

    All HTML pages will have profiling information appended to them.
    The data is isolated to that single request, and does not include
    data from previous requests.

    This uses the ``hotshot`` module, which effects performance of the
    application.  It also runs in a single-threaded mode, so it is
    only usable in development environments.
    """

    style = ('background-color: #ff9; color: #000; '
             'border: 2px solid #000; padding: 5px;')

    config_profile_log_filename = metadata.Config("""
    The filename to write profiling data to.
    """, default='profile.log')
    config_profile_limit = metadata.Config("""
    Only this number of function calls will be displayed.
    """, default=40)

    def __init__(self, application):
        self.application = application
        self.lock = threading.Lock()

    def __call__(self, environ, start_response):
        prof_filename = environ['paste.config'].get(
            'profile_log_filename', 'profile.log')
        display_limit = environ['paste.config'].get('profile_limit', 40)
        response = []
        body = []
        def replace_start_response(status, headers):
            response.extend([status, headers])
            start_response(status, headers)
            return body.append
        def run_app():
            body.extend(self.application(environ, replace_start_response))
        self.lock.acquire()
        try:
            prof = hotshot.Profile(prof_filename)
            prof.addinfo('URL', environ.get('PATH_INFO', ''))
            try:
                prof.runcall(run_app)
            finally:
                prof.close()
            body = ''.join(body)
            headers = response[1]
            content_type = wsgilib.header_value(headers, 'content-type')
            if not content_type.startswith('text/html'):
                # We can't add info to non-HTML output
                return [body]
            stats = hotshot.stats.load(prof_filename)
            stats.strip_dirs()
            stats.sort_stats('time', 'calls')
            output = capture_output(stats.print_stats, display_limit)
            output_callers = capture_output(
                stats.print_callers, display_limit)
            body += '<pre style="%s">%s\n%s</pre>' % (
                self.style, cgi.escape(output), cgi.escape(output_callers))
            return [body]
        finally:
            self.lock.release()

def capture_output(func, *args, **kw):
    # Not threadsafe! (that's okay when ProfileMiddleware uses it,
    # though, since it synchronizes itself.)
    out = StringIO()
    old_stdout = sys.stdout
    sys.stdout = out
    try:
        func(*args, **kw)
    finally:
        sys.stdout = old_stdout
    return out.getvalue()