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 """
40 filename, line, name = func_name
41 if filename.endswith("__init__.py"):
42 return os.path.basename(filename[:-12]) + filename[-12:], line, name
43 return os.path.basename(filename), line, name
44
45 try:
46 import profile
47 import pstats
48 pstats.func_strip_path = new_func_strip_path
49 except ImportError:
50 profile = None
51 pstats = None
52
53 import os
54 import os.path
55 import sys
56 import warnings
57
58 from cherrypy._cpcompat import StringIO
59
60 _count = 0
61
62
64
66 if not path:
67 path = os.path.join(os.path.dirname(__file__), "profile")
68 self.path = path
69 if not os.path.exists(path):
70 os.makedirs(path)
71
72 - def run(self, func, *args, **params):
73 """Dump profile data into self.path."""
74 global _count
75 c = _count = _count + 1
76 path = os.path.join(self.path, "cp_%04d.prof" % c)
77 prof = profile.Profile()
78 result = prof.runcall(func, *args, **params)
79 prof.dump_stats(path)
80 return result
81
83 """:rtype: list of available profiles.
84 """
85 return [f for f in os.listdir(self.path)
86 if f.startswith("cp_") and f.endswith(".prof")]
87
88 - def stats(self, filename, sortby='cumulative'):
89 """:rtype stats(index): output of print_stats() for the given profile.
90 """
91 sio = StringIO()
92 if sys.version_info >= (2, 5):
93 s = pstats.Stats(os.path.join(self.path, filename), stream=sio)
94 s.strip_dirs()
95 s.sort_stats(sortby)
96 s.print_stats()
97 else:
98
99
100 s = pstats.Stats(os.path.join(self.path, filename))
101 s.strip_dirs()
102 s.sort_stats(sortby)
103 oldout = sys.stdout
104 try:
105 sys.stdout = sio
106 s.print_stats()
107 finally:
108 sys.stdout = oldout
109 response = sio.getvalue()
110 sio.close()
111 return response
112
114 return """<html>
115 <head><title>CherryPy profile data</title></head>
116 <frameset cols='200, 1*'>
117 <frame src='menu' />
118 <frame name='main' src='' />
119 </frameset>
120 </html>
121 """
122 index.exposed = True
123
125 yield "<h2>Profiling runs</h2>"
126 yield "<p>Click on one of the runs below to see profiling data.</p>"
127 runs = self.statfiles()
128 runs.sort()
129 for i in runs:
130 yield "<a href='report?filename=%s' target='main'>%s</a><br />" % (
131 i, i)
132 menu.exposed = True
133
138 report.exposed = True
139
140
142
148
149 - def run(self, func, *args, **params):
150 path = os.path.join(self.path, "cp_%04d.prof" % self.count)
151 result = self.profiler.runcall(func, *args, **params)
152 self.profiler.dump_stats(path)
153 return result
154
155
157
158 - def __init__(self, nextapp, path=None, aggregate=False):
159 """Make a WSGI middleware app which wraps 'nextapp' with profiling.
160
161 nextapp
162 the WSGI application to wrap, usually an instance of
163 cherrypy.Application.
164
165 path
166 where to dump the profiling output.
167
168 aggregate
169 if True, profile data for all HTTP requests will go in
170 a single file. If False (the default), each HTTP request will
171 dump its profile data into a separate file.
172
173 """
174
175
176
177
178
179
180
181
182
183
184
185 self.nextapp = nextapp
186 self.aggregate = aggregate
187 if aggregate:
188 self.profiler = ProfileAggregator(path)
189 else:
190 self.profiler = Profiler(path)
191
192 - def __call__(self, environ, start_response):
193 def gather():
194 result = []
195 for line in self.nextapp(environ, start_response):
196 result.append(line)
197 return result
198 return self.profiler.run(gather)
199
200
201 -def serve(path=None, port=8080):
202 if profile is None or pstats is None:
203 msg = ("Your installation of Python does not have a profile module. "
204 "If you're on Debian, try "
205 "`sudo apt-get install python-profiler`. "
206 "See http://www.cherrypy.org/wiki/ProfilingOnDebian "
207 "for details.")
208 warnings.warn(msg)
209
210 import cherrypy
211 cherrypy.config.update({'server.socket_port': int(port),
212 'server.thread_pool': 10,
213 'environment': "production",
214 })
215 cherrypy.quickstart(Profiler(path))
216
217
218 if __name__ == "__main__":
219 serve(*tuple(sys.argv[1:]))
220