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 app.wsgiapp.pipeline = []
105
106
108 """A null HTTP request class, returning 204 and an empty body."""
109
110 - def __init__(self, local, remote, scheme="http"):
112
115
116 - def run(self, method, path, query_string, protocol, headers, rfile):
125
126
129
130
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
204 self.path = path
205 self.requests = requests
206 self.concurrency = concurrency
207
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
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:
384
385 print "Starting CherryPy app server..."
386
388 """Suppresses the printing of socket errors."""
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
406 cherrypy.engine.start_with_callback(run)
407