Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/branches/cherrypy-3.0.x/cherrypy/test/benchmark.py

Revision 1711 (checked in by fumanchu, 1 year ago)

Great progress on #718 (High count of uncollectable objects). Folded the separate InternalRedirect? WSGI middleware back into AppResponse?.

  • Property svn:eol-style set to native
Line 
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
49 class Root:
50    
51     def index(self):
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    
68     def hello(self):
69         return "Hello, world\r\n"
70     hello.exposed = True
71    
72     def sizer(self, size):
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 # Cheat mode on ;)
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
105 class NullRequest:
106     """A null HTTP request class, returning 204 and an empty body."""
107    
108     def __init__(self, local, remote, scheme="http"):
109         pass
110    
111     def close(self):
112         pass
113    
114     def run(self, method, path, query_string, protocol, headers, rfile):
115         cherrypy.response.status = "204 No Content"
116         cherrypy.response.header_list = [("Content-Type", 'text/html'),
117                                          ("Server", "Null CherryPy"),
118                                          ("Date", http.HTTPDate()),
119                                          ("Content-Length", "0"),
120                                          ]
121         cherrypy.response.body = [""]
122         return cherrypy.response
123
124
125 class NullResponse:
126     pass
127
128
129 class ABSession:
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    
201     def __init__(self, path=SCRIPT_NAME + "/hello", requests=1000, concurrency=10):
202         self.path = path
203         self.requests = requests
204         self.concurrency = concurrency
205    
206     def args(self):
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    
213     def run(self):
214         # Parse output of ab, setting attributes on self
215         try:
216             self.output = _cpmodpy.read_process(AB_PATH or "ab", self.args())
217         except:
218             print _cperror.format_exc()
219             raise
220        
221         for attr, name, pattern in self.parse_patterns:
222             val = re.search(pattern, self.output, re.MULTILINE)
223             if val:
224                 val = val.group(1)
225                 setattr(self, attr, val)
226             else:
227                 setattr(self, attr, None)
228
229
230 safe_threads = (25, 50, 100, 200, 400)
231 if sys.platform in ("win32",):
232     # For some reason, ab crashes with > 50 threads on my Win2k laptop.
233     safe_threads = (10, 20, 30, 40, 50)
234
235
236 def thread_report(path=SCRIPT_NAME + "/hello", concurrency=safe_threads):
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     # Add a row of averages.
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
267 def print_report(rows):
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
279 def run_standard_benchmarks():
280     print
281     print ("Client Thread Report (1000 requests, 14 byte response body, "
282            "%s server threads):" % cherrypy.server.thread_pool)
283     print_report(thread_report())
284    
285     print
286     print ("Client Thread Report (1000 requests, 14 bytes via staticdir, "
287            "%s server threads):" % cherrypy.server.thread_pool)
288     print_report(thread_report("%s/static/index.html" % SCRIPT_NAME))
289    
290     print
291     print ("Size Report (1000 requests, 50 client threads, "
292            "%s server threads):" % cherrypy.server.thread_pool)
293     print_report(size_report())
294
295
296 #                         modpython and other WSGI                         #
297
298 def startup_modpython(req=None):
299     """Start the CherryPy app server in 'serverless' mode (for modpython/WSGI)."""
300     if cherrypy.engine.state == cherrypy._cpengine.STOPPED:
301         if req:
302             if req.get_options().has_key("nullreq"):
303                 cherrypy.engine.request_class = NullRequest
304                 cherrypy.engine.response_class = NullResponse
305             ab_opt = req.get_options().get("ab", "")
306             if ab_opt:
307                 global AB_PATH
308                 AB_PATH = ab_opt
309         cherrypy.engine.start(blocking=False)
310     if cherrypy.engine.state == cherrypy._cpengine.STARTING:
311         cherrypy.engine.wait()
312     return 0 # apache.OK
313
314
315 def run_modpython(use_wsgi=False):
316     print "Starting mod_python..."
317     pyopts = []
318    
319     # Pass the null and ab=path options through Apache
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         # Return without stopping the server, so that the pages
363         # can be tested from a standard web browser.
364         def run():
365             port = cherrypy.server.socket_port
366             print ("You may now open http://localhost:%s%s/" %
367                    (port, SCRIPT_NAME))
368            
369             if "--null" in opts:
370                 print "Using null Request object"
371     else:
372         def run():
373             end = time.time() - start
374             print "Started in %s seconds" % end
375             if "--null" in opts:
376                 print "\nUsing null Request object"
377             try:
378                 run_standard_benchmarks()
379             finally:
380                 cherrypy.engine.stop()
381                 cherrypy.server.stop()
382    
383     print "Starting CherryPy app server..."
384    
385     class NullWriter(object):
386         """Suppresses the printing of socket errors."""
387         def write(self, data):
388             pass
389     sys.stderr = NullWriter()
390    
391     start = time.time()
392    
393     if "--cpmodpy" in opts:
394         run_modpython()
395     elif "--modpython" in opts:
396         run_modpython(use_wsgi=True)
397     else:
398         if "--null" in opts:
399             cherrypy.server.request_class = NullRequest
400             cherrypy.server.response_class = NullResponse
401        
402         cherrypy.server.quickstart()
403         # This will block
404         cherrypy.engine.start_with_callback(run)
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets