1
1
from abc import abstractmethod
2
2
import asyncio
3
+ import time
3
4
from typing import (
4
5
cast ,
5
6
Generic ,
33
34
TReturn = TypeVar ('TReturn' )
34
35
35
36
37
+ class ResponseTimeTracker :
38
+
39
+ def __init__ (self ) -> None :
40
+ self .total_msgs = 0
41
+ self .total_items = 0
42
+ self .total_timeouts = 0
43
+ self .total_response_time = 0.0
44
+
45
+ def get_stats (self ) -> str :
46
+ if not self .total_msgs :
47
+ return 'None'
48
+ avg_rtt = self .total_response_time / self .total_msgs
49
+ if not self .total_items :
50
+ per_item_rtt = 0.0
51
+ else :
52
+ per_item_rtt = self .total_response_time / self .total_items
53
+ return 'count=%d, items=%d, avg_rtt=%.2f, avg_time_per_item=%.5f, timeouts=%d' % (
54
+ self .total_msgs , self .total_items , avg_rtt , per_item_rtt , self .total_timeouts )
55
+
56
+ def add (self , time : float , size : int ) -> None :
57
+ self .total_msgs += 1
58
+ self .total_items += size
59
+ self .total_response_time += time
60
+
61
+
36
62
class BaseRequestManager (PeerSubscriber , BaseService , Generic [TPeer , TRequest , TResponse , TReturn ]): # noqa: E501
37
63
#
38
64
# PeerSubscriber
@@ -51,6 +77,7 @@ def subscription_msg_types(self) -> Set[Type[Command]]:
51
77
52
78
def __init__ (self , peer : TPeer , token : CancelToken ) -> None :
53
79
self ._peer = peer
80
+ self .response_times = ResponseTimeTracker ()
54
81
super ().__init__ (token )
55
82
56
83
#
@@ -61,8 +88,7 @@ async def _run(self) -> None:
61
88
62
89
with self .subscribe_peer (self ._peer ):
63
90
while self .is_running :
64
- peer , cmd , msg = await self .wait (
65
- self .msg_queue .get (), token = self .cancel_token )
91
+ peer , cmd , msg = await self .wait (self .msg_queue .get ())
66
92
if peer != self ._peer :
67
93
self .logger .error ("Unexpected peer: %s expected: %s" , peer , self ._peer )
68
94
continue
@@ -81,12 +107,15 @@ async def _handle_msg(self, msg: TResponse) -> None:
81
107
)
82
108
return
83
109
110
+ self .response_times .add (
111
+ time .time () - self ._pending_request_start , self ._get_item_count (msg ))
112
+
84
113
request , future = self .pending_request
85
114
86
115
try :
87
116
response = await self ._normalize_response (msg )
88
117
except MalformedMessage as err :
89
- self .logger .warn (
118
+ self .logger .warning (
90
119
"Malformed response for pending %s request from peer %s, disconnecting: %s" ,
91
120
self .response_msg_name ,
92
121
self ._peer ,
@@ -112,6 +141,10 @@ async def _handle_msg(self, msg: TResponse) -> None:
112
141
async def _normalize_response (self , msg : TResponse ) -> TReturn :
113
142
pass
114
143
144
+ @abstractmethod
145
+ def _get_item_count (self , msg : TResponse ) -> int :
146
+ pass
147
+
115
148
@abstractmethod
116
149
def __call__ (self ) -> TReturn :
117
150
"""
@@ -141,7 +174,23 @@ def _send_sub_proto_request(self, request: TRequest) -> None:
141
174
142
175
async def _wait_for_response (self ,
143
176
request : TRequest ,
144
- timeout : int = None ) -> TReturn :
177
+ timeout : int ) -> TReturn :
178
+ future : 'asyncio.Future[TReturn]' = asyncio .Future ()
179
+ self ._pending_request_start = time .time ()
180
+ self .pending_request = (request , future )
181
+
182
+ try :
183
+ response = await self .wait (future , timeout = timeout )
184
+ except TimeoutError :
185
+ self .response_times .total_timeouts += 1
186
+ raise
187
+ finally :
188
+ # Always ensure that we reset the `pending_request` to `None` on exit.
189
+ self .pending_request = None
190
+
191
+ return response
192
+
193
+ async def _request_and_wait (self , request : TRequest , timeout : int = None ) -> TReturn :
145
194
if self .pending_request is not None :
146
195
self .logger .error (
147
196
"Already waiting for response to %s for peer: %s" ,
@@ -155,19 +204,10 @@ async def _wait_for_response(self,
155
204
)
156
205
)
157
206
158
- future : 'asyncio.Future[TReturn]' = asyncio .Future ()
159
- self .pending_request = (request , future )
160
-
161
- try :
162
- response = await self .wait (future , timeout = timeout )
163
- finally :
164
- # Always ensure that we reset the `pending_request` to `None` on exit.
165
- self .pending_request = None
166
-
167
- return response
168
-
169
- async def _request_and_wait (self , request : TRequest , timeout : int = None ) -> TReturn :
170
207
if timeout is None :
171
208
timeout = self .response_timout
172
209
self ._send_sub_proto_request (request )
173
210
return await self ._wait_for_response (request , timeout = timeout )
211
+
212
+ def get_stats (self ) -> str :
213
+ return '%s: %s' % (self .response_msg_name , self .response_times .get_stats ())
0 commit comments