dory-sdk 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dory/__init__.py +70 -0
- dory/auto_instrument.py +142 -0
- dory/cli/__init__.py +5 -0
- dory/cli/main.py +290 -0
- dory/cli/templates.py +333 -0
- dory/config/__init__.py +23 -0
- dory/config/defaults.py +50 -0
- dory/config/loader.py +361 -0
- dory/config/presets.py +325 -0
- dory/config/schema.py +152 -0
- dory/core/__init__.py +27 -0
- dory/core/app.py +404 -0
- dory/core/context.py +209 -0
- dory/core/lifecycle.py +214 -0
- dory/core/meta.py +121 -0
- dory/core/modes.py +479 -0
- dory/core/processor.py +654 -0
- dory/core/signals.py +122 -0
- dory/decorators.py +142 -0
- dory/errors/__init__.py +117 -0
- dory/errors/classification.py +362 -0
- dory/errors/codes.py +495 -0
- dory/health/__init__.py +10 -0
- dory/health/probes.py +210 -0
- dory/health/server.py +306 -0
- dory/k8s/__init__.py +11 -0
- dory/k8s/annotation_watcher.py +184 -0
- dory/k8s/client.py +251 -0
- dory/k8s/pod_metadata.py +182 -0
- dory/logging/__init__.py +9 -0
- dory/logging/logger.py +175 -0
- dory/metrics/__init__.py +7 -0
- dory/metrics/collector.py +301 -0
- dory/middleware/__init__.py +36 -0
- dory/middleware/connection_tracker.py +608 -0
- dory/middleware/request_id.py +321 -0
- dory/middleware/request_tracker.py +501 -0
- dory/migration/__init__.py +11 -0
- dory/migration/configmap.py +260 -0
- dory/migration/serialization.py +167 -0
- dory/migration/state_manager.py +301 -0
- dory/monitoring/__init__.py +23 -0
- dory/monitoring/opentelemetry.py +462 -0
- dory/py.typed +2 -0
- dory/recovery/__init__.py +60 -0
- dory/recovery/golden_image.py +480 -0
- dory/recovery/golden_snapshot.py +561 -0
- dory/recovery/golden_validator.py +518 -0
- dory/recovery/partial_recovery.py +479 -0
- dory/recovery/recovery_decision.py +242 -0
- dory/recovery/restart_detector.py +142 -0
- dory/recovery/state_validator.py +187 -0
- dory/resilience/__init__.py +45 -0
- dory/resilience/circuit_breaker.py +454 -0
- dory/resilience/retry.py +389 -0
- dory/sidecar/__init__.py +6 -0
- dory/sidecar/main.py +75 -0
- dory/sidecar/server.py +329 -0
- dory/simple.py +342 -0
- dory/types.py +75 -0
- dory/utils/__init__.py +25 -0
- dory/utils/errors.py +59 -0
- dory/utils/retry.py +115 -0
- dory/utils/timeout.py +80 -0
- dory_sdk-2.1.0.dist-info/METADATA +663 -0
- dory_sdk-2.1.0.dist-info/RECORD +69 -0
- dory_sdk-2.1.0.dist-info/WHEEL +5 -0
- dory_sdk-2.1.0.dist-info/entry_points.txt +3 -0
- dory_sdk-2.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Request Tracker Middleware
|
|
3
|
+
|
|
4
|
+
Automatically tracks request lifecycle, duration, success/failure, and metrics.
|
|
5
|
+
Eliminates boilerplate for request tracking.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Automatic request lifecycle tracking
|
|
9
|
+
- Duration measurement
|
|
10
|
+
- Success/failure counting
|
|
11
|
+
- Active request tracking
|
|
12
|
+
- Request metrics aggregation
|
|
13
|
+
- Decorators for easy integration
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import logging
|
|
18
|
+
import time
|
|
19
|
+
from contextlib import asynccontextmanager
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from functools import wraps
|
|
24
|
+
from typing import Any, Dict, Optional, List, Callable, Set
|
|
25
|
+
from collections import defaultdict
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RequestStatus(Enum):
|
|
31
|
+
"""Request status."""
|
|
32
|
+
PENDING = "pending"
|
|
33
|
+
IN_PROGRESS = "in_progress"
|
|
34
|
+
SUCCESS = "success"
|
|
35
|
+
FAILED = "failed"
|
|
36
|
+
TIMEOUT = "timeout"
|
|
37
|
+
CANCELLED = "cancelled"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class RequestInfo:
|
|
42
|
+
"""
|
|
43
|
+
Information about a tracked request.
|
|
44
|
+
"""
|
|
45
|
+
request_id: str
|
|
46
|
+
request_type: str
|
|
47
|
+
status: RequestStatus
|
|
48
|
+
start_time: float
|
|
49
|
+
end_time: Optional[float] = None
|
|
50
|
+
duration: Optional[float] = None
|
|
51
|
+
error: Optional[str] = None
|
|
52
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
53
|
+
|
|
54
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
55
|
+
"""Convert to dictionary."""
|
|
56
|
+
return {
|
|
57
|
+
"request_id": self.request_id,
|
|
58
|
+
"request_type": self.request_type,
|
|
59
|
+
"status": self.status.value,
|
|
60
|
+
"start_time": self.start_time,
|
|
61
|
+
"end_time": self.end_time,
|
|
62
|
+
"duration": self.duration,
|
|
63
|
+
"error": self.error,
|
|
64
|
+
"metadata": self.metadata,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
def is_active(self) -> bool:
|
|
68
|
+
"""Check if request is still active."""
|
|
69
|
+
return self.status in [RequestStatus.PENDING, RequestStatus.IN_PROGRESS]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class RequestMetrics:
|
|
74
|
+
"""
|
|
75
|
+
Aggregated request metrics.
|
|
76
|
+
"""
|
|
77
|
+
total_requests: int = 0
|
|
78
|
+
active_requests: int = 0
|
|
79
|
+
successful_requests: int = 0
|
|
80
|
+
failed_requests: int = 0
|
|
81
|
+
timeout_requests: int = 0
|
|
82
|
+
cancelled_requests: int = 0
|
|
83
|
+
total_duration: float = 0.0
|
|
84
|
+
min_duration: Optional[float] = None
|
|
85
|
+
max_duration: Optional[float] = None
|
|
86
|
+
avg_duration: float = 0.0
|
|
87
|
+
|
|
88
|
+
def update_duration(self, duration: float) -> None:
|
|
89
|
+
"""Update duration statistics."""
|
|
90
|
+
self.total_duration += duration
|
|
91
|
+
if self.min_duration is None or duration < self.min_duration:
|
|
92
|
+
self.min_duration = duration
|
|
93
|
+
if self.max_duration is None or duration > self.max_duration:
|
|
94
|
+
self.max_duration = duration
|
|
95
|
+
|
|
96
|
+
completed = self.successful_requests + self.failed_requests + self.timeout_requests + self.cancelled_requests
|
|
97
|
+
if completed > 0:
|
|
98
|
+
self.avg_duration = self.total_duration / completed
|
|
99
|
+
|
|
100
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
101
|
+
"""Convert to dictionary."""
|
|
102
|
+
return {
|
|
103
|
+
"total_requests": self.total_requests,
|
|
104
|
+
"active_requests": self.active_requests,
|
|
105
|
+
"successful_requests": self.successful_requests,
|
|
106
|
+
"failed_requests": self.failed_requests,
|
|
107
|
+
"timeout_requests": self.timeout_requests,
|
|
108
|
+
"cancelled_requests": self.cancelled_requests,
|
|
109
|
+
"total_duration": self.total_duration,
|
|
110
|
+
"min_duration": self.min_duration,
|
|
111
|
+
"max_duration": self.max_duration,
|
|
112
|
+
"avg_duration": self.avg_duration,
|
|
113
|
+
"success_rate": self.get_success_rate(),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
def get_success_rate(self) -> float:
|
|
117
|
+
"""Calculate success rate."""
|
|
118
|
+
completed = self.successful_requests + self.failed_requests + self.timeout_requests + self.cancelled_requests
|
|
119
|
+
if completed == 0:
|
|
120
|
+
return 0.0
|
|
121
|
+
return self.successful_requests / completed
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class RequestTracker:
|
|
125
|
+
"""
|
|
126
|
+
Tracks requests automatically.
|
|
127
|
+
|
|
128
|
+
Features:
|
|
129
|
+
- Automatic request tracking
|
|
130
|
+
- Duration measurement
|
|
131
|
+
- Success/failure counting
|
|
132
|
+
- Active request monitoring
|
|
133
|
+
- Metrics aggregation by request type
|
|
134
|
+
- Request history
|
|
135
|
+
|
|
136
|
+
Usage:
|
|
137
|
+
tracker = RequestTracker()
|
|
138
|
+
|
|
139
|
+
# Track request manually
|
|
140
|
+
async with tracker.track("process_item") as request_id:
|
|
141
|
+
await process_item()
|
|
142
|
+
|
|
143
|
+
# Or use decorator
|
|
144
|
+
@track_request(tracker, "process_item")
|
|
145
|
+
async def process_item():
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
# Get metrics
|
|
149
|
+
metrics = tracker.get_metrics()
|
|
150
|
+
print(f"Active requests: {metrics.active_requests}")
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
max_history: int = 1000,
|
|
156
|
+
enable_history: bool = True,
|
|
157
|
+
on_request_start: Optional[Callable] = None,
|
|
158
|
+
on_request_complete: Optional[Callable] = None,
|
|
159
|
+
):
|
|
160
|
+
"""
|
|
161
|
+
Initialize request tracker.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
max_history: Maximum number of completed requests to keep in history
|
|
165
|
+
enable_history: Whether to keep request history
|
|
166
|
+
on_request_start: Callback when request starts
|
|
167
|
+
on_request_complete: Callback when request completes
|
|
168
|
+
"""
|
|
169
|
+
self.max_history = max_history
|
|
170
|
+
self.enable_history = enable_history
|
|
171
|
+
self.on_request_start = on_request_start
|
|
172
|
+
self.on_request_complete = on_request_complete
|
|
173
|
+
|
|
174
|
+
# Active requests
|
|
175
|
+
self._active_requests: Dict[str, RequestInfo] = {}
|
|
176
|
+
|
|
177
|
+
# Request history (completed requests)
|
|
178
|
+
self._request_history: List[RequestInfo] = []
|
|
179
|
+
|
|
180
|
+
# Metrics by request type
|
|
181
|
+
self._metrics: Dict[str, RequestMetrics] = defaultdict(RequestMetrics)
|
|
182
|
+
|
|
183
|
+
# Overall metrics
|
|
184
|
+
self._overall_metrics = RequestMetrics()
|
|
185
|
+
|
|
186
|
+
# Request counter
|
|
187
|
+
self._request_counter = 0
|
|
188
|
+
|
|
189
|
+
# Lock for thread safety
|
|
190
|
+
self._lock = asyncio.Lock()
|
|
191
|
+
|
|
192
|
+
logger.info(
|
|
193
|
+
f"RequestTracker initialized: max_history={max_history}, "
|
|
194
|
+
f"enable_history={enable_history}"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def _generate_request_id(self, request_type: str) -> str:
|
|
198
|
+
"""Generate unique request ID."""
|
|
199
|
+
self._request_counter += 1
|
|
200
|
+
timestamp = int(time.time() * 1000)
|
|
201
|
+
return f"{request_type}_{timestamp}_{self._request_counter}"
|
|
202
|
+
|
|
203
|
+
@asynccontextmanager
|
|
204
|
+
async def track(
|
|
205
|
+
self,
|
|
206
|
+
request_type: str,
|
|
207
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
208
|
+
timeout: Optional[float] = None,
|
|
209
|
+
):
|
|
210
|
+
"""
|
|
211
|
+
Context manager to track a request.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
request_type: Type of request
|
|
215
|
+
metadata: Optional metadata
|
|
216
|
+
timeout: Optional timeout in seconds
|
|
217
|
+
|
|
218
|
+
Yields:
|
|
219
|
+
Request ID
|
|
220
|
+
|
|
221
|
+
Example:
|
|
222
|
+
async with tracker.track("process_item") as request_id:
|
|
223
|
+
await process_item()
|
|
224
|
+
"""
|
|
225
|
+
request_id = self._generate_request_id(request_type)
|
|
226
|
+
|
|
227
|
+
async with self._lock:
|
|
228
|
+
# Create request info
|
|
229
|
+
request_info = RequestInfo(
|
|
230
|
+
request_id=request_id,
|
|
231
|
+
request_type=request_type,
|
|
232
|
+
status=RequestStatus.IN_PROGRESS,
|
|
233
|
+
start_time=time.time(),
|
|
234
|
+
metadata=metadata or {},
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Add to active requests
|
|
238
|
+
self._active_requests[request_id] = request_info
|
|
239
|
+
|
|
240
|
+
# Update metrics
|
|
241
|
+
self._metrics[request_type].total_requests += 1
|
|
242
|
+
self._metrics[request_type].active_requests += 1
|
|
243
|
+
self._overall_metrics.total_requests += 1
|
|
244
|
+
self._overall_metrics.active_requests += 1
|
|
245
|
+
|
|
246
|
+
# Call start callback
|
|
247
|
+
if self.on_request_start:
|
|
248
|
+
try:
|
|
249
|
+
if asyncio.iscoroutinefunction(self.on_request_start):
|
|
250
|
+
await self.on_request_start(request_info)
|
|
251
|
+
else:
|
|
252
|
+
self.on_request_start(request_info)
|
|
253
|
+
except Exception as e:
|
|
254
|
+
logger.error(f"Request start callback failed: {e}")
|
|
255
|
+
|
|
256
|
+
logger.debug(f"Request started: {request_id} ({request_type})")
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
# Execute with optional timeout
|
|
260
|
+
if timeout:
|
|
261
|
+
# Python 3.10 compatible timeout implementation
|
|
262
|
+
timeout_handle = None
|
|
263
|
+
timed_out = False
|
|
264
|
+
|
|
265
|
+
def _timeout_callback():
|
|
266
|
+
nonlocal timed_out
|
|
267
|
+
timed_out = True
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
# Set up timeout
|
|
271
|
+
loop = asyncio.get_event_loop()
|
|
272
|
+
timeout_handle = loop.call_later(timeout, _timeout_callback)
|
|
273
|
+
|
|
274
|
+
yield request_id
|
|
275
|
+
|
|
276
|
+
# Check if timed out
|
|
277
|
+
if timed_out:
|
|
278
|
+
await self._complete_request(request_id, RequestStatus.TIMEOUT, "Request timeout")
|
|
279
|
+
raise asyncio.TimeoutError("Request timeout")
|
|
280
|
+
|
|
281
|
+
await self._complete_request(request_id, RequestStatus.SUCCESS)
|
|
282
|
+
|
|
283
|
+
finally:
|
|
284
|
+
# Cancel timeout if still pending
|
|
285
|
+
if timeout_handle is not None:
|
|
286
|
+
timeout_handle.cancel()
|
|
287
|
+
else:
|
|
288
|
+
yield request_id
|
|
289
|
+
await self._complete_request(request_id, RequestStatus.SUCCESS)
|
|
290
|
+
|
|
291
|
+
except asyncio.CancelledError:
|
|
292
|
+
await self._complete_request(request_id, RequestStatus.CANCELLED, "Request cancelled")
|
|
293
|
+
raise
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
await self._complete_request(request_id, RequestStatus.FAILED, str(e))
|
|
297
|
+
raise
|
|
298
|
+
|
|
299
|
+
async def _complete_request(
|
|
300
|
+
self,
|
|
301
|
+
request_id: str,
|
|
302
|
+
status: RequestStatus,
|
|
303
|
+
error: Optional[str] = None,
|
|
304
|
+
) -> None:
|
|
305
|
+
"""Complete a request."""
|
|
306
|
+
async with self._lock:
|
|
307
|
+
if request_id not in self._active_requests:
|
|
308
|
+
logger.warning(f"Request {request_id} not found in active requests")
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
request_info = self._active_requests[request_id]
|
|
312
|
+
request_type = request_info.request_type
|
|
313
|
+
|
|
314
|
+
# Update request info
|
|
315
|
+
request_info.status = status
|
|
316
|
+
request_info.end_time = time.time()
|
|
317
|
+
request_info.duration = request_info.end_time - request_info.start_time
|
|
318
|
+
request_info.error = error
|
|
319
|
+
|
|
320
|
+
# Update metrics
|
|
321
|
+
self._metrics[request_type].active_requests -= 1
|
|
322
|
+
self._overall_metrics.active_requests -= 1
|
|
323
|
+
|
|
324
|
+
if status == RequestStatus.SUCCESS:
|
|
325
|
+
self._metrics[request_type].successful_requests += 1
|
|
326
|
+
self._overall_metrics.successful_requests += 1
|
|
327
|
+
elif status == RequestStatus.FAILED:
|
|
328
|
+
self._metrics[request_type].failed_requests += 1
|
|
329
|
+
self._overall_metrics.failed_requests += 1
|
|
330
|
+
elif status == RequestStatus.TIMEOUT:
|
|
331
|
+
self._metrics[request_type].timeout_requests += 1
|
|
332
|
+
self._overall_metrics.timeout_requests += 1
|
|
333
|
+
elif status == RequestStatus.CANCELLED:
|
|
334
|
+
self._metrics[request_type].cancelled_requests += 1
|
|
335
|
+
self._overall_metrics.cancelled_requests += 1
|
|
336
|
+
|
|
337
|
+
# Update duration stats
|
|
338
|
+
if request_info.duration is not None:
|
|
339
|
+
self._metrics[request_type].update_duration(request_info.duration)
|
|
340
|
+
self._overall_metrics.update_duration(request_info.duration)
|
|
341
|
+
|
|
342
|
+
# Move to history
|
|
343
|
+
if self.enable_history:
|
|
344
|
+
self._request_history.append(request_info)
|
|
345
|
+
if len(self._request_history) > self.max_history:
|
|
346
|
+
self._request_history.pop(0)
|
|
347
|
+
|
|
348
|
+
# Remove from active
|
|
349
|
+
del self._active_requests[request_id]
|
|
350
|
+
|
|
351
|
+
# Call complete callback
|
|
352
|
+
if self.on_request_complete:
|
|
353
|
+
try:
|
|
354
|
+
if asyncio.iscoroutinefunction(self.on_request_complete):
|
|
355
|
+
await self.on_request_complete(request_info)
|
|
356
|
+
else:
|
|
357
|
+
self.on_request_complete(request_info)
|
|
358
|
+
except Exception as e:
|
|
359
|
+
logger.error(f"Request complete callback failed: {e}")
|
|
360
|
+
|
|
361
|
+
logger.debug(
|
|
362
|
+
f"Request completed: {request_id} ({request_type}) - "
|
|
363
|
+
f"{status.value} in {request_info.duration:.3f}s"
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
def get_active_requests(self, request_type: Optional[str] = None) -> List[RequestInfo]:
|
|
367
|
+
"""
|
|
368
|
+
Get active requests.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
request_type: Optional filter by request type
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
List of active request info
|
|
375
|
+
"""
|
|
376
|
+
if request_type:
|
|
377
|
+
return [
|
|
378
|
+
req for req in self._active_requests.values()
|
|
379
|
+
if req.request_type == request_type
|
|
380
|
+
]
|
|
381
|
+
return list(self._active_requests.values())
|
|
382
|
+
|
|
383
|
+
def get_request_history(
|
|
384
|
+
self,
|
|
385
|
+
request_type: Optional[str] = None,
|
|
386
|
+
limit: Optional[int] = None,
|
|
387
|
+
) -> List[RequestInfo]:
|
|
388
|
+
"""
|
|
389
|
+
Get request history.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
request_type: Optional filter by request type
|
|
393
|
+
limit: Optional limit on number of requests
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
List of completed request info (most recent first)
|
|
397
|
+
"""
|
|
398
|
+
history = list(reversed(self._request_history))
|
|
399
|
+
|
|
400
|
+
if request_type:
|
|
401
|
+
history = [req for req in history if req.request_type == request_type]
|
|
402
|
+
|
|
403
|
+
if limit:
|
|
404
|
+
history = history[:limit]
|
|
405
|
+
|
|
406
|
+
return history
|
|
407
|
+
|
|
408
|
+
def get_metrics(self, request_type: Optional[str] = None) -> RequestMetrics:
|
|
409
|
+
"""
|
|
410
|
+
Get request metrics.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
request_type: Optional filter by request type (None for overall)
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
RequestMetrics
|
|
417
|
+
"""
|
|
418
|
+
if request_type:
|
|
419
|
+
return self._metrics[request_type]
|
|
420
|
+
return self._overall_metrics
|
|
421
|
+
|
|
422
|
+
def get_all_metrics(self) -> Dict[str, RequestMetrics]:
|
|
423
|
+
"""
|
|
424
|
+
Get metrics for all request types.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Dictionary mapping request type to metrics
|
|
428
|
+
"""
|
|
429
|
+
return dict(self._metrics)
|
|
430
|
+
|
|
431
|
+
def reset_metrics(self, request_type: Optional[str] = None) -> None:
|
|
432
|
+
"""
|
|
433
|
+
Reset metrics.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
request_type: Optional request type (None resets all)
|
|
437
|
+
"""
|
|
438
|
+
if request_type:
|
|
439
|
+
self._metrics[request_type] = RequestMetrics()
|
|
440
|
+
else:
|
|
441
|
+
self._metrics.clear()
|
|
442
|
+
self._overall_metrics = RequestMetrics()
|
|
443
|
+
|
|
444
|
+
logger.info(f"Metrics reset: {request_type or 'all'}")
|
|
445
|
+
|
|
446
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
447
|
+
"""
|
|
448
|
+
Get tracker statistics.
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
Dictionary of statistics
|
|
452
|
+
"""
|
|
453
|
+
return {
|
|
454
|
+
"active_requests_count": len(self._active_requests),
|
|
455
|
+
"history_count": len(self._request_history),
|
|
456
|
+
"tracked_request_types": len(self._metrics),
|
|
457
|
+
"overall_metrics": self._overall_metrics.to_dict(),
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
# Decorator for automatic request tracking
|
|
462
|
+
|
|
463
|
+
def track_request(
|
|
464
|
+
tracker: RequestTracker,
|
|
465
|
+
request_type: Optional[str] = None,
|
|
466
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
467
|
+
timeout: Optional[float] = None,
|
|
468
|
+
):
|
|
469
|
+
"""
|
|
470
|
+
Decorator to automatically track requests.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
tracker: RequestTracker instance
|
|
474
|
+
request_type: Type of request (uses function name if None)
|
|
475
|
+
metadata: Optional metadata
|
|
476
|
+
timeout: Optional timeout in seconds
|
|
477
|
+
|
|
478
|
+
Example:
|
|
479
|
+
tracker = RequestTracker()
|
|
480
|
+
|
|
481
|
+
@track_request(tracker, "process_item")
|
|
482
|
+
async def process_item(item):
|
|
483
|
+
# Processing logic
|
|
484
|
+
pass
|
|
485
|
+
|
|
486
|
+
# Request automatically tracked with duration, success/failure
|
|
487
|
+
await process_item(my_item)
|
|
488
|
+
"""
|
|
489
|
+
def decorator(func):
|
|
490
|
+
nonlocal request_type
|
|
491
|
+
if request_type is None:
|
|
492
|
+
request_type = func.__name__
|
|
493
|
+
|
|
494
|
+
@wraps(func)
|
|
495
|
+
async def wrapper(*args, **kwargs):
|
|
496
|
+
async with tracker.track(request_type, metadata, timeout):
|
|
497
|
+
return await func(*args, **kwargs)
|
|
498
|
+
|
|
499
|
+
return wrapper
|
|
500
|
+
|
|
501
|
+
return decorator
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Migration modules for state management during pod transitions."""
|
|
2
|
+
|
|
3
|
+
from dory.migration.state_manager import StateManager
|
|
4
|
+
from dory.migration.serialization import StateSerializer
|
|
5
|
+
from dory.migration.configmap import ConfigMapStore
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"StateManager",
|
|
9
|
+
"StateSerializer",
|
|
10
|
+
"ConfigMapStore",
|
|
11
|
+
]
|