import json import os import random import time import uuid from collections import Counter from concurrent.futures import ThreadPoolExecutor, as_completed from copy import deepcopy from urllib.error import HTTPError, URLError from urllib.request import Request, urlopen BASE_URL = os.getenv( "BASE_URL", "http://localhost:8000", ) ENDPOINT = f"{BASE_URL}/api/v1/cdr/raw" RUN_ID = os.getenv( "RUN_ID", uuid.uuid4().hex[:8], ) def valid_payload(index: int) -> dict: start = int(time.time()) return { "raw_id": f"functional-{RUN_ID}-{index:04d}", "src_ip": "10.240.12.1", "device_id": "310410998877665", "caller_num": "+14155552671", "svc": "4G_DATA", "up_bytes": 1024, "down_bytes": 4096, "rat": 6, "lac": "4021", "ci": "8812", "epoch_start": start, "epoch_end": start + 300, } def send_json(payload: dict) -> int | str: body = json.dumps( payload ).encode("utf-8") return send_raw(body) def send_raw(body: bytes) -> int | str: request = Request( ENDPOINT, data=body, method="POST", headers={ "Content-Type": "application/json", }, ) try: with urlopen( request, timeout=10, ) as response: return response.status except HTTPError as exc: return exc.code except URLError as exc: return f"network_error:{exc.reason}" def main() -> None: requests: list[ tuple[str, dict | bytes] ] = [] valid_records = [ valid_payload(index) for index in range(1000) ] # 1000 valid CDRs for payload in valid_records: requests.append( ("valid", payload) ) # 20 missing caller_num for index in range(20): payload = valid_payload( 2000 + index ) payload.pop( "caller_num" ) requests.append( ("missing_caller_num", payload) ) # 10 malformed JSON requests for _ in range(10): requests.append( ( "invalid_json", b'{"raw_id": invalid}', ) ) # 10 invalid RAT values for index in range(10): payload = valid_payload( 3000 + index ) payload["rat"] = 99 requests.append( ("invalid_rat", payload) ) # 10 invalid epochs for index in range(10): payload = valid_payload( 4000 + index ) payload["epoch_start"] = ( payload["epoch_end"] + 100 ) requests.append( ("invalid_epoch", payload) ) # 20 duplicates of already accepted valid records. for payload in valid_records[:20]: requests.append( ( "duplicate", deepcopy(payload), ) ) random.shuffle( requests ) results = Counter() started_at = time.perf_counter() with ThreadPoolExecutor( max_workers=50 ) as executor: futures = [] for request_type, payload in requests: if isinstance( payload, bytes, ): future = executor.submit( send_raw, payload, ) else: future = executor.submit( send_json, payload, ) futures.append( ( request_type, future, ) ) for request_type, future in futures: status = future.result() results[ ( request_type, status, ) ] += 1 elapsed = ( time.perf_counter() - started_at ) print() print( f"Run ID: {RUN_ID}" ) print( f"Total requests: {len(requests)}" ) print( f"Elapsed: {elapsed:.2f}s" ) print() for ( request_type, status, ), count in sorted( results.items(), key=lambda item: str( item[0] ), ): print( f"{request_type:24} " f"status={status!s:8} " f"count={count}" ) print() print("Expected:") print(" valid → 1000 x 202") print(" duplicate → 20 x 202") print(" missing_caller → 20 x 400") print(" invalid_json → 10 x 400") print(" invalid_rat → 10 x 400") print(" invalid_epoch → 10 x 400") if __name__ == "__main__": main()