mdb-engine 0.1.6__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.
- mdb_engine/README.md +144 -0
- mdb_engine/__init__.py +37 -0
- mdb_engine/auth/README.md +631 -0
- mdb_engine/auth/__init__.py +128 -0
- mdb_engine/auth/casbin_factory.py +199 -0
- mdb_engine/auth/casbin_models.py +46 -0
- mdb_engine/auth/config_defaults.py +71 -0
- mdb_engine/auth/config_helpers.py +213 -0
- mdb_engine/auth/cookie_utils.py +158 -0
- mdb_engine/auth/decorators.py +350 -0
- mdb_engine/auth/dependencies.py +747 -0
- mdb_engine/auth/helpers.py +64 -0
- mdb_engine/auth/integration.py +578 -0
- mdb_engine/auth/jwt.py +225 -0
- mdb_engine/auth/middleware.py +241 -0
- mdb_engine/auth/oso_factory.py +323 -0
- mdb_engine/auth/provider.py +570 -0
- mdb_engine/auth/restrictions.py +271 -0
- mdb_engine/auth/session_manager.py +477 -0
- mdb_engine/auth/token_lifecycle.py +213 -0
- mdb_engine/auth/token_store.py +289 -0
- mdb_engine/auth/users.py +1516 -0
- mdb_engine/auth/utils.py +614 -0
- mdb_engine/cli/__init__.py +13 -0
- mdb_engine/cli/commands/__init__.py +7 -0
- mdb_engine/cli/commands/generate.py +105 -0
- mdb_engine/cli/commands/migrate.py +83 -0
- mdb_engine/cli/commands/show.py +70 -0
- mdb_engine/cli/commands/validate.py +63 -0
- mdb_engine/cli/main.py +41 -0
- mdb_engine/cli/utils.py +92 -0
- mdb_engine/config.py +217 -0
- mdb_engine/constants.py +160 -0
- mdb_engine/core/README.md +542 -0
- mdb_engine/core/__init__.py +42 -0
- mdb_engine/core/app_registration.py +392 -0
- mdb_engine/core/connection.py +243 -0
- mdb_engine/core/engine.py +749 -0
- mdb_engine/core/index_management.py +162 -0
- mdb_engine/core/manifest.py +2793 -0
- mdb_engine/core/seeding.py +179 -0
- mdb_engine/core/service_initialization.py +355 -0
- mdb_engine/core/types.py +413 -0
- mdb_engine/database/README.md +522 -0
- mdb_engine/database/__init__.py +31 -0
- mdb_engine/database/abstraction.py +635 -0
- mdb_engine/database/connection.py +387 -0
- mdb_engine/database/scoped_wrapper.py +1721 -0
- mdb_engine/embeddings/README.md +184 -0
- mdb_engine/embeddings/__init__.py +62 -0
- mdb_engine/embeddings/dependencies.py +193 -0
- mdb_engine/embeddings/service.py +759 -0
- mdb_engine/exceptions.py +167 -0
- mdb_engine/indexes/README.md +651 -0
- mdb_engine/indexes/__init__.py +21 -0
- mdb_engine/indexes/helpers.py +145 -0
- mdb_engine/indexes/manager.py +895 -0
- mdb_engine/memory/README.md +451 -0
- mdb_engine/memory/__init__.py +30 -0
- mdb_engine/memory/service.py +1285 -0
- mdb_engine/observability/README.md +515 -0
- mdb_engine/observability/__init__.py +42 -0
- mdb_engine/observability/health.py +296 -0
- mdb_engine/observability/logging.py +161 -0
- mdb_engine/observability/metrics.py +297 -0
- mdb_engine/routing/README.md +462 -0
- mdb_engine/routing/__init__.py +73 -0
- mdb_engine/routing/websockets.py +813 -0
- mdb_engine/utils/__init__.py +7 -0
- mdb_engine-0.1.6.dist-info/METADATA +213 -0
- mdb_engine-0.1.6.dist-info/RECORD +75 -0
- mdb_engine-0.1.6.dist-info/WHEEL +5 -0
- mdb_engine-0.1.6.dist-info/entry_points.txt +2 -0
- mdb_engine-0.1.6.dist-info/licenses/LICENSE +661 -0
- mdb_engine-0.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Metrics collection for MDB_ENGINE.
|
|
3
|
+
|
|
4
|
+
This module provides metrics collection for monitoring engine performance,
|
|
5
|
+
operations, and health.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
from collections import OrderedDict
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Any, Callable, Dict, Optional
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class OperationMetrics:
|
|
21
|
+
"""Metrics for a single operation."""
|
|
22
|
+
|
|
23
|
+
operation_name: str
|
|
24
|
+
count: int = 0
|
|
25
|
+
total_duration_ms: float = 0.0
|
|
26
|
+
min_duration_ms: float = float("inf")
|
|
27
|
+
max_duration_ms: float = 0.0
|
|
28
|
+
error_count: int = 0
|
|
29
|
+
last_execution: Optional[datetime] = None
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def avg_duration_ms(self) -> float:
|
|
33
|
+
"""Calculate average duration in milliseconds."""
|
|
34
|
+
return self.total_duration_ms / self.count if self.count > 0 else 0.0
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def error_rate(self) -> float:
|
|
38
|
+
"""Calculate error rate as percentage."""
|
|
39
|
+
return (self.error_count / self.count * 100) if self.count > 0 else 0.0
|
|
40
|
+
|
|
41
|
+
def record(self, duration_ms: float, success: bool = True) -> None:
|
|
42
|
+
"""Record a single operation execution."""
|
|
43
|
+
self.count += 1
|
|
44
|
+
self.total_duration_ms += duration_ms
|
|
45
|
+
self.min_duration_ms = min(self.min_duration_ms, duration_ms)
|
|
46
|
+
self.max_duration_ms = max(self.max_duration_ms, duration_ms)
|
|
47
|
+
if not success:
|
|
48
|
+
self.error_count += 1
|
|
49
|
+
self.last_execution = datetime.now()
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
52
|
+
"""Convert metrics to dictionary."""
|
|
53
|
+
return {
|
|
54
|
+
"operation": self.operation_name,
|
|
55
|
+
"count": self.count,
|
|
56
|
+
"avg_duration_ms": round(self.avg_duration_ms, 2),
|
|
57
|
+
"min_duration_ms": (
|
|
58
|
+
round(self.min_duration_ms, 2)
|
|
59
|
+
if self.min_duration_ms != float("inf")
|
|
60
|
+
else 0.0
|
|
61
|
+
),
|
|
62
|
+
"max_duration_ms": round(self.max_duration_ms, 2),
|
|
63
|
+
"error_count": self.error_count,
|
|
64
|
+
"error_rate_percent": round(self.error_rate, 2),
|
|
65
|
+
"last_execution": (
|
|
66
|
+
self.last_execution.isoformat() if self.last_execution else None
|
|
67
|
+
),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MetricsCollector:
|
|
72
|
+
"""
|
|
73
|
+
Centralized metrics collector for MDB_ENGINE.
|
|
74
|
+
|
|
75
|
+
Collects and aggregates metrics for:
|
|
76
|
+
- Engine operations (initialization, app registration)
|
|
77
|
+
- Database operations (queries, writes)
|
|
78
|
+
- Index operations (creation, updates)
|
|
79
|
+
- Performance metrics (latency, throughput)
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, max_metrics: int = 10000):
|
|
83
|
+
"""
|
|
84
|
+
Initialize the metrics collector.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
max_metrics: Maximum number of metrics to store before evicting oldest (LRU).
|
|
88
|
+
Defaults to 10000.
|
|
89
|
+
"""
|
|
90
|
+
self._metrics: OrderedDict[str, OperationMetrics] = OrderedDict()
|
|
91
|
+
self._lock = threading.Lock()
|
|
92
|
+
self._max_metrics = max_metrics
|
|
93
|
+
|
|
94
|
+
def record_operation(
|
|
95
|
+
self, operation_name: str, duration_ms: float, success: bool = True, **tags: Any
|
|
96
|
+
) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Record an operation execution.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
operation_name: Name of the operation (e.g., "engine.initialize")
|
|
102
|
+
duration_ms: Duration in milliseconds
|
|
103
|
+
success: Whether the operation succeeded
|
|
104
|
+
**tags: Additional tags for filtering (app_slug, collection_name, etc.)
|
|
105
|
+
"""
|
|
106
|
+
# Create a key that includes tags for more granular metrics
|
|
107
|
+
key = operation_name
|
|
108
|
+
if tags:
|
|
109
|
+
tag_str = "_".join(f"{k}={v}" for k, v in sorted(tags.items()))
|
|
110
|
+
key = f"{operation_name}[{tag_str}]"
|
|
111
|
+
|
|
112
|
+
with self._lock:
|
|
113
|
+
# Check if this is a new metric
|
|
114
|
+
is_new = key not in self._metrics
|
|
115
|
+
|
|
116
|
+
# Evict oldest if at capacity and adding new metric
|
|
117
|
+
if is_new and len(self._metrics) >= self._max_metrics:
|
|
118
|
+
self._metrics.popitem(last=False) # Remove oldest (LRU)
|
|
119
|
+
|
|
120
|
+
if is_new:
|
|
121
|
+
self._metrics[key] = OperationMetrics(operation_name=operation_name)
|
|
122
|
+
else:
|
|
123
|
+
# Move to end (most recently used)
|
|
124
|
+
self._metrics.move_to_end(key)
|
|
125
|
+
|
|
126
|
+
self._metrics[key].record(duration_ms, success)
|
|
127
|
+
|
|
128
|
+
def get_metrics(self, operation_name: Optional[str] = None) -> Dict[str, Any]:
|
|
129
|
+
"""
|
|
130
|
+
Get metrics for operations.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
operation_name: Optional operation name to filter by
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dictionary of metrics
|
|
137
|
+
"""
|
|
138
|
+
with self._lock:
|
|
139
|
+
if operation_name:
|
|
140
|
+
metrics = {
|
|
141
|
+
k: v.to_dict()
|
|
142
|
+
for k, v in self._metrics.items()
|
|
143
|
+
if k.startswith(operation_name)
|
|
144
|
+
}
|
|
145
|
+
# Move accessed metrics to end (LRU)
|
|
146
|
+
for key in list(self._metrics.keys()):
|
|
147
|
+
if key.startswith(operation_name):
|
|
148
|
+
self._metrics.move_to_end(key)
|
|
149
|
+
else:
|
|
150
|
+
metrics = {k: v.to_dict() for k, v in self._metrics.items()}
|
|
151
|
+
# Move all accessed metrics to end (LRU)
|
|
152
|
+
for key in list(self._metrics.keys()):
|
|
153
|
+
self._metrics.move_to_end(key)
|
|
154
|
+
|
|
155
|
+
total_operations = len(self._metrics)
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
"timestamp": datetime.now().isoformat(),
|
|
159
|
+
"metrics": metrics,
|
|
160
|
+
"total_operations": total_operations,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
def get_summary(self) -> Dict[str, Any]:
|
|
164
|
+
"""
|
|
165
|
+
Get a summary of all metrics.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Summary dictionary with aggregated statistics
|
|
169
|
+
"""
|
|
170
|
+
with self._lock:
|
|
171
|
+
if not self._metrics:
|
|
172
|
+
return {
|
|
173
|
+
"timestamp": datetime.now().isoformat(),
|
|
174
|
+
"total_operations": 0,
|
|
175
|
+
"summary": {},
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Aggregate by base operation name (without tags)
|
|
179
|
+
aggregated: Dict[str, OperationMetrics] = {}
|
|
180
|
+
|
|
181
|
+
for key, metric in self._metrics.items():
|
|
182
|
+
base_name = metric.operation_name
|
|
183
|
+
if base_name not in aggregated:
|
|
184
|
+
aggregated[base_name] = OperationMetrics(operation_name=base_name)
|
|
185
|
+
|
|
186
|
+
agg = aggregated[base_name]
|
|
187
|
+
agg.count += metric.count
|
|
188
|
+
agg.total_duration_ms += metric.total_duration_ms
|
|
189
|
+
agg.min_duration_ms = min(agg.min_duration_ms, metric.min_duration_ms)
|
|
190
|
+
agg.max_duration_ms = max(agg.max_duration_ms, metric.max_duration_ms)
|
|
191
|
+
agg.error_count += metric.error_count
|
|
192
|
+
if metric.last_execution and (
|
|
193
|
+
not agg.last_execution or metric.last_execution > agg.last_execution
|
|
194
|
+
):
|
|
195
|
+
agg.last_execution = metric.last_execution
|
|
196
|
+
|
|
197
|
+
# Move all accessed metrics to end (LRU)
|
|
198
|
+
for key in list(self._metrics.keys()):
|
|
199
|
+
self._metrics.move_to_end(key)
|
|
200
|
+
|
|
201
|
+
total_operations = len(self._metrics)
|
|
202
|
+
summary = {name: m.to_dict() for name, m in aggregated.items()}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
"timestamp": datetime.now().isoformat(),
|
|
206
|
+
"total_operations": total_operations,
|
|
207
|
+
"summary": summary,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
def reset(self) -> None:
|
|
211
|
+
"""Reset all metrics."""
|
|
212
|
+
with self._lock:
|
|
213
|
+
self._metrics.clear()
|
|
214
|
+
|
|
215
|
+
def get_operation_count(self, operation_name: str) -> int:
|
|
216
|
+
"""Get the count of executions for an operation."""
|
|
217
|
+
with self._lock:
|
|
218
|
+
total = 0
|
|
219
|
+
for metric in self._metrics.values():
|
|
220
|
+
if metric.operation_name == operation_name:
|
|
221
|
+
total += metric.count
|
|
222
|
+
return total
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# Global metrics collector instance
|
|
226
|
+
_metrics_collector: Optional[MetricsCollector] = None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def get_metrics_collector() -> MetricsCollector:
|
|
230
|
+
"""Get or create the global metrics collector."""
|
|
231
|
+
global _metrics_collector
|
|
232
|
+
if _metrics_collector is None:
|
|
233
|
+
_metrics_collector = MetricsCollector()
|
|
234
|
+
return _metrics_collector
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def record_operation(
|
|
238
|
+
operation_name: str, duration_ms: float, success: bool = True, **tags: Any
|
|
239
|
+
) -> None:
|
|
240
|
+
"""
|
|
241
|
+
Record an operation in the global metrics collector.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
operation_name: Name of the operation
|
|
245
|
+
duration_ms: Duration in milliseconds
|
|
246
|
+
success: Whether the operation succeeded
|
|
247
|
+
**tags: Additional tags
|
|
248
|
+
"""
|
|
249
|
+
collector = get_metrics_collector()
|
|
250
|
+
collector.record_operation(operation_name, duration_ms, success, **tags)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def timed_operation(operation_name: str, **tags: Any):
|
|
254
|
+
"""
|
|
255
|
+
Decorator to automatically time and record an operation.
|
|
256
|
+
|
|
257
|
+
Usage:
|
|
258
|
+
@timed_operation("engine.initialize")
|
|
259
|
+
async def initialize(self):
|
|
260
|
+
...
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
def decorator(func: Callable) -> Callable:
|
|
264
|
+
if hasattr(func, "__code__") and func.__code__.co_flags & 0x80: # CO_COROUTINE
|
|
265
|
+
# Async function
|
|
266
|
+
async def async_wrapper(*args, **kwargs):
|
|
267
|
+
start_time = time.time()
|
|
268
|
+
success = True
|
|
269
|
+
try:
|
|
270
|
+
result = await func(*args, **kwargs)
|
|
271
|
+
return result
|
|
272
|
+
except Exception:
|
|
273
|
+
success = False
|
|
274
|
+
raise
|
|
275
|
+
finally:
|
|
276
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
277
|
+
record_operation(operation_name, duration_ms, success, **tags)
|
|
278
|
+
|
|
279
|
+
return async_wrapper
|
|
280
|
+
else:
|
|
281
|
+
# Sync function
|
|
282
|
+
def sync_wrapper(*args, **kwargs):
|
|
283
|
+
start_time = time.time()
|
|
284
|
+
success = True
|
|
285
|
+
try:
|
|
286
|
+
result = func(*args, **kwargs)
|
|
287
|
+
return result
|
|
288
|
+
except Exception:
|
|
289
|
+
success = False
|
|
290
|
+
raise
|
|
291
|
+
finally:
|
|
292
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
293
|
+
record_operation(operation_name, duration_ms, success, **tags)
|
|
294
|
+
|
|
295
|
+
return sync_wrapper
|
|
296
|
+
|
|
297
|
+
return decorator
|