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