From dd47a04ad10659af9ba1c9ac99929fcdab28ed1a Mon Sep 17 00:00:00 2001 From: sonic182 Date: Wed, 14 Aug 2024 22:48:10 +0200 Subject: [PATCH] refactor performance script --- tests/performance.py | 145 +++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 89 deletions(-) diff --git a/tests/performance.py b/tests/performance.py index 6acc3c8..1acf674 100644 --- a/tests/performance.py +++ b/tests/performance.py @@ -24,9 +24,8 @@ try: import uvloop - uvloop.install() -except Exception: +except ImportError: pass @@ -36,41 +35,33 @@ def is_tool(name): async def app(scope, receive, send): + """Simple ASGI app.""" assert scope["type"] == "http" res = b"foo" - await send( - { - "type": "http.response.start", - "status": 200, - "headers": [ - [b"content-type", b"text/plain"], - [b"content-length", b"%d" % len(res)], - ], - } - ) - await send( - { - "type": "http.response.body", - "body": res, - } - ) + await send({ + "type": "http.response.start", + "status": 200, + "headers": [ + [b"content-type", b"text/plain"], + [b"content-length", b"%d" % len(res)], + ], + }) + await send({ + "type": "http.response.body", + "body": res, + }) async def start_dummy_server(port): """Start dummy server.""" - host = "0.0.0.0" - - config = Config(app, host=host, port=port, workers=2, log_level="warning") + config = Config(app, host="0.0.0.0", port=port, workers=2, log_level="warning") server = Server(config=config) - await server.serve() -async def timeit_coro(func, *args, **kwargs): - """To time stuffs.""" - repeat = kwargs.pop("repeat", 1000) +async def timeit_coro(func, *args, repeat=1000, **kwargs): + """Measure the time taken for repeated asynchronous tasks.""" before = datetime.now() - # Concurrent coroutines await asyncio.gather(*[func(*args, **kwargs) for _ in range(repeat)]) after = datetime.now() return (after - before) / timedelta(milliseconds=1) @@ -78,20 +69,18 @@ async def timeit_coro(func, *args, **kwargs): async def performance_aiohttp(url, concurrency): """Test aiohttp performance.""" - async with aiohttp.ClientSession( - connector=aiohttp.TCPConnector(limit=concurrency) - ) as session: - return await timeit_coro(session.get, (url)) + async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=concurrency)) as session: + return await timeit_coro(session.get, url) async def performance_aiosonic(url, concurrency, pool_cls=None, timeouts=None): - """Test aiohttp performance.""" + """Test aiosonic performance.""" client = aiosonic.HTTPClient(TCPConnector(pool_size=concurrency, pool_cls=pool_cls)) return await timeit_coro(client.get, url, timeouts=timeouts) -async def performance_httpx(url, concurrency, pool_cls=None): - """Test aiohttp performance.""" +async def performance_httpx(url, concurrency): + """Test httpx performance.""" async with httpx.AsyncClient() as client: return await timeit_coro(client.get, url) @@ -104,72 +93,42 @@ def timeit_requests(url, concurrency, repeat=1000): ) session.mount("http://", adapter) with futures.ThreadPoolExecutor(concurrency) as executor: - to_wait = [] + futures_list = [executor.submit(session.get, url) for _ in range(repeat)] before = datetime.now() - for _ in range(repeat): - to_wait.append(executor.submit(session.get, url)) - for fut in to_wait: + for fut in futures.as_completed(futures_list): fut.result() after = datetime.now() return (after - before) / timedelta(milliseconds=1) -def do_tests(url): +async def do_tests(url): """Start benchmark.""" - print("doing tests...") + print("Running performance tests...") concurrency = 25 - # aiohttp - res1 = asyncio.run(performance_aiohttp(url, concurrency)) - - # aiosonic - res2 = asyncio.run(performance_aiosonic(url, concurrency)) - - # requests - res3 = timeit_requests(url, concurrency) + results = {} - # aiosonic cyclic - res4 = asyncio.run(performance_aiosonic(url, concurrency, pool_cls=CyclicQueuePool)) + results["aiohttp"] = await performance_aiohttp(url, concurrency) + results["aiosonic"] = await performance_aiosonic(url, concurrency) + results["requests"] = timeit_requests(url, concurrency) + results["aiosonic_cyclic"] = await performance_aiosonic(url, concurrency, pool_cls=CyclicQueuePool) - # httpx - httpx_exc = False - res5 = None try: - res5 = asyncio.run(performance_httpx(url, concurrency)) + results["httpx"] = await performance_httpx(url, concurrency) except Exception as exc: - httpx_exc = exc - print("httpx did break with: " + str(exc)) + # results["httpx_error"] = str(exc) + print(f"httpx encountered an error: {exc}") - to_print = { - "aiosonic": "1000 requests in %.2f ms" % res2, - "aiosonic cyclic": "1000 requests in %.2f ms" % res4, - "aiohttp": "1000 requests in %.2f ms" % res1, - "requests": "1000 requests in %.2f ms" % res3, - } + print(json.dumps({k: f"1000 requests in {v:.2f} ms" for k, v in results.items() if not k.endswith('_error')}, indent=4)) - if not httpx_exc: - to_print.update({"httpx": "1000 requests in %.2f ms" % res5}) + if "httpx" in results: + print(f"aiosonic is {((results['httpx'] / results['aiosonic']) - 1) * 100:.2f}% faster than httpx") - print(json.dumps(to_print, indent=True)) + print(f"aiosonic is {((results['aiohttp'] / results['aiosonic']) - 1) * 100:.2f}% faster than aiohttp") + print(f"aiosonic is {((results['requests'] / results['aiosonic']) - 1) * 100:.2f}% faster than requests") + print(f"aiosonic is {((results['aiosonic_cyclic'] / results['aiosonic']) - 1) * 100:.2f}% faster than aiosonic cyclic") - print("aiosonic is %.2f%% faster than aiohttp" % (((res1 / res2) - 1) * 100)) - print("aiosonic is %.2f%% faster than requests" % (((res3 / res2) - 1) * 100)) - print( - "aiosonic is %.2f%% faster than aiosonic cyclic" % (((res4 / res2) - 1) * 100) - ) - - res = [ - ["aiohttp", res1], - ["aiosonic", res2], - ["requests", res3], - ["aiosonic_cyclic", res4], - ] - - if not httpx_exc: - print("aiosonic is %.2f%% faster than httpx" % (((res5 / res2) - 1) * 100)) - res.append(["httpx", res5]) - - return res + return results def start_server(port): @@ -181,28 +140,36 @@ def start_server(port): def main(): - """Start.""" + """Start the performance test.""" port = random.randint(1000, 9000) - url = "http://0.0.0.0:%d" % port + url = f"http://0.0.0.0:{port}" process = Process(target=start_server, args=(port,)) process.start() max_wait = datetime.now() + timedelta(seconds=5) - while True: + while datetime.now() < max_wait: try: with urlopen(url) as response: response.read() break except URLError: sleep(1) - if datetime.now() > max_wait: - raise + else: + print("Server did not start in time.") + process.terminate() + return + try: - res = do_tests(url) - assert "aiosonic" in sorted(res, key=lambda x: x[1])[0][0] + res = asyncio.run(do_tests(url)) + + # Check if any results are valid and proceed + fastest_client = sorted(res.items(), key=lambda x: x[1])[0][0] + assert "aiosonic" in fastest_client + finally: process.terminate() + if __name__ == "__main__": main()