22
33import asyncio
44import time
5- from typing import Any , Dict , Optional
5+ from typing import Any , Optional
66
77import aiohttp
88
1212class HttpTimingCollector :
1313 """Utility class for measuring HTTP request timing with detailed breakdown."""
1414
15- def __init__ (self ):
16- self .timing : Dict [str , float ] = {}
15+ def __init__ (self ) -> None :
16+ self .timing : dict [str , float ] = {}
1717
1818 def create_trace_config (self ) -> aiohttp .TraceConfig :
1919 """Create aiohttp trace configuration for detailed timing measurement."""
2020 trace_config = aiohttp .TraceConfig ()
2121
22- async def on_request_start (session , context , params ) :
22+ async def on_request_start (_session : Any , _context : Any , _params : Any ) -> None :
2323 self .timing ["start" ] = time .monotonic ()
2424
25- async def on_dns_resolvehost_start (session , context , params ):
26- self .timing ["dns_start" ] = time .monotonic ()
27-
28- async def on_dns_resolvehost_end (session , context , params ):
29- self .timing ["dns_end" ] = time .monotonic ()
30-
31- async def on_connection_create_start (session , context , params ):
25+ async def on_connection_create_start (_session : Any , _context : Any , _params : Any ) -> None :
3226 self .timing ["conn_start" ] = time .monotonic ()
3327
34- async def on_connection_create_end (session , context , params ) :
28+ async def on_connection_create_end (_session : Any , _context : Any , _params : Any ) -> None :
3529 self .timing ["conn_end" ] = time .monotonic ()
3630
37- async def on_request_end (session , context , params ) :
31+ async def on_request_end (_session : Any , _context : Any , _params : Any ) -> None :
3832 self .timing ["end" ] = time .monotonic ()
3933
4034 trace_config .on_request_start .append (on_request_start )
41- trace_config .on_dns_resolvehost_start .append (on_dns_resolvehost_start )
42- trace_config .on_dns_resolvehost_end .append (on_dns_resolvehost_end )
4335 trace_config .on_connection_create_start .append (on_connection_create_start )
4436 trace_config .on_connection_create_end .append (on_connection_create_end )
4537 trace_config .on_request_end .append (on_request_end )
@@ -52,52 +44,75 @@ def get_connection_time(self) -> float:
5244 return self .timing ["conn_end" ] - self .timing ["conn_start" ]
5345 return 0.0
5446
55- def get_dns_time (self ) -> float :
56- """Get DNS resolution time in seconds."""
57- if "dns_start " in self .timing and "dns_end " in self .timing :
58- return self .timing ["dns_end " ] - self .timing ["dns_start " ]
47+ def get_total_time (self ) -> float :
48+ """Get total request time in seconds."""
49+ if "start " in self .timing and "end " in self .timing :
50+ return self .timing ["end " ] - self .timing ["start " ]
5951 return 0.0
6052
6153
6254async def measure_http_request_timing (
6355 session : aiohttp .ClientSession ,
6456 method : str ,
6557 url : str ,
66- headers : Optional [Dict [str , str ]] = None ,
67- json_data : Optional [Dict [str , Any ]] = None ,
58+ headers : Optional [dict [str , str ]] = None ,
59+ json_data : Optional [dict [str , Any ]] = None ,
6860 exclude_connection_time : bool = True ,
6961) -> tuple [float , aiohttp .ClientResponse ]:
7062 """Measure HTTP request timing with retry logic and detailed breakdown.
7163
64+ Args:
65+ exclude_connection_time: If True, exclude connection establishment time
66+ from the returned timing to measure pure API response time
67+
7268 Returns:
7369 tuple: (response_time_seconds, response)
7470 """
75- response_time = 0.0
76- response = None
71+ timing_collector = HttpTimingCollector ()
72+ trace_config : aiohttp . TraceConfig = timing_collector . create_trace_config ()
7773
78- for retry_count in range ( MAX_RETRIES ):
79- start_time = time . monotonic ( )
74+ # Add timing trace to session temporarily
75+ session . _trace_configs . append ( trace_config )
8076
81- # Send request
82- if method .upper () == "POST" :
83- response = await session .post (
84- url , headers = headers , json = json_data
85- )
86- else :
87- response = await session .get (url , headers = headers )
77+ try :
78+ response = None
79+
80+ for retry_count in range (MAX_RETRIES ):
81+ # Reset timing for each retry
82+ timing_collector .timing .clear ()
8883
89- response_time = time .monotonic () - start_time
84+ # Send request
85+ if method .upper () == "POST" :
86+ response = await session .post (
87+ url , headers = headers , json = json_data
88+ )
89+ else :
90+ response = await session .get (url , headers = headers )
9091
91- # Handle rate limiting
92- if response .status == 429 and retry_count < MAX_RETRIES - 1 :
93- wait_time = int (response .headers .get ("Retry-After" , 3 ))
94- await response .release ()
95- await asyncio .sleep (wait_time )
96- continue
92+ # Handle rate limiting
93+ if response .status == 429 and retry_count < MAX_RETRIES - 1 :
94+ wait_time = int (response .headers .get ("Retry-After" , 3 ))
95+ await response .release ()
96+ await asyncio .sleep (wait_time )
97+ continue
9798
98- break
99+ break
100+
101+ if not response :
102+ raise ValueError ("No response received" )
103+
104+ # Calculate response time based on exclusion setting
105+ total_time : float = timing_collector .get_total_time ()
106+
107+ if exclude_connection_time :
108+ connection_time : float = timing_collector .get_connection_time ()
109+ response_time : float = max (0.0 , total_time - connection_time )
110+ else :
111+ response_time = total_time
99112
100- if not response :
101- raise ValueError ("No response received" )
113+ return response_time , response
102114
103- return response_time , response
115+ finally :
116+ # Remove trace config to avoid affecting other requests
117+ if trace_config in session ._trace_configs :
118+ session ._trace_configs .remove (trace_config )
0 commit comments