1 """Profiler tools for CherryPy.
2
3 CherryPy users
4 ==============
5
6 You can profile any of your pages as follows::
7
8 from cherrypy.lib import profiler
9
10 class Root:
11 p = profile.Profiler("/path/to/profile/dir")
12
13 def index(self):
14 self.p.run(self._index)
15 index.exposed = True
16
17 def _index(self):
18 return "Hello, world!"
19
20 cherrypy.tree.mount(Root())
21
22 You can also turn on profiling for all requests
23 using the ``make_app`` function as WSGI middleware.
24
25 CherryPy developers
26 ===================
27
28 This module can be used whenever you make changes to CherryPy,
29 to get a quick sanity-check on overall CP performance. Use the
30 ``--profile`` flag when running the test suite. Then, use the ``serve()``
31 function to browse the results in a web browser. If you run this
32 module from the command line, it will call ``serve()`` for you.
33
34 """
35
36
38 """Make profiler output more readable by adding ``__init__`` modules' parents"""
39 filename, line, name = func_name
40 if filename.endswith("__init__.py"):
41 return os.path.basename(filename[:-12]) + filename[-12:], line, name
42 return os.path.basename(filename), line, name
43
44 try:
45 import profile
46 import pstats
47 pstats.func_strip_path = new_func_strip_path
48 except ImportError:
49 profile = None
50 pstats = None
51
52 import os, os.path
53 import sys
54 import warnings
55
56 from cherrypy._cpcompat import BytesIO
57
58 _count = 0
59
61
63 if not path:
64 path = os.path.join(os.path.dirname(__file__), "profile")
65 self.path = path
66 if not os.path.exists(path):
67 os.makedirs(path)
68
69 - def run(self, func, *args, **params):
70 """Dump profile data into self.path."""
71 global _count
72 c = _count = _count + 1
73 path = os.path.join(self.path, "cp_%04d.prof" % c)
74 prof = profile.Profile()
75 result = prof.runcall(func, *args, **params)
76 prof.dump_stats(path)
77 return result
78
80 """:rtype: list of available profiles.
81 """
82 return [f for f in os.listdir(self.path)
83 if f.startswith("cp_") and f.endswith(".prof")]
84
85 - def stats(self, filename, sortby='cumulative'):
86 """:rtype stats(index): output of print_stats() for the given profile.
87 """
88 sio = BytesIO()
89 if sys.version_info >= (2, 5):
90 s = pstats.Stats(os.path.join(self.path, filename), stream=sio)
91 s.strip_dirs()
92 s.sort_stats(sortby)
93 s.print_stats()
94 else:
95
96
97 s = pstats.Stats(os.path.join(self.path, filename))
98 s.strip_dirs()
99 s.sort_stats(sortby)
100 oldout = sys.stdout
101 try:
102 sys.stdout = sio
103 s.print_stats()
104 finally:
105 sys.stdout = oldout
106 response = sio.getvalue()
107 sio.close()
108 return response
109
111 return """<html>
112 <head><title>CherryPy profile data</title></head>
113 <frameset cols='200, 1*'>
114 <frame src='menu' />
115 <frame name='main' src='' />
116 </frameset>
117 </html>
118 """
119 index.exposed = True
120
122 yield "<h2>Profiling runs</h2>"
123 yield "<p>Click on one of the runs below to see profiling data.</p>"
124 runs = self.statfiles()
125 runs.sort()
126 for i in runs:
127 yield "<a href='report?filename=%s' target='main'>%s</a><br />" % (i, i)
128 menu.exposed = True
129
134 report.exposed = True
135
136
138
144
145 - def run(self, func, *args):
146 path = os.path.join(self.path, "cp_%04d.prof" % self.count)
147 result = self.profiler.runcall(func, *args)
148 self.profiler.dump_stats(path)
149 return result
150
151
153 - def __init__(self, nextapp, path=None, aggregate=False):
154 """Make a WSGI middleware app which wraps 'nextapp' with profiling.
155
156 nextapp
157 the WSGI application to wrap, usually an instance of
158 cherrypy.Application.
159
160 path
161 where to dump the profiling output.
162
163 aggregate
164 if True, profile data for all HTTP requests will go in
165 a single file. If False (the default), each HTTP request will
166 dump its profile data into a separate file.
167
168 """
169
170
171
172
173
174
175
176
177
178 self.nextapp = nextapp
179 self.aggregate = aggregate
180 if aggregate:
181 self.profiler = ProfileAggregator(path)
182 else:
183 self.profiler = Profiler(path)
184
185 - def __call__(self, environ, start_response):
186 def gather():
187 result = []
188 for line in self.nextapp(environ, start_response):
189 result.append(line)
190 return result
191 return self.profiler.run(gather)
192
193
194 -def serve(path=None, port=8080):
195 if profile is None or pstats is None:
196 msg = ("Your installation of Python does not have a profile module. "
197 "If you're on Debian, try `sudo apt-get install python-profiler`. "
198 "See http://www.cherrypy.org/wiki/ProfilingOnDebian for details.")
199 warnings.warn(msg)
200
201 import cherrypy
202 cherrypy.config.update({'server.socket_port': int(port),
203 'server.thread_pool': 10,
204 'environment': "production",
205 })
206 cherrypy.quickstart(Profiler(path))
207
208
209 if __name__ == "__main__":
210 serve(*tuple(sys.argv[1:]))
211