kailash 0.6.6__py3-none-any.whl → 0.8.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.
- kailash/__init__.py +35 -5
- kailash/access_control.py +64 -46
- kailash/adapters/__init__.py +5 -0
- kailash/adapters/mcp_platform_adapter.py +273 -0
- kailash/api/workflow_api.py +34 -3
- kailash/channels/__init__.py +21 -0
- kailash/channels/api_channel.py +409 -0
- kailash/channels/base.py +271 -0
- kailash/channels/cli_channel.py +661 -0
- kailash/channels/event_router.py +496 -0
- kailash/channels/mcp_channel.py +648 -0
- kailash/channels/session.py +423 -0
- kailash/mcp_server/discovery.py +57 -18
- kailash/middleware/communication/api_gateway.py +23 -3
- kailash/middleware/communication/realtime.py +83 -0
- kailash/middleware/core/agent_ui.py +1 -1
- kailash/middleware/gateway/storage_backends.py +393 -0
- kailash/middleware/mcp/enhanced_server.py +22 -16
- kailash/nexus/__init__.py +21 -0
- kailash/nexus/cli/__init__.py +5 -0
- kailash/nexus/cli/__main__.py +6 -0
- kailash/nexus/cli/main.py +176 -0
- kailash/nexus/factory.py +413 -0
- kailash/nexus/gateway.py +545 -0
- kailash/nodes/__init__.py +8 -5
- kailash/nodes/ai/iterative_llm_agent.py +988 -17
- kailash/nodes/ai/llm_agent.py +29 -9
- kailash/nodes/api/__init__.py +2 -2
- kailash/nodes/api/monitoring.py +1 -1
- kailash/nodes/base.py +29 -5
- kailash/nodes/base_async.py +54 -14
- kailash/nodes/code/async_python.py +1 -1
- kailash/nodes/code/python.py +50 -6
- kailash/nodes/data/async_sql.py +90 -0
- kailash/nodes/data/bulk_operations.py +939 -0
- kailash/nodes/data/query_builder.py +373 -0
- kailash/nodes/data/query_cache.py +512 -0
- kailash/nodes/monitoring/__init__.py +10 -0
- kailash/nodes/monitoring/deadlock_detector.py +964 -0
- kailash/nodes/monitoring/performance_anomaly.py +1078 -0
- kailash/nodes/monitoring/race_condition_detector.py +1151 -0
- kailash/nodes/monitoring/transaction_metrics.py +790 -0
- kailash/nodes/monitoring/transaction_monitor.py +931 -0
- kailash/nodes/security/behavior_analysis.py +414 -0
- kailash/nodes/system/__init__.py +17 -0
- kailash/nodes/system/command_parser.py +820 -0
- kailash/nodes/transaction/__init__.py +48 -0
- kailash/nodes/transaction/distributed_transaction_manager.py +983 -0
- kailash/nodes/transaction/saga_coordinator.py +652 -0
- kailash/nodes/transaction/saga_state_storage.py +411 -0
- kailash/nodes/transaction/saga_step.py +467 -0
- kailash/nodes/transaction/transaction_context.py +756 -0
- kailash/nodes/transaction/two_phase_commit.py +978 -0
- kailash/nodes/transform/processors.py +17 -1
- kailash/nodes/validation/__init__.py +21 -0
- kailash/nodes/validation/test_executor.py +532 -0
- kailash/nodes/validation/validation_nodes.py +447 -0
- kailash/resources/factory.py +1 -1
- kailash/runtime/access_controlled.py +9 -7
- kailash/runtime/async_local.py +84 -21
- kailash/runtime/local.py +21 -2
- kailash/runtime/parameter_injector.py +187 -31
- kailash/runtime/runner.py +6 -4
- kailash/runtime/testing.py +1 -1
- kailash/security.py +22 -3
- kailash/servers/__init__.py +32 -0
- kailash/servers/durable_workflow_server.py +430 -0
- kailash/servers/enterprise_workflow_server.py +522 -0
- kailash/servers/gateway.py +183 -0
- kailash/servers/workflow_server.py +293 -0
- kailash/utils/data_validation.py +192 -0
- kailash/workflow/builder.py +382 -15
- kailash/workflow/cyclic_runner.py +102 -10
- kailash/workflow/validation.py +144 -8
- kailash/workflow/visualization.py +99 -27
- {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/METADATA +3 -2
- {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/RECORD +81 -40
- kailash/workflow/builder_improvements.py +0 -207
- {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/WHEEL +0 -0
- {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.6.6.dist-info → kailash-0.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,496 @@
|
|
1
|
+
"""Cross-channel event router for Nexus framework."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import fnmatch
|
5
|
+
import logging
|
6
|
+
import time
|
7
|
+
from dataclasses import dataclass, field
|
8
|
+
from enum import Enum
|
9
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Union
|
10
|
+
|
11
|
+
from .base import Channel, ChannelEvent
|
12
|
+
from .session import CrossChannelSession, SessionManager
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
class RoutingRule(Enum):
|
18
|
+
"""Event routing rule types."""
|
19
|
+
|
20
|
+
BROADCAST = "broadcast" # Send to all channels
|
21
|
+
UNICAST = "unicast" # Send to specific channel
|
22
|
+
MULTICAST = "multicast" # Send to selected channels
|
23
|
+
SESSION = "session" # Route based on session
|
24
|
+
PATTERN = "pattern" # Route based on pattern matching
|
25
|
+
|
26
|
+
|
27
|
+
@dataclass
|
28
|
+
class EventRoute:
|
29
|
+
"""Defines an event routing rule."""
|
30
|
+
|
31
|
+
rule_type: RoutingRule
|
32
|
+
source_patterns: List[str] = field(default_factory=list)
|
33
|
+
target_channels: List[str] = field(default_factory=list)
|
34
|
+
event_type_patterns: List[str] = field(default_factory=list)
|
35
|
+
session_filter: Optional[Callable[[CrossChannelSession], bool]] = None
|
36
|
+
condition: Optional[Callable[[ChannelEvent], bool]] = None
|
37
|
+
transform: Optional[Callable[[ChannelEvent], ChannelEvent]] = None
|
38
|
+
priority: int = 100 # Lower number = higher priority
|
39
|
+
enabled: bool = True
|
40
|
+
|
41
|
+
|
42
|
+
@dataclass
|
43
|
+
class RoutingStats:
|
44
|
+
"""Event routing statistics."""
|
45
|
+
|
46
|
+
total_events: int = 0
|
47
|
+
routed_events: int = 0
|
48
|
+
dropped_events: int = 0
|
49
|
+
failed_events: int = 0
|
50
|
+
routes_matched: Dict[str, int] = field(default_factory=dict)
|
51
|
+
channel_stats: Dict[str, Dict[str, int]] = field(default_factory=dict)
|
52
|
+
|
53
|
+
|
54
|
+
class EventRouter:
|
55
|
+
"""Cross-channel event router for the Nexus framework.
|
56
|
+
|
57
|
+
This router handles event distribution between different channels,
|
58
|
+
enabling unified communication across API, CLI, and MCP interfaces.
|
59
|
+
"""
|
60
|
+
|
61
|
+
def __init__(self, session_manager: Optional[SessionManager] = None):
|
62
|
+
"""Initialize event router.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
session_manager: Optional session manager for session-based routing
|
66
|
+
"""
|
67
|
+
self.session_manager = session_manager
|
68
|
+
self._channels: Dict[str, Channel] = {}
|
69
|
+
self._routes: List[EventRoute] = []
|
70
|
+
self._event_queue: Optional[asyncio.Queue] = None
|
71
|
+
self._router_task: Optional[asyncio.Task] = None
|
72
|
+
self._running = False
|
73
|
+
self._stats = RoutingStats()
|
74
|
+
|
75
|
+
# Setup default routes
|
76
|
+
self._setup_default_routes()
|
77
|
+
|
78
|
+
logger.info("Event router initialized")
|
79
|
+
|
80
|
+
def _setup_default_routes(self) -> None:
|
81
|
+
"""Set up default routing rules."""
|
82
|
+
|
83
|
+
# Route channel lifecycle events to all other channels
|
84
|
+
self.add_route(
|
85
|
+
EventRoute(
|
86
|
+
rule_type=RoutingRule.BROADCAST,
|
87
|
+
event_type_patterns=[
|
88
|
+
"channel_started",
|
89
|
+
"channel_stopped",
|
90
|
+
"channel_error",
|
91
|
+
],
|
92
|
+
priority=50,
|
93
|
+
)
|
94
|
+
)
|
95
|
+
|
96
|
+
# Route session events to channels in the same session
|
97
|
+
self.add_route(
|
98
|
+
EventRoute(
|
99
|
+
rule_type=RoutingRule.SESSION,
|
100
|
+
event_type_patterns=["session_*"],
|
101
|
+
priority=60,
|
102
|
+
)
|
103
|
+
)
|
104
|
+
|
105
|
+
# Route workflow events based on session
|
106
|
+
self.add_route(
|
107
|
+
EventRoute(
|
108
|
+
rule_type=RoutingRule.SESSION,
|
109
|
+
event_type_patterns=["workflow_*", "mcp_*", "command_*"],
|
110
|
+
priority=70,
|
111
|
+
)
|
112
|
+
)
|
113
|
+
|
114
|
+
async def start(self) -> None:
|
115
|
+
"""Start the event router."""
|
116
|
+
if self._running:
|
117
|
+
logger.warning("Event router is already running")
|
118
|
+
return
|
119
|
+
|
120
|
+
self._running = True
|
121
|
+
self._event_queue = asyncio.Queue(maxsize=10000)
|
122
|
+
self._router_task = asyncio.create_task(self._routing_loop())
|
123
|
+
|
124
|
+
logger.info("Event router started")
|
125
|
+
|
126
|
+
async def stop(self) -> None:
|
127
|
+
"""Stop the event router."""
|
128
|
+
if not self._running:
|
129
|
+
return
|
130
|
+
|
131
|
+
self._running = False
|
132
|
+
|
133
|
+
if self._router_task and not self._router_task.done():
|
134
|
+
self._router_task.cancel()
|
135
|
+
try:
|
136
|
+
await self._router_task
|
137
|
+
except asyncio.CancelledError:
|
138
|
+
pass
|
139
|
+
|
140
|
+
# Clear remaining events
|
141
|
+
if self._event_queue:
|
142
|
+
while not self._event_queue.empty():
|
143
|
+
try:
|
144
|
+
self._event_queue.get_nowait()
|
145
|
+
except asyncio.QueueEmpty:
|
146
|
+
break
|
147
|
+
|
148
|
+
logger.info("Event router stopped")
|
149
|
+
|
150
|
+
def register_channel(self, channel: Channel) -> None:
|
151
|
+
"""Register a channel with the router.
|
152
|
+
|
153
|
+
Args:
|
154
|
+
channel: Channel to register
|
155
|
+
"""
|
156
|
+
self._channels[channel.name] = channel
|
157
|
+
|
158
|
+
# Initialize channel stats
|
159
|
+
if channel.name not in self._stats.channel_stats:
|
160
|
+
self._stats.channel_stats[channel.name] = {
|
161
|
+
"events_sent": 0,
|
162
|
+
"events_received": 0,
|
163
|
+
"events_failed": 0,
|
164
|
+
}
|
165
|
+
|
166
|
+
logger.info(f"Registered channel '{channel.name}' with event router")
|
167
|
+
|
168
|
+
def unregister_channel(self, channel_name: str) -> None:
|
169
|
+
"""Unregister a channel from the router.
|
170
|
+
|
171
|
+
Args:
|
172
|
+
channel_name: Name of channel to unregister
|
173
|
+
"""
|
174
|
+
if channel_name in self._channels:
|
175
|
+
del self._channels[channel_name]
|
176
|
+
logger.info(f"Unregistered channel '{channel_name}' from event router")
|
177
|
+
|
178
|
+
def add_route(self, route: EventRoute) -> None:
|
179
|
+
"""Add a routing rule.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
route: Routing rule to add
|
183
|
+
"""
|
184
|
+
self._routes.append(route)
|
185
|
+
# Sort by priority (lower number = higher priority)
|
186
|
+
self._routes.sort(key=lambda r: r.priority)
|
187
|
+
logger.debug(f"Added routing rule: {route.rule_type.value}")
|
188
|
+
|
189
|
+
def remove_route(self, route: EventRoute) -> None:
|
190
|
+
"""Remove a routing rule.
|
191
|
+
|
192
|
+
Args:
|
193
|
+
route: Routing rule to remove
|
194
|
+
"""
|
195
|
+
if route in self._routes:
|
196
|
+
self._routes.remove(route)
|
197
|
+
logger.debug(f"Removed routing rule: {route.rule_type.value}")
|
198
|
+
|
199
|
+
async def route_event(self, event: ChannelEvent) -> None:
|
200
|
+
"""Route an event to appropriate channels.
|
201
|
+
|
202
|
+
Args:
|
203
|
+
event: Event to route
|
204
|
+
"""
|
205
|
+
if not self._running or not self._event_queue:
|
206
|
+
logger.warning("Event router not running, dropping event")
|
207
|
+
return
|
208
|
+
|
209
|
+
try:
|
210
|
+
await self._event_queue.put(event)
|
211
|
+
self._stats.total_events += 1
|
212
|
+
except asyncio.QueueFull:
|
213
|
+
logger.warning("Event queue full, dropping event")
|
214
|
+
self._stats.dropped_events += 1
|
215
|
+
|
216
|
+
async def _routing_loop(self) -> None:
|
217
|
+
"""Main event routing loop."""
|
218
|
+
while self._running:
|
219
|
+
try:
|
220
|
+
if not self._event_queue:
|
221
|
+
break
|
222
|
+
|
223
|
+
# Get event with timeout
|
224
|
+
try:
|
225
|
+
event = await asyncio.wait_for(self._event_queue.get(), timeout=1.0)
|
226
|
+
except asyncio.TimeoutError:
|
227
|
+
continue
|
228
|
+
|
229
|
+
await self._process_event(event)
|
230
|
+
|
231
|
+
except asyncio.CancelledError:
|
232
|
+
break
|
233
|
+
except Exception as e:
|
234
|
+
logger.error(f"Error in routing loop: {e}")
|
235
|
+
|
236
|
+
async def _process_event(self, event: ChannelEvent) -> None:
|
237
|
+
"""Process a single event through routing rules.
|
238
|
+
|
239
|
+
Args:
|
240
|
+
event: Event to process
|
241
|
+
"""
|
242
|
+
try:
|
243
|
+
# Update stats
|
244
|
+
source_channel = event.channel_name
|
245
|
+
if source_channel in self._stats.channel_stats:
|
246
|
+
self._stats.channel_stats[source_channel]["events_sent"] += 1
|
247
|
+
|
248
|
+
# Find matching routes
|
249
|
+
matching_routes = self._find_matching_routes(event)
|
250
|
+
|
251
|
+
if not matching_routes:
|
252
|
+
logger.debug(f"No routes found for event {event.event_id}")
|
253
|
+
self._stats.dropped_events += 1
|
254
|
+
return
|
255
|
+
|
256
|
+
# Apply routes in priority order
|
257
|
+
for route in matching_routes:
|
258
|
+
try:
|
259
|
+
await self._apply_route(event, route)
|
260
|
+
self._stats.routed_events += 1
|
261
|
+
|
262
|
+
# Update route stats
|
263
|
+
route_key = f"{route.rule_type.value}_{id(route)}"
|
264
|
+
self._stats.routes_matched[route_key] = (
|
265
|
+
self._stats.routes_matched.get(route_key, 0) + 1
|
266
|
+
)
|
267
|
+
|
268
|
+
except Exception as e:
|
269
|
+
logger.error(f"Error applying route {route.rule_type.value}: {e}")
|
270
|
+
self._stats.failed_events += 1
|
271
|
+
|
272
|
+
except Exception as e:
|
273
|
+
logger.error(f"Error processing event {event.event_id}: {e}")
|
274
|
+
self._stats.failed_events += 1
|
275
|
+
|
276
|
+
def _find_matching_routes(self, event: ChannelEvent) -> List[EventRoute]:
|
277
|
+
"""Find routes that match the given event.
|
278
|
+
|
279
|
+
Args:
|
280
|
+
event: Event to match
|
281
|
+
|
282
|
+
Returns:
|
283
|
+
List of matching routes in priority order
|
284
|
+
"""
|
285
|
+
matching_routes = []
|
286
|
+
|
287
|
+
for route in self._routes:
|
288
|
+
if not route.enabled:
|
289
|
+
continue
|
290
|
+
|
291
|
+
# Check source pattern
|
292
|
+
if route.source_patterns and not self._match_patterns(
|
293
|
+
event.channel_name, route.source_patterns
|
294
|
+
):
|
295
|
+
continue
|
296
|
+
|
297
|
+
# Check event type pattern
|
298
|
+
if route.event_type_patterns and not self._match_patterns(
|
299
|
+
event.event_type, route.event_type_patterns
|
300
|
+
):
|
301
|
+
continue
|
302
|
+
|
303
|
+
# Check custom condition
|
304
|
+
if route.condition and not route.condition(event):
|
305
|
+
continue
|
306
|
+
|
307
|
+
matching_routes.append(route)
|
308
|
+
|
309
|
+
return matching_routes
|
310
|
+
|
311
|
+
def _match_patterns(self, value: str, patterns: List[str]) -> bool:
|
312
|
+
"""Check if value matches any of the patterns.
|
313
|
+
|
314
|
+
Args:
|
315
|
+
value: Value to match
|
316
|
+
patterns: List of patterns (supports wildcards)
|
317
|
+
|
318
|
+
Returns:
|
319
|
+
True if value matches any pattern
|
320
|
+
"""
|
321
|
+
for pattern in patterns:
|
322
|
+
if fnmatch.fnmatch(value, pattern):
|
323
|
+
return True
|
324
|
+
return False
|
325
|
+
|
326
|
+
async def _apply_route(self, event: ChannelEvent, route: EventRoute) -> None:
|
327
|
+
"""Apply a routing rule to an event.
|
328
|
+
|
329
|
+
Args:
|
330
|
+
event: Event to route
|
331
|
+
route: Route to apply
|
332
|
+
"""
|
333
|
+
# Transform event if needed
|
334
|
+
if route.transform:
|
335
|
+
event = route.transform(event)
|
336
|
+
|
337
|
+
# Determine target channels based on route type
|
338
|
+
if route.rule_type == RoutingRule.BROADCAST:
|
339
|
+
targets = [
|
340
|
+
name for name in self._channels.keys() if name != event.channel_name
|
341
|
+
]
|
342
|
+
elif route.rule_type == RoutingRule.UNICAST:
|
343
|
+
targets = route.target_channels[:1] if route.target_channels else []
|
344
|
+
elif route.rule_type == RoutingRule.MULTICAST:
|
345
|
+
targets = route.target_channels
|
346
|
+
elif route.rule_type == RoutingRule.SESSION:
|
347
|
+
targets = await self._find_session_targets(event, route)
|
348
|
+
elif route.rule_type == RoutingRule.PATTERN:
|
349
|
+
targets = self._find_pattern_targets(event, route)
|
350
|
+
else:
|
351
|
+
targets = []
|
352
|
+
|
353
|
+
# Send event to target channels
|
354
|
+
for target_name in targets:
|
355
|
+
if target_name in self._channels:
|
356
|
+
try:
|
357
|
+
target_channel = self._channels[target_name]
|
358
|
+
await target_channel.handle_event(event)
|
359
|
+
|
360
|
+
# Update target channel stats
|
361
|
+
if target_name in self._stats.channel_stats:
|
362
|
+
self._stats.channel_stats[target_name]["events_received"] += 1
|
363
|
+
|
364
|
+
except Exception as e:
|
365
|
+
logger.error(f"Error sending event to channel {target_name}: {e}")
|
366
|
+
if target_name in self._stats.channel_stats:
|
367
|
+
self._stats.channel_stats[target_name]["events_failed"] += 1
|
368
|
+
|
369
|
+
async def _find_session_targets(
|
370
|
+
self, event: ChannelEvent, route: EventRoute
|
371
|
+
) -> List[str]:
|
372
|
+
"""Find target channels based on session routing.
|
373
|
+
|
374
|
+
Args:
|
375
|
+
event: Event to route
|
376
|
+
route: Route configuration
|
377
|
+
|
378
|
+
Returns:
|
379
|
+
List of target channel names
|
380
|
+
"""
|
381
|
+
if not self.session_manager or not event.session_id:
|
382
|
+
return []
|
383
|
+
|
384
|
+
session = self.session_manager.get_session(event.session_id)
|
385
|
+
if not session:
|
386
|
+
return []
|
387
|
+
|
388
|
+
# Apply session filter if provided
|
389
|
+
if route.session_filter and not route.session_filter(session):
|
390
|
+
return []
|
391
|
+
|
392
|
+
# Return channels active in the session (excluding source)
|
393
|
+
return [
|
394
|
+
ch
|
395
|
+
for ch in session.active_channels
|
396
|
+
if ch != event.channel_name and ch in self._channels
|
397
|
+
]
|
398
|
+
|
399
|
+
def _find_pattern_targets(
|
400
|
+
self, event: ChannelEvent, route: EventRoute
|
401
|
+
) -> List[str]:
|
402
|
+
"""Find target channels based on pattern matching.
|
403
|
+
|
404
|
+
Args:
|
405
|
+
event: Event to route
|
406
|
+
route: Route configuration
|
407
|
+
|
408
|
+
Returns:
|
409
|
+
List of target channel names
|
410
|
+
"""
|
411
|
+
targets = []
|
412
|
+
|
413
|
+
for channel_name in self._channels.keys():
|
414
|
+
if channel_name == event.channel_name:
|
415
|
+
continue # Don't send back to source
|
416
|
+
|
417
|
+
# Check if channel matches target patterns
|
418
|
+
if route.target_channels:
|
419
|
+
if any(
|
420
|
+
self._match_patterns(channel_name, [pattern])
|
421
|
+
for pattern in route.target_channels
|
422
|
+
):
|
423
|
+
targets.append(channel_name)
|
424
|
+
|
425
|
+
return targets
|
426
|
+
|
427
|
+
def get_stats(self) -> Dict[str, Any]:
|
428
|
+
"""Get event routing statistics.
|
429
|
+
|
430
|
+
Returns:
|
431
|
+
Dictionary with routing statistics
|
432
|
+
"""
|
433
|
+
return {
|
434
|
+
"total_events": self._stats.total_events,
|
435
|
+
"routed_events": self._stats.routed_events,
|
436
|
+
"dropped_events": self._stats.dropped_events,
|
437
|
+
"failed_events": self._stats.failed_events,
|
438
|
+
"success_rate": (
|
439
|
+
self._stats.routed_events / max(1, self._stats.total_events)
|
440
|
+
)
|
441
|
+
* 100,
|
442
|
+
"routes_count": len(self._routes),
|
443
|
+
"channels_count": len(self._channels),
|
444
|
+
"queue_size": self._event_queue.qsize() if self._event_queue else 0,
|
445
|
+
"route_matches": dict(self._stats.routes_matched),
|
446
|
+
"channel_stats": dict(self._stats.channel_stats),
|
447
|
+
"channels_registered": list(self._channels.keys()),
|
448
|
+
}
|
449
|
+
|
450
|
+
def reset_stats(self) -> None:
|
451
|
+
"""Reset routing statistics."""
|
452
|
+
self._stats = RoutingStats()
|
453
|
+
logger.info("Event router statistics reset")
|
454
|
+
|
455
|
+
async def health_check(self) -> Dict[str, Any]:
|
456
|
+
"""Perform health check on the event router.
|
457
|
+
|
458
|
+
Returns:
|
459
|
+
Health check results
|
460
|
+
"""
|
461
|
+
try:
|
462
|
+
is_healthy = (
|
463
|
+
self._running
|
464
|
+
and self._event_queue is not None
|
465
|
+
and self._router_task is not None
|
466
|
+
and not self._router_task.done()
|
467
|
+
)
|
468
|
+
|
469
|
+
queue_health = True
|
470
|
+
if self._event_queue:
|
471
|
+
queue_size = self._event_queue.qsize()
|
472
|
+
queue_health = queue_size < 8000 # Warning if queue getting full
|
473
|
+
|
474
|
+
return {
|
475
|
+
"healthy": is_healthy and queue_health,
|
476
|
+
"running": self._running,
|
477
|
+
"queue_size": self._event_queue.qsize() if self._event_queue else 0,
|
478
|
+
"channels_registered": len(self._channels),
|
479
|
+
"routes_configured": len(self._routes),
|
480
|
+
"total_events_processed": self._stats.total_events,
|
481
|
+
"success_rate": (
|
482
|
+
self._stats.routed_events / max(1, self._stats.total_events)
|
483
|
+
)
|
484
|
+
* 100,
|
485
|
+
"checks": {
|
486
|
+
"router_running": self._running,
|
487
|
+
"queue_available": self._event_queue is not None,
|
488
|
+
"task_active": self._router_task is not None
|
489
|
+
and not self._router_task.done(),
|
490
|
+
"queue_healthy": queue_health,
|
491
|
+
"channels_available": len(self._channels) > 0,
|
492
|
+
},
|
493
|
+
}
|
494
|
+
|
495
|
+
except Exception as e:
|
496
|
+
return {"healthy": False, "error": str(e), "checks": {}}
|