mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-16 02:02:58 -07:00
Update cherrpy to 17.4.2
This commit is contained in:
parent
f28e741ad7
commit
4d6279a626
131 changed files with 15864 additions and 10389 deletions
425
lib/cherrypy/test/benchmark.py
Normal file
425
lib/cherrypy/test/benchmark.py
Normal file
|
@ -0,0 +1,425 @@
|
|||
"""CherryPy Benchmark Tool
|
||||
|
||||
Usage:
|
||||
benchmark.py [options]
|
||||
|
||||
--null: use a null Request object (to bench the HTTP server only)
|
||||
--notests: start the server but do not run the tests; this allows
|
||||
you to check the tested pages with a browser
|
||||
--help: show this help message
|
||||
--cpmodpy: run tests via apache on 54583 (with the builtin _cpmodpy)
|
||||
--modpython: run tests via apache on 54583 (with modpython_gateway)
|
||||
--ab=path: Use the ab script/executable at 'path' (see below)
|
||||
--apache=path: Use the apache script/exe at 'path' (see below)
|
||||
|
||||
To run the benchmarks, the Apache Benchmark tool "ab" must either be on
|
||||
your system path, or specified via the --ab=path option.
|
||||
|
||||
To run the modpython tests, the "apache" executable or script must be
|
||||
on your system path, or provided via the --apache=path option. On some
|
||||
platforms, "apache" may be called "apachectl" or "apache2ctl"--create
|
||||
a symlink to them if needed.
|
||||
"""
|
||||
|
||||
import getopt
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
import cherrypy
|
||||
from cherrypy import _cperror, _cpmodpy
|
||||
from cherrypy.lib import httputil
|
||||
|
||||
|
||||
curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
|
||||
|
||||
AB_PATH = ''
|
||||
APACHE_PATH = 'apache'
|
||||
SCRIPT_NAME = '/cpbench/users/rdelon/apps/blog'
|
||||
|
||||
__all__ = ['ABSession', 'Root', 'print_report',
|
||||
'run_standard_benchmarks', 'safe_threads',
|
||||
'size_report', 'thread_report',
|
||||
]
|
||||
|
||||
size_cache = {}
|
||||
|
||||
|
||||
class Root:
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return """<html>
|
||||
<head>
|
||||
<title>CherryPy Benchmark</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li><a href="hello">Hello, world! (14 byte dynamic)</a></li>
|
||||
<li><a href="static/index.html">Static file (14 bytes static)</a></li>
|
||||
<li><form action="sizer">Response of length:
|
||||
<input type='text' name='size' value='10' /></form>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
@cherrypy.expose
|
||||
def hello(self):
|
||||
return 'Hello, world\r\n'
|
||||
|
||||
@cherrypy.expose
|
||||
def sizer(self, size):
|
||||
resp = size_cache.get(size, None)
|
||||
if resp is None:
|
||||
size_cache[size] = resp = 'X' * int(size)
|
||||
return resp
|
||||
|
||||
|
||||
def init():
|
||||
|
||||
cherrypy.config.update({
|
||||
'log.error.file': '',
|
||||
'environment': 'production',
|
||||
'server.socket_host': '127.0.0.1',
|
||||
'server.socket_port': 54583,
|
||||
'server.max_request_header_size': 0,
|
||||
'server.max_request_body_size': 0,
|
||||
})
|
||||
|
||||
# Cheat mode on ;)
|
||||
del cherrypy.config['tools.log_tracebacks.on']
|
||||
del cherrypy.config['tools.log_headers.on']
|
||||
del cherrypy.config['tools.trailing_slash.on']
|
||||
|
||||
appconf = {
|
||||
'/static': {
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': 'static',
|
||||
'tools.staticdir.root': curdir,
|
||||
},
|
||||
}
|
||||
globals().update(
|
||||
app=cherrypy.tree.mount(Root(), SCRIPT_NAME, appconf),
|
||||
)
|
||||
|
||||
|
||||
class NullRequest:
|
||||
|
||||
"""A null HTTP request class, returning 200 and an empty body."""
|
||||
|
||||
def __init__(self, local, remote, scheme='http'):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def run(self, method, path, query_string, protocol, headers, rfile):
|
||||
cherrypy.response.status = '200 OK'
|
||||
cherrypy.response.header_list = [('Content-Type', 'text/html'),
|
||||
('Server', 'Null CherryPy'),
|
||||
('Date', httputil.HTTPDate()),
|
||||
('Content-Length', '0'),
|
||||
]
|
||||
cherrypy.response.body = ['']
|
||||
return cherrypy.response
|
||||
|
||||
|
||||
class NullResponse:
|
||||
pass
|
||||
|
||||
|
||||
class ABSession:
|
||||
|
||||
"""A session of 'ab', the Apache HTTP server benchmarking tool.
|
||||
|
||||
Example output from ab:
|
||||
|
||||
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.121.2.1 $> apache-2.0
|
||||
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
|
||||
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
|
||||
|
||||
Benchmarking 127.0.0.1 (be patient)
|
||||
Completed 100 requests
|
||||
Completed 200 requests
|
||||
Completed 300 requests
|
||||
Completed 400 requests
|
||||
Completed 500 requests
|
||||
Completed 600 requests
|
||||
Completed 700 requests
|
||||
Completed 800 requests
|
||||
Completed 900 requests
|
||||
|
||||
|
||||
Server Software: CherryPy/3.1beta
|
||||
Server Hostname: 127.0.0.1
|
||||
Server Port: 54583
|
||||
|
||||
Document Path: /static/index.html
|
||||
Document Length: 14 bytes
|
||||
|
||||
Concurrency Level: 10
|
||||
Time taken for tests: 9.643867 seconds
|
||||
Complete requests: 1000
|
||||
Failed requests: 0
|
||||
Write errors: 0
|
||||
Total transferred: 189000 bytes
|
||||
HTML transferred: 14000 bytes
|
||||
Requests per second: 103.69 [#/sec] (mean)
|
||||
Time per request: 96.439 [ms] (mean)
|
||||
Time per request: 9.644 [ms] (mean, across all concurrent requests)
|
||||
Transfer rate: 19.08 [Kbytes/sec] received
|
||||
|
||||
Connection Times (ms)
|
||||
min mean[+/-sd] median max
|
||||
Connect: 0 0 2.9 0 10
|
||||
Processing: 20 94 7.3 90 130
|
||||
Waiting: 0 43 28.1 40 100
|
||||
Total: 20 95 7.3 100 130
|
||||
|
||||
Percentage of the requests served within a certain time (ms)
|
||||
50% 100
|
||||
66% 100
|
||||
75% 100
|
||||
80% 100
|
||||
90% 100
|
||||
95% 100
|
||||
98% 100
|
||||
99% 110
|
||||
100% 130 (longest request)
|
||||
Finished 1000 requests
|
||||
"""
|
||||
|
||||
parse_patterns = [
|
||||
('complete_requests', 'Completed',
|
||||
br'^Complete requests:\s*(\d+)'),
|
||||
('failed_requests', 'Failed',
|
||||
br'^Failed requests:\s*(\d+)'),
|
||||
('requests_per_second', 'req/sec',
|
||||
br'^Requests per second:\s*([0-9.]+)'),
|
||||
('time_per_request_concurrent', 'msec/req',
|
||||
br'^Time per request:\s*([0-9.]+).*concurrent requests\)$'),
|
||||
('transfer_rate', 'KB/sec',
|
||||
br'^Transfer rate:\s*([0-9.]+)')
|
||||
]
|
||||
|
||||
def __init__(self, path=SCRIPT_NAME + '/hello', requests=1000,
|
||||
concurrency=10):
|
||||
self.path = path
|
||||
self.requests = requests
|
||||
self.concurrency = concurrency
|
||||
|
||||
def args(self):
|
||||
port = cherrypy.server.socket_port
|
||||
assert self.concurrency > 0
|
||||
assert self.requests > 0
|
||||
# Don't use "localhost".
|
||||
# Cf
|
||||
# http://mail.python.org/pipermail/python-win32/2008-March/007050.html
|
||||
return ('-k -n %s -c %s http://127.0.0.1:%s%s' %
|
||||
(self.requests, self.concurrency, port, self.path))
|
||||
|
||||
def run(self):
|
||||
# Parse output of ab, setting attributes on self
|
||||
try:
|
||||
self.output = _cpmodpy.read_process(AB_PATH or 'ab', self.args())
|
||||
except Exception:
|
||||
print(_cperror.format_exc())
|
||||
raise
|
||||
|
||||
for attr, name, pattern in self.parse_patterns:
|
||||
val = re.search(pattern, self.output, re.MULTILINE)
|
||||
if val:
|
||||
val = val.group(1)
|
||||
setattr(self, attr, val)
|
||||
else:
|
||||
setattr(self, attr, None)
|
||||
|
||||
|
||||
safe_threads = (25, 50, 100, 200, 400)
|
||||
if sys.platform in ('win32',):
|
||||
# For some reason, ab crashes with > 50 threads on my Win2k laptop.
|
||||
safe_threads = (10, 20, 30, 40, 50)
|
||||
|
||||
|
||||
def thread_report(path=SCRIPT_NAME + '/hello', concurrency=safe_threads):
|
||||
sess = ABSession(path)
|
||||
attrs, names, patterns = list(zip(*sess.parse_patterns))
|
||||
avg = dict.fromkeys(attrs, 0.0)
|
||||
|
||||
yield ('threads',) + names
|
||||
for c in concurrency:
|
||||
sess.concurrency = c
|
||||
sess.run()
|
||||
row = [c]
|
||||
for attr in attrs:
|
||||
val = getattr(sess, attr)
|
||||
if val is None:
|
||||
print(sess.output)
|
||||
row = None
|
||||
break
|
||||
val = float(val)
|
||||
avg[attr] += float(val)
|
||||
row.append(val)
|
||||
if row:
|
||||
yield row
|
||||
|
||||
# Add a row of averages.
|
||||
yield ['Average'] + [str(avg[attr] / len(concurrency)) for attr in attrs]
|
||||
|
||||
|
||||
def size_report(sizes=(10, 100, 1000, 10000, 100000, 100000000),
|
||||
concurrency=50):
|
||||
sess = ABSession(concurrency=concurrency)
|
||||
attrs, names, patterns = list(zip(*sess.parse_patterns))
|
||||
yield ('bytes',) + names
|
||||
for sz in sizes:
|
||||
sess.path = '%s/sizer?size=%s' % (SCRIPT_NAME, sz)
|
||||
sess.run()
|
||||
yield [sz] + [getattr(sess, attr) for attr in attrs]
|
||||
|
||||
|
||||
def print_report(rows):
|
||||
for row in rows:
|
||||
print('')
|
||||
for val in row:
|
||||
sys.stdout.write(str(val).rjust(10) + ' | ')
|
||||
print('')
|
||||
|
||||
|
||||
def run_standard_benchmarks():
|
||||
print('')
|
||||
print('Client Thread Report (1000 requests, 14 byte response body, '
|
||||
'%s server threads):' % cherrypy.server.thread_pool)
|
||||
print_report(thread_report())
|
||||
|
||||
print('')
|
||||
print('Client Thread Report (1000 requests, 14 bytes via staticdir, '
|
||||
'%s server threads):' % cherrypy.server.thread_pool)
|
||||
print_report(thread_report('%s/static/index.html' % SCRIPT_NAME))
|
||||
|
||||
print('')
|
||||
print('Size Report (1000 requests, 50 client threads, '
|
||||
'%s server threads):' % cherrypy.server.thread_pool)
|
||||
print_report(size_report())
|
||||
|
||||
|
||||
# modpython and other WSGI #
|
||||
|
||||
def startup_modpython(req=None):
|
||||
"""Start the CherryPy app server in 'serverless' mode (for modpython/WSGI).
|
||||
"""
|
||||
if cherrypy.engine.state == cherrypy._cpengine.STOPPED:
|
||||
if req:
|
||||
if 'nullreq' in req.get_options():
|
||||
cherrypy.engine.request_class = NullRequest
|
||||
cherrypy.engine.response_class = NullResponse
|
||||
ab_opt = req.get_options().get('ab', '')
|
||||
if ab_opt:
|
||||
global AB_PATH
|
||||
AB_PATH = ab_opt
|
||||
cherrypy.engine.start()
|
||||
if cherrypy.engine.state == cherrypy._cpengine.STARTING:
|
||||
cherrypy.engine.wait()
|
||||
return 0 # apache.OK
|
||||
|
||||
|
||||
def run_modpython(use_wsgi=False):
|
||||
print('Starting mod_python...')
|
||||
pyopts = []
|
||||
|
||||
# Pass the null and ab=path options through Apache
|
||||
if '--null' in opts:
|
||||
pyopts.append(('nullreq', ''))
|
||||
|
||||
if '--ab' in opts:
|
||||
pyopts.append(('ab', opts['--ab']))
|
||||
|
||||
s = _cpmodpy.ModPythonServer
|
||||
if use_wsgi:
|
||||
pyopts.append(('wsgi.application', 'cherrypy::tree'))
|
||||
pyopts.append(
|
||||
('wsgi.startup', 'cherrypy.test.benchmark::startup_modpython'))
|
||||
handler = 'modpython_gateway::handler'
|
||||
s = s(port=54583, opts=pyopts,
|
||||
apache_path=APACHE_PATH, handler=handler)
|
||||
else:
|
||||
pyopts.append(
|
||||
('cherrypy.setup', 'cherrypy.test.benchmark::startup_modpython'))
|
||||
s = s(port=54583, opts=pyopts, apache_path=APACHE_PATH)
|
||||
|
||||
try:
|
||||
s.start()
|
||||
run()
|
||||
finally:
|
||||
s.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
init()
|
||||
|
||||
longopts = ['cpmodpy', 'modpython', 'null', 'notests',
|
||||
'help', 'ab=', 'apache=']
|
||||
try:
|
||||
switches, args = getopt.getopt(sys.argv[1:], '', longopts)
|
||||
opts = dict(switches)
|
||||
except getopt.GetoptError:
|
||||
print(__doc__)
|
||||
sys.exit(2)
|
||||
|
||||
if '--help' in opts:
|
||||
print(__doc__)
|
||||
sys.exit(0)
|
||||
|
||||
if '--ab' in opts:
|
||||
AB_PATH = opts['--ab']
|
||||
|
||||
if '--notests' in opts:
|
||||
# Return without stopping the server, so that the pages
|
||||
# can be tested from a standard web browser.
|
||||
def run():
|
||||
port = cherrypy.server.socket_port
|
||||
print('You may now open http://127.0.0.1:%s%s/' %
|
||||
(port, SCRIPT_NAME))
|
||||
|
||||
if '--null' in opts:
|
||||
print('Using null Request object')
|
||||
else:
|
||||
def run():
|
||||
end = time.time() - start
|
||||
print('Started in %s seconds' % end)
|
||||
if '--null' in opts:
|
||||
print('\nUsing null Request object')
|
||||
try:
|
||||
try:
|
||||
run_standard_benchmarks()
|
||||
except Exception:
|
||||
print(_cperror.format_exc())
|
||||
raise
|
||||
finally:
|
||||
cherrypy.engine.exit()
|
||||
|
||||
print('Starting CherryPy app server...')
|
||||
|
||||
class NullWriter(object):
|
||||
|
||||
"""Suppresses the printing of socket errors."""
|
||||
|
||||
def write(self, data):
|
||||
pass
|
||||
sys.stderr = NullWriter()
|
||||
|
||||
start = time.time()
|
||||
|
||||
if '--cpmodpy' in opts:
|
||||
run_modpython()
|
||||
elif '--modpython' in opts:
|
||||
run_modpython(use_wsgi=True)
|
||||
else:
|
||||
if '--null' in opts:
|
||||
cherrypy.server.request_class = NullRequest
|
||||
cherrypy.server.response_class = NullResponse
|
||||
|
||||
cherrypy.engine.start_with_callback(run)
|
||||
cherrypy.engine.block()
|
Loading…
Add table
Add a link
Reference in a new issue