Package cherrypy :: Package test :: Module benchmark
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.test.benchmark

  1  """CherryPy Benchmark Tool 
  2   
  3      Usage: 
  4          benchmark.py --null --notests --help --cpmodpy --modpython --ab=path --apache=path 
  5       
  6      --null:        use a null Request object (to bench the HTTP server only) 
  7      --notests:     start the server but do not run the tests; this allows 
  8                     you to check the tested pages with a browser 
  9      --help:        show this help message 
 10      --cpmodpy:     run tests via apache on 8080 (with the builtin _cpmodpy) 
 11      --modpython:   run tests via apache on 8080 (with modpython_gateway) 
 12      --ab=path:     Use the ab script/executable at 'path' (see below) 
 13      --apache=path: Use the apache script/exe at 'path' (see below) 
 14       
 15      To run the benchmarks, the Apache Benchmark tool "ab" must either be on 
 16      your system path, or specified via the --ab=path option. 
 17       
 18      To run the modpython tests, the "apache" executable or script must be 
 19      on your system path, or provided via the --apache=path option. On some 
 20      platforms, "apache" may be called "apachectl" or "apache2ctl"--create 
 21      a symlink to them if needed. 
 22  """ 
 23   
 24  import getopt 
 25  import os 
 26  curdir = os.path.join(os.getcwd(), os.path.dirname(__file__)) 
 27   
 28  import re 
 29  import sys 
 30  import time 
 31  import traceback 
 32   
 33  import cherrypy 
 34  from cherrypy import _cperror, _cpmodpy 
 35  from cherrypy.lib import http 
 36   
 37   
 38  AB_PATH = "" 
 39  APACHE_PATH = "apache" 
 40  SCRIPT_NAME = "/cpbench/users/rdelon/apps/blog" 
 41   
 42  __all__ = ['ABSession', 'Root', 'print_report', 
 43             'run_standard_benchmarks', 'safe_threads', 
 44             'size_report', 'startup', 'thread_report', 
 45             ] 
 46   
 47  size_cache = {} 
 48   
