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': '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
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 localhost (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.0.1alpha
151 Server Hostname: localhost
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 return ("-k -n %s -c %s http://localhost:%s%s" %
211 (self.requests, self.concurrency, port, self.path))
212
228
229
230 safe_threads = (25, 50, 100, 200, 400)
231 if sys.platform in ("win32",):
232
233 safe_threads = (10, 20, 30, 40, 50)
234
235
237 sess = ABSession(path)
238 attrs, names, patterns = zip(*sess.parse_patterns)
239 avg = dict.fromkeys(attrs, 0.0)
240
241 rows = [('threads',) + names]
242 for c in concurrency:
243 sess.concurrency = c
244 sess.run()
245 row = [c]
246 for attr in attrs:
247 val = getattr(sess, attr)
248 avg[attr] += float(val)
249 row.append(val)
250 rows.append(row)
251
252
253 rows.append(["Average"] + [str(avg[attr] / len(concurrency)) for attr in attrs])
254 return rows
255
256 -def size_report(sizes=(10, 100, 1000, 10000, 100000, 100000000),
257 concurrency=50):
258 sess = ABSession(concurrency=concurrency)
259 attrs, names, patterns = zip(*sess.parse_patterns)
260 rows = [('bytes',) + names]
261 for sz in sizes:
262 sess.path = "%s/sizer?size=%s" % (SCRIPT_NAME, sz)
263 sess.run()
264 rows.append([sz] + [getattr(sess, attr) for attr in attrs])
265 return rows
266
268 widths = []
269 for i in range(len(rows[0])):
270 lengths = [len(str(row[i])) for row in rows]
271 widths.append(max(lengths))
272 for row in rows:
273 print
274 for i, val in enumerate(row):
275 print str(val).rjust(widths[i]), "|",
276 print
277
278
294
295
296
297
313
314
316 print "Starting mod_python..."
317 pyopts = []
318
319
320 if "--null" in opts:
321 pyopts.append(("nullreq", ""))
322
323 if "--ab" in opts:
324 pyopts.append(("ab", opts["--ab"]))
325
326 s = _cpmodpy.ModPythonServer
327 if use_wsgi:
328 pyopts.append(("wsgi.application", "cherrypy::tree"))
329 pyopts.append(("wsgi.startup", "cherrypy.test.benchmark::startup_modpython"))
330 handler = "modpython_gateway::handler"
331 s = s(port=8080, opts=pyopts, apache_path=APACHE_PATH, handler=handler)
332 else:
333 pyopts.append(("cherrypy.setup", "cherrypy.test.benchmark::startup_modpython"))
334 s = s(port=8080, opts=pyopts, apache_path=APACHE_PATH)
335
336 try:
337 s.start()
338 run()
339 finally:
340 s.stop()
341
342
343
344 if __name__ == '__main__':
345 longopts = ['cpmodpy', 'modpython', 'null', 'notests',
346 'help', 'ab=', 'apache=']
347 try:
348 switches, args = getopt.getopt(sys.argv[1:], "", longopts)
349 opts = dict(switches)
350 except getopt.GetoptError:
351 print __doc__
352 sys.exit(2)
353
354 if "--help" in opts:
355 print __doc__
356 sys.exit(0)
357
358 if "--ab" in opts:
359 AB_PATH = opts['--ab']
360
361 if "--notests" in opts:
362
363
371 else:
382
383 print "Starting CherryPy app server..."
384