import asyncio
import random
import time
from dataclasses import dataclass


TOTAL_REQUESTS = 1000
MAX_CONCURRENCY = 100


@dataclass
class Result:
    success: bool
    latency_ms: float


async def call_downstream(request_id: int) -> Result:
    start = time.perf_counter()

    # Simulate an I/O request such as HTTP, Redis, database, or message queue access.
    await asyncio.sleep(random.uniform(0.02, 0.12))

    if request_id % 97 == 0:
      return Result(False, (time.perf_counter() - start) * 1000)

    return Result(True, (time.perf_counter() - start) * 1000)


async def handle_request(request_id: int, limiter: asyncio.Semaphore) -> Result:
    async with limiter:
        return await call_downstream(request_id)


async def main() -> None:
    started = time.perf_counter()
    limiter = asyncio.Semaphore(MAX_CONCURRENCY)
    tasks = [asyncio.create_task(handle_request(i, limiter)) for i in range(1, TOTAL_REQUESTS + 1)]
    results = await asyncio.gather(*tasks)

    elapsed = time.perf_counter() - started
    success_count = sum(1 for item in results if item.success)
    failed_count = TOTAL_REQUESTS - success_count
    avg_latency = sum(item.latency_ms for item in results) / len(results)
    max_latency = max(item.latency_ms for item in results)

    print(f"total requests: {TOTAL_REQUESTS}")
    print(f"max concurrency: {MAX_CONCURRENCY}")
    print(f"success: {success_count}, failed: {failed_count}")
    print(f"elapsed: {elapsed:.2f}s")
    print(f"throughput: {TOTAL_REQUESTS / elapsed:.2f} req/s")
    print(f"avg latency: {avg_latency:.2f}ms")
    print(f"max latency: {max_latency:.2f}ms")


if __name__ == "__main__":
    asyncio.run(main())