49 -class Root:
50
51 - def index(self):
52 return """<html> 53 <head> 54 <title>CherryPy Benchmark</title> 55 </head> 56 <body> 57 <ul> 58 <li><a href="hello">Hello, world! (14 byte dynamic)</a></li> 59 <li><a href="static/index.html">Static file (14 bytes static)</a></li> 60 <li><form action="sizer">Response of length: 61 <input type='text' name='size' value='10' /></form> 62 </li> 63 </ul> 64 </body> 65 </html>"""
66 index.exposed = True 67
68 - def hello(self):
69 return "Hello, world\r\n"
70 hello.exposed = True 71
72 - def sizer(self, size):
73 resp = size_cache.get(size, None) 74 if resp is None: 75 size_cache[size] = resp = "X" * int(size) 76 return resp
77 sizer.exposed = True
78 79 80 cherrypy.config.update({ 81 'log.error.file': '', 82 'environment': 'production', 83 'server.socket_host': 'localhost', 84 'server.socket_port': 8080, 85 'server.max_request_header_size': 0, 86 'server.max_request_body_size': 0, 87 'engine.deadlock_poll_freq': 0, 88 }) 89 90 # Cheat mode on ;) 91 del cherrypy.config['tools.log_tracebacks.on'] 92 del cherrypy.config['tools.log_headers.on'] 93 del cherrypy.config['tools.trailing_slash.on'] 94 95 appconf = { 96 '/static': { 97 'tools.staticdir.on': True, 98 'tools.staticdir.dir': 'static', 99 'tools.staticdir.root': curdir, 100 }, 101 } 102 app = cherrypy.tree.mount(Root(), SCRIPT_NAME, appconf) 103 104
105 -class NullRequest:
106 """A null HTTP request class, returning 204 and an empty body.""" 107
108 - def __init__(self, local, remote, scheme="http"):
109 pass
110
111 - def close(self):
112 pass
113
114 - def run(self, method, path, query_string, protocol, headers, rfile):
115 cherrypy.response.status = "204 No Content" 116 cherrypy.response.header_list = [("Content-Type", 'text/html'), 117 ("Server", "Null CherryPy"), 118 ("Date", http.HTTPDate()), 119 ("Content-Length", "0"), 120 ] 121 cherrypy.response.body = [""] 122 return cherrypy.response
123 124
125 -class NullResponse:
126 pass
127 128
129 -class ABSession:
130 """A session of 'ab', the Apache HTTP server benchmarking tool. 131 132 Example output from ab: 133 134 This is ApacheBench, Version 2.0.40-dev <$Revision: 1.121.2.1 $> apache-2.0 135 Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 136 Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/ 137 138 Benchmarking localhost (be patient) 139 Completed 100 requests 140 Completed 200 requests 141 Completed 300 requests 142 Completed 400 requests 143 Completed 500 requests 144 Completed 600 requests 145 Completed 700 requests 146 Completed 800 requests 147 Completed 900 requests 148 149 150 Server Software: CherryPy/3.0.1alpha 151 Server Hostname: localhost 152 Server Port: 8080 153 154 Document Path: /static/index.html 155 Document Length: 14 bytes 156 157 Concurrency Level: 10 158 Time taken for tests: 9.643867 seconds 159 Complete requests: 1000 160 Failed requests: 0 161 Write errors: 0 162 Total transferred: 189000 bytes 163 HTML transferred: 14000 bytes 164 Requests per second: 103.69 [#/sec] (mean) 165 Time per request: 96.439 [ms] (mean) 166 Time per request: 9.644 [ms] (mean, across all concurrent requests) 167 Transfer rate: 19.08 [Kbytes/sec] received 168 169 Connection Times (ms) 170 min mean[+/-sd] median max 171 Connect: 0 0 2.9 0 10 172 Processing: 20 94 7.3 90 130 173 Waiting: 0 43 28.1 40 100 174 Total: 20 95 7.3 100 130 175 176 Percentage of the requests served within a certain time (ms) 177 50% 100 178 66% 100 179 75% 100 180 80% 100 181 90% 100 182 95% 100 183 98% 100 184 99% 110 185 100% 130 (longest request) 186 Finished 1000 requests 187 """ 188 189 parse_patterns = [('complete_requests', 'Completed', 190 r'^Complete requests:\s*(\d+)'), 191 ('failed_requests', 'Failed', 192 r'^Failed requests:\s*(\d+)'), 193 ('requests_per_second', 'req/sec', 194 r'^Requests per second:\s*([0-9.]+)'), 195 ('time_per_request_concurrent', 'msec/req', 196 r'^Time per request:\s*([0-9.]+).*concurrent requests\)$'), 197 ('transfer_rate', 'KB/sec', 198 r'^Transfer rate:\s*([0-9.]+)'), 199 ] 200
201 - def __init__(self, path=SCRIPT_NAME + "/hello", requests=1000, concurrency=10):
202 self.path = path 203 self.requests = requests 204 self.concurrency = concurrency
205
206 - def args(self):
207 port = cherrypy.server.socket_port 208 assert self.concurrency > 0 209 assert self.requests > 0 210 return ("-k -n %s -c %s http://localhost:%s%s" % 211 (self.requests, self.concurrency, port, self.path))
212
213 - def run(self):
214 # Parse output of ab, setting attributes on self 215 try: 216 self.output = _cpmodpy.read_process(AB_PATH or "ab", self.args()) 217 except: 218 print _cperror.format_exc() 219 raise 220 221 for attr, name, pattern in self.parse_patterns: 222 val = re.search(pattern, self.output, re.MULTILINE) 223 if val: 224 val = val.group(1) 225 setattr(self, attr, val) 226 else: 227 setattr(self, attr, None)
228 229 230 safe_threads = (25, 50, 100, 200, 400) 231 if sys.platform in ("win32",): 232 # For some reason, ab crashes with > 50 threads on my Win2k laptop. 233 safe_threads = (10, 20, 30, 40, 50) 234 235
236 -def thread_report(path=SCRIPT_NAME + "/hello", concurrency=safe_threads):
237 sess = ABSession(path) 238 attrs, names, patterns = zip(*sess.parse_patterns) 239 avg = dict.fromkeys(attrs, 0.0) 240 241 rows = [('threads',) + names] 242 for c in concurrency: 243 sess.concurrency = c 244 sess.run() 245 row = [c] 246 for attr in attrs: 247 val = getattr(sess, attr) 248 avg[attr] += float(val) 249 row.append(val) 250 rows.append(row) 251 252 # Add a row of averages. 253 rows.append(["Average"] + [str(avg[attr] / len(concurrency)) for attr in attrs]) 254 return rows
255
256 -def size_report(sizes=(10, 100, 1000, 10000, 100000, 100000000), 257 concurrency=50):
258 sess = ABSession(concurrency=concurrency) 259 attrs, names, patterns = zip(*sess.parse_patterns) 260 rows = [('bytes',) + names] 261 for sz in sizes: 262 sess.path = "%s/sizer?size=%s" % (SCRIPT_NAME, sz) 263 sess.run() 264 rows.append([sz] + [getattr(sess, attr) for attr in attrs]) 265 return rows
266 277 278
279 -def run_standard_benchmarks():
280 print 281 print ("Client Thread Report (1000 requests, 14 byte response body, " 282 "%s server threads):" % cherrypy.server.thread_pool) 283 print_report(thread_report()) 284 285 print 286 print ("Client Thread Report (1000 requests, 14 bytes via staticdir, " 287 "%s server threads):" % cherrypy.server.thread_pool) 288 print_report(thread_report("%s/static/index.html" % SCRIPT_NAME)) 289 290 print 291 print ("Size Report (1000 requests, 50 client threads, " 292 "%s server threads):" % cherrypy.server.thread_pool) 293 print_report(size_report())
294 295 296 # modpython and other WSGI # 297
298 -def startup_modpython(req=None):
299 """Start the CherryPy app server in 'serverless' mode (for modpython/WSGI).""" 300 if cherrypy.engine.state == cherrypy._cpengine.STOPPED: 301 if req: 302 if req.get_options().has_key("nullreq"): 303 cherrypy.engine.request_class = NullRequest 304 cherrypy.engine.response_class = NullResponse 305 ab_opt = req.get_options().get("ab", "") 306 if ab_opt: 307 global AB_PATH 308 AB_PATH = ab_opt 309 cherrypy.engine.start(blocking=False) 310 if cherrypy.engine.state == cherrypy._cpengine.STARTING: 311 cherrypy.engine.wait() 312 return 0 # apache.OK
313 314
315 -def run_modpython(use_wsgi=False):
316 print "Starting mod_python..." 317 pyopts = [] 318 319 # Pass the null and ab=path options through Apache 320 if "--null" in opts: 321 pyopts.append(("nullreq", "")) 322 323 if "--ab" in opts: 324 pyopts.append(("ab", opts["--ab"])) 325 326 s = _cpmodpy.ModPythonServer 327 if use_wsgi: 328 pyopts.append(("wsgi.application", "cherrypy::tree")) 329 pyopts.append(("wsgi.startup", "cherrypy.test.benchmark::startup_modpython")) 330 handler = "modpython_gateway::handler" 331 s = s(port=8080, opts=pyopts, apache_path=APACHE_PATH, handler=handler) 332 else: 333 pyopts.append(("cherrypy.setup", "cherrypy.test.benchmark::startup_modpython")) 334 s = s(port=8080, opts=pyopts, apache_path=APACHE_PATH) 335 336 try: 337 s.start() 338 run() 339 finally: 340 s.stop()
341 342 343 344 if __name__ == '__main__': 345 longopts = ['cpmodpy', 'modpython', 'null', 'notests', 346 'help', 'ab=', 'apache='] 347 try: 348 switches, args = getopt.getopt(sys.argv[1:], "", longopts) 349 opts = dict(switches) 350 except getopt.GetoptError: 351 print __doc__ 352 sys.exit(2) 353 354 if "--help" in opts: 355 print __doc__ 356 sys.exit(0) 357 358 if "--ab" in opts: 359 AB_PATH = opts['--ab'] 360 361 if "--notests" in opts: 362 # Return without stopping the server, so that the pages 363 # can be tested from a standard web browser.
364 - def run():
365 port = cherrypy.server.socket_port 366 print ("You may now open http://localhost:%s%s/" % 367 (port, SCRIPT_NAME)) 368 369 if "--null" in opts: 370 print "Using null Request object"
371 else:
372 - def run():
373 end = time.time() - start 374 print "Started in %s seconds" % end 375 if "--null" in opts: 376 print "\nUsing null Request object" 377 try: 378 run_standard_benchmarks() 379 finally: 380 cherrypy.engine.stop() 381 cherrypy.server.stop()
382 383 print "Starting CherryPy app server..." 384
385 - class