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
50
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
69 return "Hello, world\r\n"
70 hello.exposed = True
71
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
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
106 """A null HTTP request class, returning 204 and an empty body."""
107
108 - def __init__(self, local, remote, scheme="http"):
110
113
114 - def run(self, method, path, query_string, protocol, headers, rfile):
123
124
127
128
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
202 self.path = path
203 self.requests = requests
204 self.concurrency = concurrency
205
207 port = cherrypy.server.socket_port
208 assert self.concurrency > 0
209 assert self.requests > 0
210
211
212 return ("-k -n %s -c %s http://127.0.0.1:%s%s" %
213 (self.requests, self.concurrency, port, self.path))
214
230
231
232 safe_threads = (25, 50, 100, 200, 400)
233 if sys.platform in ("win32",):
234
235 safe_threads = (10, 20, 30, 40, 50)
236
237
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
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
270 widths = []
271 for i in range(len(rows[0])):
272 lengths = [len(str(row[i])) for row in rows]
273 widths.append(max(lengths))
274 for row in rows:
275 print
276 for i, val in enumerate(row):
277 print str(val).rjust(widths[i]), "|",
278 print
279
280
296
297
298
299
315
316
318 print "Starting mod_python..."
319 pyopts = []
320
321
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
365
373 else:
383
384 print "Starting CherryPy app server..."
385
387 """Suppresses the printing of socket errors."""
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