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 # Remove internalredirect (nastily on by default) 104 app.wsgiapp.pipeline = [] 105 106
107 -class NullRequest:
108 """A null HTTP request class, returning 204 and an empty body.""" 109
110 - def __init__(self, local, remote, scheme="http"):
111 pass
112
113 - def close(self):
114 pass
115
116 - def run(self, method, path, query_string, protocol, headers, rfile):
117 cherrypy.response.status = "204 No Content" 118 cherrypy.response.header_list = [("Content-Type", 'text/html'), 119 ("Server", "Null CherryPy"), 120 ("Date", http.HTTPDate()), 121 ("Content-Length", "0"), 122 ] 123 cherrypy.response.body = [""] 124 return cherrypy.response
125 126
127 -class NullResponse:
128 pass
129 130
131 -class ABSession:
132 """A session of 'ab', the Apache HTTP server benchmarking tool. 133 134 Example output from ab: 135 136 This is ApacheBench, Version 2.0.40-dev <$Revision: 1.121.2.1 $> apache-2.0 137 Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 138 Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/ 139 140 Benchmarking localhost (be patient) 141 Completed 100 requests 142 Completed 200 requests 143 Completed 300 requests 144 Completed 400 requests 145 Completed 500 requests 146 Completed 600 requests 147 Completed 700 requests 148 Completed 800 requests 149 Completed 900 requests 150 151 152 Server Software: CherryPy/3.0.1alpha 153 Server Hostname: localhost 154 Server Port: 8080 155 156 Document Path: /static/index.html 157 Document Length: 14 bytes 158 159 Concurrency Level: 10 160 Time taken for tests: 9.643867 seconds 161 Complete requests: 1000 162 Failed requests: 0 163 Write errors: 0 164 Total transferred: 189000 bytes 165 HTML transferred: 14000 bytes 166 Requests per second: 103.69 [#/sec] (mean) 167 Time per request: 96.439 [ms] (mean) 168 Time per request: 9.644 [ms] (mean, across all concurrent requests) 169 Transfer rate: 19.08 [Kbytes/sec] received 170 171 Connection Times (ms) 172 min mean[+/-sd] median max 173 Connect: 0 0 2.9 0 10 174 Processing: 20 94 7.3 90 130 175 Waiting: 0 43 28.1 40 100 176 Total: 20 95 7.3 100 130 177 178 Percentage of the requests served within a certain time (ms) 179 50% 100 180 66% 100 181 75% 100 182 80% 100 183 90% 100 184 95% 100 185 98% 100 186 99% 110 187 100% 130 (longest request) 188 Finished 1000 requests 189 """ 190 191 parse_patterns = [('complete_requests', 'Completed', 192 r'^Complete requests:\s*(\d+)'), 193 ('failed_requests', 'Failed', 194 r'^Failed requests:\s*(\d+)'), 195 ('requests_per_second', 'req/sec', 196 r'^Requests per second:\s*([0-9.]+)'), 197 ('time_per_request_concurrent', 'msec/req', 198 r'^Time per request:\s*([0-9.]+).*concurrent requests\)$'), 199 ('transfer_rate', 'KB/sec', 200 r'^Transfer rate:\s*([0-9.]+)'), 201 ] 202
203 - def __init__(self, path=SCRIPT_NAME + "/hello", requests=1000, concurrency=10):
204 self.path = path 205 self.requests = requests 206 self.concurrency = concurrency
207
208 - def args(self):
209 port = cherrypy.server.socket_port 210 assert self.concurrency > 0 211 assert self.requests > 0 212 return ("-k -n %s -c %s http://localhost:%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(blocking=False) 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://localhost:%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.stop() 383 cherrypy.server.stop()
384 385 print "Starting CherryPy app server..." 386
387 - class NullWriter(object):
388 """Suppresses the printing of socket errors."""
389 - def write(self, data):
390 pass
391 sys.stderr = NullWriter() 392 393 start = time.time() 394 395 if "--cpmodpy" in opts: 396 run_modpython() 397 elif "--modpython" in opts: 398 run_modpython(use_wsgi=True) 399 else: 400 if "--null" in opts: 401 cherrypy.server.request_class = NullRequest 402 cherrypy.server.response_class = NullResponse 403 404 cherrypy.server.quickstart() 405 # This will block 406 cherrypy.engine.start_with_callback(run) 407