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

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

Revision 997 (checked in by fumanchu, 3 years ago)

Even more improvements to benchmark.py. New getopt options --ab=path and --apache=path. The modpython conf is now generated automatically.

  • Property svn:eol-style set to native
Line 
1 """CherryPy Benchmark Tool
2
3     Usage:
4         benchmark.py --null --notests --help --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 don't run the tests; this allows
8                    you to check the tested pages with a browser
9     --help:        show this help message
10     --modpython:   start up apache on 8080 (with a custom modpython
11                    config) and run the tests
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.lib import httptools
35
36
37 AB_PATH = ""
38 APACHE_PATH = ""
39 MOUNT_POINT = "/cpbench/users/rdelon/apps/blog"
40
41 __all__ = ['ABSession', 'Root', 'print_report', 'read_process',
42            'run_standard_benchmarks', 'safe_threads',
43            'size_report', 'startup', 'thread_report',
44            ]
45
46 size_cache = {}
47
48 class Root:
49     def index(self):
50         return "Hello, world\r\n"
51     index.exposed = True
52    
53     def sizer(self, size):
54         resp = size_cache.get(size, None)
55         if resp is None:
56             size_cache[size] = resp = "X" * int(size)
57         return resp
58     sizer.exposed = True
59
60
61 conf = {
62     'global': {
63         'server.log_to_screen': False,
64 ##        'server.log_file': os.path.join(curdir, "bench.log"),
65         'server.environment': 'production',
66         'server.socket_host': 'localhost',
67         'server.socket_port': 8080,
68         'server.max_request_header_size': 0,
69         'server.max_request_body_size': 0,
70         },
71     '/static': {
72         'static_filter.on': True,
73         'static_filter.dir': 'static',
74         'static_filter.root': curdir,
75         },
76     }
77 cherrypy.tree.mount(Root(), MOUNT_POINT, conf)
78 cherrypy.lowercase_api = True
79
80
81 class NullRequest:
82     """A null HTTP request class, returning 204 and an empty body."""
83    
84     def __init__(self, remoteAddr, remotePort, remoteHost, scheme="http"):
85         pass
86    
87     def close(self):
88         pass
89    
90     def run(self, requestLine, headers, rfile):
91         cherrypy.response.status = "204 No Content"
92         cherrypy.response.header_list = [("Content-Type", 'text/html'),
93                                          ("Server", "Null CherryPy"),
94                                          ("Date", httptools.HTTPDate()),
95                                          ("Content-Length", "0"),
96                                          ]
97         cherrypy.response.body = [""]
98         return cherrypy.response
99
100
101 class NullResponse:
102     pass
103
104
105 def read_process(cmd, args=""):
106     pipein, pipeout = os.popen4("%s %s" % (cmd, args))
107     try:
108         firstline = pipeout.readline()
109         if (re.search(r"(not recognized|No such file|not found)", firstline,
110                       re.IGNORECASE)):
111             raise IOError('%s must be on your system path.' % cmd)
112         output = firstline + pipeout.read()
113     finally:
114         pipeout.close()
115     return output
116
117
118 class ABSession:
119     """A session of 'ab', the Apache HTTP server benchmarking tool.
120
121 Example output from ab:
122
123 This is ApacheBench, Version 2.0.40-dev <$Revision: 1.121.2.1 $> apache-2.0
124 Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
125 Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
126
127 Benchmarking localhost (be patient)
128 Completed 100 requests
129 Completed 200 requests
130 Completed 300 requests
131 Completed 400 requests
132 Completed 500 requests
133 Completed 600 requests
134 Completed 700 requests
135 Completed 800 requests
136 Completed 900 requests
137
138
139 Server Software:        CherryPy/2.2.0beta
140 Server Hostname:        localhost
141 Server Port:            8080
142
143 Document Path:          /static/index.html
144 Document Length:        14 bytes
145
146 Concurrency Level:      10
147 Time taken for tests:   9.643867 seconds
148 Complete requests:      1000
149 Failed requests:        0
150 Write errors:           0
151 Total transferred:      189000 bytes
152 HTML transferred:       14000 bytes
153 Requests per second:    103.69 [#/sec] (mean)
154 Time per request:       96.439 [ms] (mean)
155 Time per request:       9.644 [ms] (mean, across all concurrent requests)
156 Transfer rate:          19.08 [Kbytes/sec] received
157
158 Connection Times (ms)
159               min  mean[+/-sd] median   max
160 Connect:        0    0   2.9      0      10
161 Processing:    20   94   7.3     90     130
162 Waiting:        0   43  28.1     40     100
163 Total:         20   95   7.3    100     130
164
165 Percentage of the requests served within a certain time (ms)
166   50%    100
167   66%    100
168   75%    100
169   80%    100
170   90%    100
171   95%    100
172   98%    100
173   99%    110
174  100%    130 (longest request)
175 Finished 1000 requests
176 """
177    
178     parse_patterns = [('complete_requests', 'Completed',
179                        r'^Complete requests:\s*(\d+)'),
180                       ('failed_requests', 'Failed',
181                        r'^Failed requests:\s*(\d+)'),
182                       ('requests_per_second', 'req/sec',
183                        r'^Requests per second:\s*([0-9.]+)'),
184                       ('time_per_request_concurrent', 'msec/req',
185                        r'^Time per request:\s*([0-9.]+).*concurrent requests\)$'),
186                       ('transfer_rate', 'KB/sec',
187                        r'^Transfer rate:\s*([0-9.]+)'),
188                       ]
189    
190     def __init__(self, path=MOUNT_POINT + "/", requests=1000, concurrency=10):
191         self.path = path
192         self.requests = requests
193         self.concurrency = concurrency
194    
195     def args(self):
196         port = cherrypy.config.get('server.socket_port')
197         assert self.concurrency > 0
198         assert self.requests > 0
199         return ("-n %s -c %s http://localhost:%s%s" %
200                 (self.requests, self.concurrency, port, self.path))
201    
202     def run(self):
203         # Parse output of ab, setting attributes on self
204         self.output = read_process(AB_PATH or "ab", self.args())
205         for attr, name, pattern in self.parse_patterns:
206             val = re.search(pattern, self.output, re.MULTILINE)
207             if val:
208                 val = val.group(1)
209                 setattr(self, attr, val)
210             else:
211                 setattr(self, attr, None)
212
213
214 safe_threads = (25, 50, 100, 200, 400)
215 if sys.platform in ("win32",):
216     # For some reason, ab crashes with > 50 threads on my Win2k laptop.
217     safe_threads = (10, 20, 30, 40, 50)
218
219
220 def thread_report(path=MOUNT_POINT + "/", concurrency=safe_threads):
221     sess = ABSession(path)
222     attrs, names, patterns = zip(*sess.parse_patterns)
223     rows = [('threads',) + names]
224     for c in concurrency:
225         sess.concurrency = c
226         sess.run()
227         rows.append([c] + [getattr(sess, attr) for attr in attrs])
228     return rows
229
230 def size_report(sizes=(1, 10, 50, 100, 100000, 100000000),
231                concurrency=50):
232     sess = ABSession(concurrency=concurrency)
233     attrs, names, patterns = zip(*sess.parse_patterns)
234     rows = [('bytes',) + names]
235     for sz in sizes:
236         sess.path = "%s/sizer?size=%s" % (MOUNT_POINT, sz)
237         sess.run()
238         rows.append([sz] + [getattr(sess, attr) for attr in attrs])
239     return rows
240
241 def print_report(rows):
242     widths = []
243     for i in range(len(rows[0])):
244         lengths = [len(str(row[i])) for row in rows]
245         widths.append(max(lengths))
246     for row in rows:
247         print
248         for i, val in enumerate(row):
249             print str(val).rjust(widths[i]), "|",
250     print
251
252
253 def run_standard_benchmarks():
254     print
255     print ("Client Thread Report (1000 requests, 14 byte response body, "
256            "%s server threads):" % cherrypy.config.get('server.thread_pool'))
257     print_report(thread_report())
258    
259     print
260     print ("Client Thread Report (1000 requests, 14 bytes via static_filter, "
261            "%s server threads):" % cherrypy.config.get('server.thread_pool'))
262     print_report(thread_report("%s/static/index.html" % MOUNT_POINT))
263    
264     print
265     print ("Size Report (1000 requests, 50 client threads, "
266            "%s server threads):" % cherrypy.config.get('server.thread_pool'))
267     print_report(size_report())
268
269
270 started = False
271 def startup(req=None):
272     """Start the CherryPy app server in 'serverless' mode (for WSGI)."""
273     global started
274     if not started:
275         started = True
276         cherrypy.server.start(init_only=True, server_class=None)
277     return 0 # apache.OK
278
279
280
281 #                         modpython and other WSGI                         #
282
283 def startup_modpython(req=None):
284     """Start the CherryPy app server in 'serverless' mode (for WSGI)."""
285     global started
286     if not started:
287         started = True
288         if req.get_options().has_key("nullreq"):
289             cherrypy.server.request_class = NullRequest
290             cherrypy.server.response_class = NullResponse
291         ab_opt = req.get_options().get("ab", "")
292         if ab_opt:
293             global AB_PATH
294             AB_PATH = ab_opt
295         cherrypy.server.start(init_only=True, server_class=None)
296    
297     import modpython_gateway
298     return modpython_gateway.handler(req)
299
300 mp_conf_template = """
301 # Apache2 server configuration file for benchmarking CherryPy with mod_python.
302
303 DocumentRoot "/"
304 Listen 8080
305 LoadModule python_module modules/mod_python.so
306
307 <Location />
308     SetHandler python-program
309     PythonHandler cherrypy.test.benchmark::startup_modpython
310     PythonOption application cherrypy._cpwsgi::wsgiApp
311     PythonDebug On
312 %s%s
313 </Location>
314 """
315
316 def run_modpython():
317     # Pass the null and ab=path options through Apache
318     nullreq_opt = ""
319     if "--null" in opts:
320         nullreq_opt = "    PythonOption nullreq\n"
321    
322     ab_opt = ""
323     if "--ab" in opts:
324         ab_opt = "    PythonOption ab %s\n" % opts["--ab"]
325    
326     conf_data = mp_conf_template % (ab_opt, nullreq_opt)
327     mpconf = os.path.join(curdir, "bench_mp.conf")
328    
329     f = open(mpconf, 'wb')
330     try:
331         f.write(conf_data)
332     finally:
333         f.close()
334    
335     apargs = "-k start -f %s" % mpconf
336     try:
337         read_process(APACHE_PATH or "apache", apargs)
338         run()
339     finally:
340         os.popen("apache -k stop")
341
342
343
344 if __name__ == '__main__':
345     longopts = ['modpython', 'null', 'notests', 'help', 'ab=', 'apache=']
346     try:
347         switches, args = getopt.getopt(sys.argv[1:], "", longopts)
348         opts = dict(switches)
349     except getopt.GetoptError:
350         print __doc__
351         sys.exit(2)
352    
353     if "--help" in opts:
354         print __doc__
355         sys.exit(0)
356    
357     if "--ab" in opts:
358         AB_PATH = opts['--ab']
359    
360     if "--notests" in opts:
361         # Return without stopping the server, so that the pages
362         # can be tested from a standard web browser.
363         def run():
364             if "--null" in opts:
365                 print "Using null Request object"
366     else:
367         def run():
368             end = time.time() - start
369             print "Started in %s seconds" % end
370             if "--null" in opts:
371                 print "\nUsing null Request object"
372             try:
373                 run_standard_benchmarks()
374             finally:
375                 cherrypy.server.stop()
376    
377     print "Starting CherryPy app server..."
378     start = time.time()
379    
380     if "--modpython" in opts:
381         run_modpython()
382     else:
383         if "--null" in opts:
384             cherrypy.server.request_class = NullRequest
385             cherrypy.server.response_class = NullResponse
386        
387         # This will block
388         cherrypy.server.start_with_callback(run)
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets