OLD | NEW |
(Empty) | |
| 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 |
| 37 def new_func_strip_path(func_name): |
| 38 """Make profiler output more readable by adding ``__init__`` modules' parent
s""" |
| 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 |
| 60 class Profiler(object): |
| 61 |
| 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) |
| 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 |
| 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")] |
| 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 # 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 |
| 109 |
| 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> |
| 118 """ |
| 119 index.exposed = True |
| 120 |
| 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 |
| 129 |
| 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 |
| 135 |
| 136 |
| 137 class ProfileAggregator(Profiler): |
| 138 |
| 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() |
| 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 |
| 152 class make_app: |
| 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 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-profil
er`. " |
| 172 "See http://www.cherrypy.org/wiki/ProfilingOnDebian for detai
ls.") |
| 173 warnings.warn(msg) |
| 174 |
| 175 self.nextapp = nextapp |
| 176 self.aggregate = aggregate |
| 177 if aggregate: |
| 178 self.profiler = ProfileAggregator(path) |
| 179 else: |
| 180 self.profiler = Profiler(path) |
| 181 |
| 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) |
| 189 |
| 190 |
| 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) |
| 197 |
| 198 import cherrypy |
| 199 cherrypy.config.update({'server.socket_port': int(port), |
| 200 'server.thread_pool': 10, |
| 201 'environment': "production", |
| 202 }) |
| 203 cherrypy.quickstart(Profiler(path)) |
| 204 |
| 205 |
| 206 if __name__ == "__main__": |
| 207 serve(*tuple(sys.argv[1:])) |
| 208 |
OLD | NEW |