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': '127.0.0.1', 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 127.0.0.1 (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.1.2 151 Server Hostname: 127.0.0.1 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 # Don't use "localhost". 211 # Cf http://mail.python.org/pipermail/python-win32/2008-March/007050.html 212 return ("-k -n %s -c %s http://127.0.0.1:%s%s" % 213 (self.requests, self.concurrency, port, self.path))
214
215 - def run(self):
216 # Parse output of ab, setting attributes on self 217 try: 218 self.output = _cpmodpy.read_process(AB_PATH or "ab", self.args()) 219 except: 220 print _cperror.format_exc() 221 raise 222 223 for attr, name, pattern in self.parse_patterns: 224 val = re.search(pattern, self.output, re.MULTILINE) 225 if val: 226 val = val.group(1) 227 setattr(self, attr, val) 228 else: 229 setattr(self, attr, None)
230 231 232 safe_threads = (25, 50, 100, 200, 400) 233 if sys.platform in ("win32",): 234 # For some reason, ab crashes with > 50 threads on my Win2k laptop. 235 safe_threads = (10, 20, 30, 40, 50) 236 237
238 -def thread_report(path=SCRIPT_NAME + "/hello", concurrency=safe_threads):
239 sess = ABSession(path) 240 attrs, names, patterns = zip(*sess.parse_patterns) 241 avg = dict.fromkeys(attrs, 0.0) 242 243 rows = [('threads',) + names] 244 for c in concurrency: 245 sess.concurrency = c 246 sess.run() 247 row = [c] 248 for attr in attrs: 249 val = getattr(sess, attr) 250 avg[attr] += float(val) 251 row.append(val) 252 rows.append(row) 253 254 # Add a row of averages. 255 rows.append(["Average"] + [str(avg[attr] / len(concurrency)) for attr in attrs]) 256 return rows
257
258 -def size_report(sizes=(10, 100, 1000, 10000, 100000, 100000000), 259 concurrency=50):
260 sess = ABSession(concurrency=concurrency) 261 attrs, names, patterns = zip(*sess.parse_patterns) 262 rows = [('bytes',) + names] 263 for sz in sizes: 264 sess.path = "%s/sizer?size=%s" % (SCRIPT_NAME, sz) 265 sess.run() 266 rows.append([sz] + [getattr(sess, attr) for attr in attrs]) 267 return rows
268 279 280
281 -def run_standard_benchmarks():
282 print 283 print ("Client Thread Report (1000 requests, 14 byte response body, " 284 "%s server threads):" % cherrypy.server.thread_pool) 285 print_report(thread_report()) 286 287 print 288 print ("Client Thread Report (1000 requests, 14 bytes via staticdir, " 289 "%s server threads):" % cherrypy.server.thread_pool) 290 print_report(thread_report("%s/static/index.html" % SCRIPT_NAME)) 291 292 print 293 print ("Size Report (1000 requests, 50 client threads, " 294 "%s server threads):" % cherrypy.server.thread_pool) 295 print_report(size_report())
296 297 298 # modpython and other WSGI # 299
300 -def startup_modpython(req=None):
301 """Start the CherryPy app server in 'serverless' mode (for modpython/WSGI).""" 302 if cherrypy.engine.state == cherrypy._cpengine.STOPPED: 303 if req: 304 if req.get_options().has_key("nullreq"): 305 cherrypy.engine.request_class = NullRequest 306 cherrypy.engine.response_class = NullResponse 307 ab_opt = req.get_options().get("ab", "") 308 if ab_opt: 309 global AB_PATH 310 AB_PATH = ab_opt 311 cherrypy.engine.start() 312 if cherrypy.engine.state == cherrypy._cpengine.STARTING: 313 cherrypy.engine.wait() 314 return 0 # apache.OK
315 316
317 -def run_modpython(use_wsgi=False):
318 print "Starting mod_python..." 319 pyopts = [] 320 321 # Pass the null and ab=path options through Apache 322 if "--null" in opts: 323 pyopts.append(("nullreq", "")) 324 325 if "--ab" in opts: 326 pyopts.append(("ab", opts["--ab"])) 327 328 s = _cpmodpy.ModPythonServer 329 if use_wsgi: 330 pyopts.append(("wsgi.application", "cherrypy::tree")) 331 pyopts.append(("wsgi.startup", "cherrypy.test.benchmark::startup_modpython")) 332 handler = "modpython_gateway::handler" 333 s = s(port=8080, opts=pyopts, apache_path=APACHE_PATH, handler=handler) 334 else: 335 pyopts.append(("cherrypy.setup", "cherrypy.test.benchmark::startup_modpython")) 336 s = s(port=8080, opts=pyopts, apache_path=APACHE_PATH) 337 338 try: 339 s.start() 340 run() 341 finally: 342 s.stop()
343 344 345 346 if __name__ == '__main__': 347 longopts = ['cpmodpy', 'modpython', 'null', 'notests', 348 'help', 'ab=', 'apache='] 349 try: 350 switches, args = getopt.getopt(sys.argv[1:], "", longopts) 351 opts = dict(switches) 352 except getopt.GetoptError: 353 print __doc__ 354 sys.exit(2) 355 356 if "--help" in opts: 357 print __doc__ 358 sys.exit(0) 359 360 if "--ab" in opts: 361 AB_PATH = opts['--ab'] 362 363 if "--notests" in opts: 364 # Return without stopping the server, so that the pages 365 # can be tested from a standard web browser.
366 - def run():
367 port = cherrypy.server.socket_port 368 print ("You may now open http://127.0.0.1:%s%s/" % 369 (port, SCRIPT_NAME)) 370 371 if "--null" in opts: 372 print "Using null Request object"
373 else:
374 - def run():
375 end = time.time() - start 376 print "Started in %s seconds" % end 377 if "--null" in opts: 378 print "\nUsing null Request object" 379 try: 380 run_standard_benchmarks() 381 finally: 382 cherrypy.engine.exit()
383 384 print "Starting CherryPy app server..." 385
386 - class NullWriter(object):
387 """Suppresses the printing of socket errors."""
388 - def write(self, data):
389 pass
390 sys.stderr = NullWriter() 391 392 start = time.time() 393 394 if "--cpmodpy" in opts: 395 run_modpython() 396 elif "--modpython" in opts: 397 run_modpython(use_wsgi=True) 398 else: 399 if "--null" in opts: 400 cherrypy.server.request_class = NullRequest 401 cherrypy.server.response_class = NullResponse 402 403 cherrypy.engine.start_with_callback(run) 404 cherrypy.engine.block() 405