Fixed python_path problem.
[smonitor.git] / lib / cherrypy / lib / profiler.py
blob785d58a3027c72f6db54ff16b784b65a581b8ce4
1 """Profiler tools for CherryPy.
3 CherryPy users
4 ==============
6 You can profile any of your pages as follows::
8 from cherrypy.lib import profiler
10 class Root:
11 p = profile.Profiler("/path/to/profile/dir")
13 def index(self):
14 self.p.run(self._index)
15 index.exposed = True
17 def _index(self):
18 return "Hello, world!"
20 cherrypy.tree.mount(Root())
22 You can also turn on profiling for all requests
23 using the ``make_app`` function as WSGI middleware.
25 CherryPy developers
26 ===================
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.
34 """
37 def new_func_strip_path(func_name):
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
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
52 import os, os.path
53 import sys
54 import warnings
56 from cherrypy._cpcompat import BytesIO
58 _count = 0
60 class Profiler(object):
62 def __init__(self, path=None):
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)
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
79 def statfiles(self):
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")]
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 # pstats.Stats before Python 2.5 didn't take a 'stream' arg,
96 # but just printed to stdout. So re-route stdout.
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
110 def index(self):
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>
119 index.exposed = True
121 def menu(self):
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
130 def report(self, filename):
131 import cherrypy
132 cherrypy.response.headers['Content-Type'] = 'text/plain'
133 return self.stats(filename)
134 report.exposed = True
137 class ProfileAggregator(Profiler):
139 def __init__(self, path=None):
140 Profiler.__init__(self, path)
141 global _count
142 self.count = _count = _count + 1
143 self.profiler = profile.Profile()
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
152 class make_app:
153 def __init__(self, nextapp, path=None, aggregate=False):
154 """Make a WSGI middleware app which wraps 'nextapp' with profiling.
156 nextapp
157 the WSGI application to wrap, usually an instance of
158 cherrypy.Application.
160 path
161 where to dump the profiling output.
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.
169 if profile is None or pstats is None:
170 msg = ("Your installation of Python does not have a profile module. "
171 "If you're on Debian, try `sudo apt-get install python-profiler`. "
172 "See http://www.cherrypy.org/wiki/ProfilingOnDebian for details.")
173 warnings.warn(msg)
175 self.nextapp = nextapp
176 self.aggregate = aggregate
177 if aggregate:
178 self.profiler = ProfileAggregator(path)
179 else:
180 self.profiler = Profiler(path)
182 def __call__(self, environ, start_response):
183 def gather():
184 result = []
185 for line in self.nextapp(environ, start_response):
186 result.append(line)
187 return result
188 return self.profiler.run(gather)
191 def serve(path=None, port=8080):
192 if profile is None or pstats is None:
193 msg = ("Your installation of Python does not have a profile module. "
194 "If you're on Debian, try `sudo apt-get install python-profiler`. "
195 "See http://www.cherrypy.org/wiki/ProfilingOnDebian for details.")
196 warnings.warn(msg)
198 import cherrypy
199 cherrypy.config.update({'server.socket_port': int(port),
200 'server.thread_pool': 10,
201 'environment': "production",
203 cherrypy.quickstart(Profiler(path))
206 if __name__ == "__main__":
207 serve(*tuple(sys.argv[1:]))