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

root/trunk/cherrypy/test/benchmark.py

Revision 1908 (checked in by fumanchu, 6 months ago)

Print PID in test suite.

  • 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': '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 # 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 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.1beta
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    
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         # Don't use "localhost".
211         # Cf http://mail.python.org/pipermail/python-win32/2008-March/007050.html
212         return ("-k -n %s -c %s http://127.0.0.1:%s%s" %
213                 (self.requests, self.concurrency, port, self.path))
214    
215     def run(self):
216         # Parse output of ab, setting attributes on self
217         try:
218             self.output = _cpmodpy.read_process(AB_PATH or "ab", self.args())
219         except:
220             print _cperror.format_exc()
221             raise
222        
223         for attr, name, pattern in self.parse_patterns:
224             val = re.search(pattern, self.output, re.MULTILINE)
225             if val:
226                 val = val.group(1)
227                 setattr(self, attr, val)
228             else:
229                 setattr(self, attr, None)
230
231
232 safe_threads = (25, 50, 100, 200, 400)
233 if sys.platform in ("win32",):
234     # For some reason, ab crashes with > 50 threads on my Win2k laptop.
235     safe_threads = (10, 20, 30, 40, 50)
236
237
238 def thread_report(path=SCRIPT_NAME + "/hello", concurrency=safe_threads):
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     # Add a row of averages.
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
269 def print_report(rows):
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
281 def run_standard_benchmarks():
282     print
283     print ("Client Thread Report (1000 requests, 14 byte response body, "
284            "%s server threads):" % cherrypy.server.thread_pool)
285     print_report(thread_report())
286    
287     print
288     print ("Client Thread Report (1000 requests, 14 bytes via staticdir, "
289            "%s server threads):" % cherrypy.server.thread_pool)
290     print_report(thread_report("%s/static/index.html" % SCRIPT_NAME))
291    
292     print
293     print ("Size Report (1000 requests, 50 client threads, "
294            "%s server threads):" % cherrypy.server.thread_pool)
295     print_report(size_report())
296
297
298 #                         modpython and other WSGI                         #
299
300 def startup_modpython(req=None):
301     """Start the CherryPy app server in 'serverless' mode (for modpython/WSGI)."""
302     if cherrypy.engine.state == cherrypy._cpengine.STOPPED:
303         if req:
304             if req.get_options().has_key("nullreq"):
305                 cherrypy.engine.request_class = NullRequest
306                 cherrypy.engine.response_class = NullResponse
307             ab_opt = req.get_options().get("ab", "")
308             if ab_opt:
309                 global AB_PATH
310                 AB_PATH = ab_opt
311         cherrypy.engine.start()
312     if cherrypy.engine.state == cherrypy._cpengine.STARTING:
313         cherrypy.engine.wait()
314     return 0 # apache.OK
315
316
317 def run_modpython(use_wsgi=False):
318     print "Starting mod_python..."
319     pyopts = []
320    
321     # Pass the null and ab=path options through Apache
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         # Return without stopping the server, so that the pages
365         # can be tested from a standard web browser.
366         def run():
367             port = cherrypy.server.socket_port
368             print ("You may now open http://127.0.0.1:%s%s/" %
369                    (port, SCRIPT_NAME))
370            
371             if "--null" in opts:
372                 print "Using null Request object"
373     else:
374         def run():
375             end = time.time() - start
376             print "Started in %s seconds" % end
377             if "--null" in opts:
378                 print "\nUsing null Request object"
379             try:
380                 run_standard_benchmarks()
381             finally:
382                 cherrypy.engine.exit()
383    
384     print "Starting CherryPy app server..."
385    
386     class NullWriter(object):
387         """Suppresses the printing of socket errors."""
388         def write(self, data):
389             pass
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()
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets