Commit 77feb65a authored by Elvis Pranskevichus's avatar Elvis Pranskevichus

Add support for HTTP benchmarks

parent 0ab494ea
#!/usr/bin/env python3
import argparse
import json
import os
import subprocess
import sys
import tempfile
def abort(msg):
print(msg, file=sys.stdout)
sys.exit(1)
luascript = '''
function done(summary, latency, requests)
tpl = [[
{
"messages": %d,
"transfer": %.2f,
"rps": %.2f,
"latency_min": %.3f,
"latency_mean": %.3f,
"latency_max": %.3f,
"latency_std": %.3f,
"latency_cv": %.2f,
"latency_percentiles": [%s]
}]]
transfer = (summary.bytes / (1024 * 1024)) / (summary.duration / 1000000)
rps = summary.requests / (summary.duration / 1000000)
latency_percentiles = {}
percentiles = {25, 50, 75, 90, 99, 99.99}
for i, percentile in ipairs(percentiles) do
table.insert(
latency_percentiles,
string.format("[%.2f, %.3f]", percentile,
latency:percentile(percentile) / 1000)
)
end
out = string.format(tpl, summary.requests, transfer, rps,
latency.min / 1000, latency.mean / 1000,
latency.max / 1000, latency.stdev / 1000,
(latency.stdev / latency.mean) * 100,
table.concat(latency_percentiles, ','))
io.stderr:write(out)
end
'''
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--msize', default=1000, type=int,
help='message size in bytes')
parser.add_argument('--duration', '-T', default=30, type=int,
help='duration of test in seconds')
parser.add_argument('--concurrency', default=3, type=int,
help='request concurrency')
parser.add_argument('--addr', default='127.0.0.1:25000', type=str,
help='server address')
parser.add_argument('--output-format', default='text', type=str,
help='output format', choices=['text', 'json'])
args = parser.parse_args()
unix = False
if args.addr.startswith('file:'):
abort('Unix sockets are not supported')
with tempfile.NamedTemporaryFile(mode='w+t', delete=False) as luaf:
luaf.write(luascript)
lua_script_path = luaf.name
wrk = ['wrk', '--latency', '--duration={}s'.format(args.duration),
'--connections={}'.format(args.concurrency),
'--script={}'.format(lua_script_path),
'http://{}/{}'.format(args.addr, args.msize)]
try:
wrk_run = subprocess.Popen(wrk, universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, data_json = wrk_run.communicate()
finally:
os.unlink(lua_script_path)
if args.output_format == 'json':
print(data_json)
else:
data = json.loads(data_json)
data['latency_percentiles'] = '; '.join(
'{}% under {}ms'.format(*v) for v in data['latency_percentiles'])
output = '''\
{messages} {size}KiB messages in {duration} seconds
Latency: min {latency_min}ms; max {latency_max}ms; mean {latency_mean}ms; \
std: {latency_std}ms ({latency_cv}%)
Latency distribtion: {latency_percentiles}
Requests/sec: {rps}
Transfer/sec: {transfer}MiB
'''.format(duration=args.duration, size=round(args.msize / 1024, 2), **data)
print(output)
......@@ -34,8 +34,8 @@ unix_address = 'file:{_socket}/server.sock'.format(_socket=_socket)
tcp_client = echo_client + ['--addr={}'.format(tcp_address)]
unix_client = echo_client + ['--addr={}'.format(unix_address)]
http_client = "wrk --latency -d 30 -c 200 -t 4 http://127.0.0.1:25000/{msize}"
http_client = ['./http_client', '--output-format=json',
'--addr={}'.format(tcp_address)]
benchmarks = [{
'name': 'tcpecho-gevent',
......@@ -143,6 +143,38 @@ benchmarks = [{
'--streams', '--uvloop'],
'server_address': unix_address,
'client': unix_client,
}, {
'name': 'http-asyncio-aiohttp',
'title': 'HTTP server (asyncio/aiohttp)',
'server': python + ['/usr/src/servers/asyncio_http_server.py',
'--type=asyncio+aiohttp',
'--addr=0.0.0.0:25000'],
'server_address': tcp_address,
'client': http_client,
}, {
'name': 'http-asyncio-httptools',
'title': 'HTTP server (asyncio/httptools)',
'server': python + ['/usr/src/servers/asyncio_http_server.py',
'--type=asyncio+httptools',
'--addr=0.0.0.0:25000'],
'server_address': tcp_address,
'client': http_client,
}, {
'name': 'http-uvloop-aiohttp',
'title': 'HTTP server (uvloop/aiohttp)',
'server': python + ['/usr/src/servers/asyncio_http_server.py',
'--type=uvloop+aiohttp',
'--addr=0.0.0.0:25000'],
'server_address': tcp_address,
'client': http_client,
}, {
'name': 'http-uvloop-httptools',
'title': 'HTTP server (uvloop/httptools)',
'server': python + ['/usr/src/servers/asyncio_http_server.py',
'--type=uvloop+httptools',
'--addr=0.0.0.0:25000'],
'server_address': tcp_address,
'client': http_client,
}]
......@@ -175,7 +207,7 @@ def start_and_wait_for_server(server_cmd, address, timeout=60):
sock.settimeout(time.monotonic() - start)
try:
sock.connect(addr)
sock.sendall(b'ping')
sock.sendall(b'GET / HTTP/1.0\r\n\r\n')
if sock.recv(4):
print('Server is up and running.')
else:
......
......@@ -5,6 +5,7 @@ import aiohttp.server
from aiohttp import web
import sys
import httptools
import uvloop
from socket import *
......@@ -32,9 +33,6 @@ class HttpResponse:
self._headers_sent = False
def write(self, data):
if isinstance(data, str):
data = data.encode()
self._protocol._transport.writelines([
'HTTP/{} 200 OK\r\n'.format(
self._request._version).encode('latin-1'),
......@@ -45,9 +43,6 @@ class HttpResponse:
])
RESP = b'Hello World' * 512
class HttpProtocol(asyncio.Protocol):
__slots__ = ('_loop',
......@@ -83,6 +78,8 @@ class HttpProtocol(asyncio.Protocol):
def connection_made(self, transport):
self._transport = transport
sock = transport.get_extra_info('socket')
sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
def connection_lost(self, exc):
self._current_request = self._current_parser = None
......@@ -96,7 +93,13 @@ class HttpProtocol(asyncio.Protocol):
self._current_parser.feed_data(data)
def handle(self, request, response):
response.write(RESP)
parsed_url = httptools.parse_url(self._current_url)
payload_size = parsed_url.path.decode('ascii')[1:]
if not payload_size:
payload_size = 1024
else:
payload_size = int(payload_size)
response.write(b'X' * payload_size)
def abort(msg):
......@@ -105,12 +108,12 @@ def abort(msg):
def aiohttp_server(loop, addr):
PAYLOAD = b'<h1>Hello, World!</h1>'
async def handle(request):
return web.Response(body=PAYLOAD)
payload_size = int(request.match_info.get('size', 1024))
return web.Response(body=b'X' * payload_size)
app = web.Application(loop=loop)
app.router.add_route('GET', '/{size}', handle)
app.router.add_route('GET', '/', handle)
handler = app.make_handler()
server = loop.create_server(handler, *addr)
......@@ -118,6 +121,10 @@ def aiohttp_server(loop, addr):
return server
def httptools_server(loop, addr):
return loop.create_server(lambda: HttpProtocol(loop=loop), *addr)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--type', default='asyncio+aiohttp', action='store')
......@@ -132,7 +139,7 @@ if __name__ == '__main__':
else:
server_type = args.type
if server_type == 'aiohttp':
if server_type in {'aiohttp', 'httptools'}:
if not loop_type:
loop_type = 'asyncio'
else:
......
......@@ -3,5 +3,5 @@ aiohttp==0.21.5
gevent==1.1.1
tornado==4.3
Twisted==16.1.1
httptools==0.0.4
httptools==0.0.5
uvloop==0.4.7
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment