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

root/tags/cherrypy-3.0.0/cherrypy/test/benchmark.py

Revision 1332 (checked in by fumanchu, 2 years ago)

Changing version to 3.0.0beta.

  • 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 _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     def index(self):
51         return "Hello, world\r\n"
52     index.exposed = True
53    
54     def sizer(self, size):
55         resp = size_cache.get(size, None)
56         if resp is None:
57             size_cache[size] = resp = "X" * int(size)
58         return resp
59     sizer.exposed = True
60
61
62 cherrypy.config.update({
63     'log.error.file': '',
64     'environment': 'production',
65     'server.socket_host': 'localhost',
66     'server.socket_port': 8080,
67     'server.max_request_header_size': 0,
68     'server.max_request_body_size': 0,
69     })
70
71 appconf = {
72     '/static': {
73         'tools.staticdir.on': True,
74         'tools.staticdir.dir': 'static',
75         'tools.staticdir.root': curdir,
76         },
77     }
78 cherrypy.tree.mount(Root(), SCRIPT_NAME, appconf)
79
80
81 class NullRequest:
82     """A null HTTP request class, returning 204 and an empty body."""
83    
84     def __init__(self, local, remote, scheme="http"):
85         pass
86    
87     def close(self):
88         pass
89    
90     def run(self, method, path, query_string, protocol, headers, rfile):
91         cherrypy.response.status = "204 No Content"
92         cherrypy.response.header_list = [("Content-Type", 'text/html'),
93                                          ("Server", "Null CherryPy"),
94                                          ("Date", http.HTTPDate()),
95                                          ("Content-Length", "0"),
96                                          ]
97         cherrypy.response.body = [""]
98         return cherrypy.response
99
100
101 class NullResponse:
102     pass
103
104
105 class ABSession:
106     """A session of 'ab', the Apache HTTP server benchmarking tool.
107
108 Example output from ab:
109
110 This is ApacheBench, Version 2.0.40-dev <$Revision: 1.121.2.1 $> apache-2.0
111 Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
112 Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
113
114 Benchmarking localhost (be patient)
115 Completed 100 requests
116 Completed 200 requests
117 Completed 300 requests
118 Completed 400 requests
119 Completed 500 requests
120 Completed 600 requests
121 Completed 700 requests
122 Completed 800 requests
123 Completed 900 requests
124
125
126 Server Software:        CherryPy/3.0.0beta
127 Server Hostname:        localhost
128 Server Port:            8080
129
130 Document Path:          /static/index.html
131 Document Length:        14 bytes
132
133 Concurrency Level:      10
134 Time taken for tests:   9.643867 seconds
135 Complete requests:      1000
136 Failed requests:        0
137 Write errors:           0
138 Total transferred:      189000 bytes
139 HTML transferred:       14000 bytes
140 Requests per second:    103.69 [#/sec] (mean)
141 Time per request:       96.439 [ms] (mean)
142 Time per request:       9.644 [ms] (mean, across all concurrent requests)
143 Transfer rate:          19.08 [Kbytes/sec] received
144
145 Connection Times (ms)
146               min  mean[+/-sd] median   max
147 Connect:        0    0   2.9      0      10
148 Processing:    20   94   7.3     90     130
149 Waiting:        0   43  28.1     40     100
150 Total:         20   95   7.3    100     130
151
152 Percentage of the requests served within a certain time (ms)
153   50%    100
154   66%    100
155   75%    100
156   80%    100
157   90%    100
158   95%    100
159   98%    100
160   99%    110
161  100%    130 (longest request)
162 Finished 1000 requests
163 """
164    
165     parse_patterns = [('complete_requests', 'Completed',
166                        r'^Complete requests:\s*(\d+)'),
167                       ('failed_requests', 'Failed',
168                        r'^Failed requests:\s*(\d+)'),
169                       ('requests_per_second', 'req/sec',
170                        r'^Requests per second:\s*([0-9.]+)'),
171                       ('time_per_request_concurrent', 'msec/req',
172                        r'^Time per request:\s*([0-9.]+).*concurrent requests\)$'),
173                       ('transfer_rate', 'KB/sec',
174                        r'^Transfer rate:\s*([0-9.]+)'),
175                       ]
176    
177     def __init__(self, path=SCRIPT_NAME + "/", requests=1000, concurrency=10):
178         self.path = path
179         self.requests = requests
180         self.concurrency = concurrency
181    
182     def args(self):
183         port = cherrypy.server.socket_port
184         assert self.concurrency > 0
185         assert self.requests > 0
186         return ("-k -n %s -c %s http://localhost:%s%s" %
187                 (self.requests, self.concurrency, port, self.path))
188    
189     def run(self):
190         # Parse output of ab, setting attributes on self
191         self.output = _cpmodpy.read_process(AB_PATH or "ab", self.args())
192         for attr, name, pattern in self.parse_patterns:
193             val = re.search(pattern, self.output, re.MULTILINE)
194             if val:
195                 val = val.group(1)
196                 setattr(self, attr, val)
197             else:
198                 setattr(self, attr, None)
199
200
201 safe_threads = (25, 50, 100, 200, 400)
202 if sys.platform in ("win32",):
203     # For some reason, ab crashes with > 50 threads on my Win2k laptop.
204     safe_threads = (10, 20, 30, 40, 50)
205
206
207 def thread_report(path=SCRIPT_NAME + "/", concurrency=safe_threads):
208     sess = ABSession(path)
209     attrs, names, patterns = zip(*sess.parse_patterns)
210     rows = [('threads',) + names]
211     for c in concurrency:
212         sess.concurrency = c
213         sess.run()
214         rows.append([c] + [getattr(sess, attr) for attr in attrs])
215     return rows
216
217 def size_report(sizes=(10, 100, 1000, 10000, 100000, 100000000),
218                concurrency=50):
219     sess = ABSession(concurrency=concurrency)
220     attrs, names, patterns = zip(*sess.parse_patterns)
221     rows = [('bytes',) + names]
222     for sz in sizes:
223         sess.path = "%s/sizer?size=%s" % (SCRIPT_NAME, sz)
224         sess.run()
225         rows.append([sz] + [getattr(sess, attr) for attr in attrs])
226     return rows
227
228 def print_report(rows):
229     widths = []
230     for i in range(len(rows[0])):
231         lengths = [len(str(row[i])) for row in rows]
232         widths.append(max(lengths))
233     for row in rows:
234         print
235         for i, val in enumerate(row):
236             print str(val).rjust(widths[i]), "|",
237     print
238
239
240 def run_standard_benchmarks():
241     print
242     print ("Client Thread Report (1000 requests, 14 byte response body, "
243            "%s server threads):" % cherrypy.server.thread_pool)
244     print_report(thread_report())
245    
246     print
247     print ("Client Thread Report (1000 requests, 14 bytes via staticdir, "
248            "%s server threads):" % cherrypy.server.thread_pool)
249     print_report(thread_report("%s/static/index.html" % SCRIPT_NAME))
250    
251     print
252     print ("Size Report (1000 requests, 50 client threads, "
253            "%s server threads):" % cherrypy.server.thread_pool)
254     print_report(size_report())
255
256
257 #                         modpython and other WSGI                         #
258
259 def startup_modpython(req=None):
260     """Start the CherryPy app server in 'serverless' mode (for modpython/WSGI)."""
261     if cherrypy.engine.state == cherrypy._cpengine.STOPPED:
262         if req:
263             if req.get_options().has_key("nullreq"):
264                 cherrypy.engine.request_class = NullRequest
265                 cherrypy.engine.response_class = NullResponse
266             ab_opt = req.get_options().get("ab", "")
267             if ab_opt:
268                 global AB_PATH
269                 AB_PATH = ab_opt
270         cherrypy.engine.start(blocking=False)
271     if cherrypy.engine.state == cherrypy._cpengine.STARTING:
272         cherrypy.engine.wait()
273     return 0 # apache.OK
274
275
276 def run_modpython(use_wsgi=False):
277     print "Starting mod_python..."
278     pyopts = []
279    
280     # Pass the null and ab=path options through Apache
281     if "--null" in opts:
282         pyopts.append(("nullreq", ""))
283    
284     if "--ab" in opts:
285         pyopts.append(("ab", opts["--ab"]))
286    
287     s = _cpmodpy.ModPythonServer
288     if use_wsgi:
289         pyopts.append(("wsgi.application", "cherrypy::tree"))
290         pyopts.append(("wsgi.startup", "cherrypy.test.benchmark::startup_modpython"))
291         handler = "modpython_gateway::handler"
292         s = s(port=8080, opts=pyopts, apache_path=APACHE_PATH, handler=handler)
293     else:
294         pyopts.append(("cherrypy.setup", "cherrypy.test.benchmark::startup_modpython"))
295         s = s(port=8080, opts=pyopts, apache_path=APACHE_PATH)
296    
297     try:
298         s.start()
299         run()
300     finally:
301         s.stop()
302
303
304
305 if __name__ == '__main__':
306     longopts = ['cpmodpy', 'modpython', 'null', 'notests',
307                 'help', 'ab=', 'apache=']
308     try:
309         switches, args = getopt.getopt(sys.argv[1:], "", longopts)
310         opts = dict(switches)
311     except getopt.GetoptError:
312         print __doc__
313         sys.exit(2)
314    
315     if "--help" in opts:
316         print __doc__
317         sys.exit(0)
318    
319     if "--ab" in opts:
320         AB_PATH = opts['--ab']
321    
322     if "--notests" in opts:
323         # Return without stopping the server, so that the pages
324         # can be tested from a standard web browser.
325         def run():
326             if "--null" in opts:
327                 print "Using null Request object"
328     else:
329         def run():
330             end = time.time() - start
331             print "Started in %s seconds" % end
332             if "--null" in opts:
333                 print "\nUsing null Request object"
334             try:
335                 run_standard_benchmarks()
336             finally:
337                 cherrypy.server.stop()
338    
339     print "Starting CherryPy app server..."
340    
341     class NullWriter(object):
342         """Suppresses the printing of socket errors."""
343         def write(self, data):
344             pass
345     sys.stderr = NullWriter()
346    
347     start = time.time()
348    
349     if "--cpmodpy" in opts:
350         run_modpython()
351     elif "--modpython" in opts:
352         run_modpython(use_wsgi=True)
353     else:
354         if "--null" in opts:
355             cherrypy.server.request_class = NullRequest
356             cherrypy.server.response_class = NullResponse
357        
358         cherrypy.server.quickstart()
359         # This will block
360         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