Skip to content

Instantly share code, notes, and snippets.

@bsidhom
Created April 23, 2023 20:05
Show Gist options
  • Save bsidhom/2e6eed44e57b5d707a8683a9e975c6bf to your computer and use it in GitHub Desktop.
Save bsidhom/2e6eed44e57b5d707a8683a9e975c6bf to your computer and use it in GitHub Desktop.
Benchmark a single-threaded aiohttp server serving simple responses
#!/usr/bin/env python3
import argparse
import asyncio
import concurrent.futures
import functools
import time
import typing
import aiohttp
async def main():
parser = argparse.ArgumentParser("Spam http requests and count qps")
parser.add_argument("--num-requests",
help="Number of requests to issue",
type=int,
required=True)
parser.add_argument(
"--parallelism",
help=
"Number of parallel clients to run. Each will send the target request count.",
type=int,
default=1)
parser.add_argument(
"--multiprocess",
help="If set, use multiple processes to send parallel requests.",
action="store_true")
args = parser.parse_args()
client = 0
total_success_count = 0
total_error_count = 0
start_ns = time.perf_counter_ns()
if args.multiprocess:
with concurrent.futures.ProcessPoolExecutor() as pool:
loop = asyncio.get_running_loop()
tasks = [
loop.run_in_executor(
pool,
functools.partial(run_test_blocking, args.num_requests))
for _ in range(args.parallelism)
]
else:
tasks = [run_test(args.num_requests) for _ in range(args.parallelism)]
for task in asyncio.as_completed(tasks):
elapsed_sec, request_count, success_count = await task
print(f"client {client} results:")
print(f" issued {request_count} requests in {elapsed_sec} seconds")
success_rate = success_count / elapsed_sec
print(f" success rate: {success_rate} qps")
error_rate = (request_count - success_count) / elapsed_sec
print(f" error rate: {error_rate} qps")
total_success_count += success_count
total_error_count += request_count - success_count
client += 1
elapsed_ns = time.perf_counter_ns() - start_ns
elapsed_sec = elapsed_ns / 1e9
total_success_rate = total_success_count / elapsed_sec
total_error_rate = total_error_count / elapsed_sec
print(f"total success rate: {total_success_rate} qps")
print(f"total error rate: {total_error_rate} qps")
def run_test_blocking(requests: int) -> typing.Tuple[float, int, int]:
return asyncio.run(run_test(requests))
async def run_test(requests: int) -> typing.Tuple[float, int, int]:
issued_requests = 0
success_count = 0
async with aiohttp.ClientSession() as session:
start = time.perf_counter_ns()
while issued_requests < requests:
issued_requests += 1
async with session.get("http://localhost:8888/?name=foo") as resp:
if resp.ok:
t = await resp.text("utf-8")
if t == "Hello, foo!":
success_count += 1
elapsed_ns = time.perf_counter_ns() - start
elapsed_sec = elapsed_ns / 1e9
return (elapsed_sec, success_count, issued_requests)
if __name__ == "__main__":
asyncio.run(main())
#!/usr/bin/env python3
import asyncio
import multidict
import yarl
from aiohttp import web
async def main():
shutdown = asyncio.Event()
app = web.Application()
app.add_routes([web.get("/", hello)])
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, "localhost", 8888)
await site.start()
await shutdown.wait()
async def hello(request: web.Request) -> web.Response:
url: yarl.URL = request.rel_url
query: multidict.MultiDict = url.query
name = query.get("name", "mysterious")
return web.Response(text=f"Hello, {name}!")
if __name__ == "__main__":
asyncio.run(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment