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
52
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
71 return "Hello, world\r\n"
72 hello.exposed = True
73
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
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
108
109 """A null HTTP request class, returning 200 and an empty body."""
110
111 - def __init__(self, local, remote, scheme="http"):
113
116
117 - def run(self, method, path, query_string, protocol, headers, rfile):
126
127
130
131
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
208 self.path = path
209 self.requests = requests
210 self.concurrency = concurrency
211
213 port = cherrypy.server.socket_port
214 assert self.concurrency > 0
215 assert self.requests > 0
216
217
218
219 return ("-k -n %s -c %s http://127.0.0.1:%s%s" %
220 (self.requests, self.concurrency, port, self.path))
221
237
238
239 safe_threads = (25, 50, 100, 200, 400)
240 if sys.platform in ("win32",):
241
242 safe_threads = (10, 20, 30, 40, 50)
243
244
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
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
283 for row in rows:
284 print("")
285 for i, val in enumerate(row):
286 sys.stdout.write(str(val).rjust(10) + " | ")
287 print("")
288
289
305
306
307
308
325
326
328 print("Starting mod_python...")
329 pyopts = []
330
331
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
377
385 else:
399
400 print("Starting CherryPy app server...")
401
403
404 """Suppresses the printing of socket errors."""
405
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