kailash 0.8.3__py3-none-any.whl → 0.8.5__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 +1 -7
- kailash/cli/__init__.py +11 -1
- kailash/cli/validation_audit.py +570 -0
- kailash/core/actors/supervisor.py +1 -1
- kailash/core/resilience/circuit_breaker.py +71 -1
- kailash/core/resilience/health_monitor.py +172 -0
- kailash/edge/compliance.py +33 -0
- kailash/edge/consistency.py +609 -0
- kailash/edge/coordination/__init__.py +30 -0
- kailash/edge/coordination/global_ordering.py +355 -0
- kailash/edge/coordination/leader_election.py +217 -0
- kailash/edge/coordination/partition_detector.py +296 -0
- kailash/edge/coordination/raft.py +485 -0
- kailash/edge/discovery.py +63 -1
- kailash/edge/migration/__init__.py +19 -0
- kailash/edge/migration/edge_migrator.py +832 -0
- kailash/edge/monitoring/__init__.py +21 -0
- kailash/edge/monitoring/edge_monitor.py +736 -0
- kailash/edge/prediction/__init__.py +10 -0
- kailash/edge/prediction/predictive_warmer.py +591 -0
- kailash/edge/resource/__init__.py +102 -0
- kailash/edge/resource/cloud_integration.py +796 -0
- kailash/edge/resource/cost_optimizer.py +949 -0
- kailash/edge/resource/docker_integration.py +919 -0
- kailash/edge/resource/kubernetes_integration.py +893 -0
- kailash/edge/resource/platform_integration.py +913 -0
- kailash/edge/resource/predictive_scaler.py +959 -0
- kailash/edge/resource/resource_analyzer.py +824 -0
- kailash/edge/resource/resource_pools.py +610 -0
- kailash/integrations/dataflow_edge.py +261 -0
- kailash/mcp_server/registry_integration.py +1 -1
- kailash/monitoring/__init__.py +18 -0
- kailash/monitoring/alerts.py +646 -0
- kailash/monitoring/metrics.py +677 -0
- kailash/nodes/__init__.py +2 -0
- kailash/nodes/ai/__init__.py +17 -0
- kailash/nodes/ai/a2a.py +1914 -43
- kailash/nodes/ai/a2a_backup.py +1807 -0
- kailash/nodes/ai/hybrid_search.py +972 -0
- kailash/nodes/ai/semantic_memory.py +558 -0
- kailash/nodes/ai/streaming_analytics.py +947 -0
- kailash/nodes/base.py +545 -0
- kailash/nodes/edge/__init__.py +36 -0
- kailash/nodes/edge/base.py +240 -0
- kailash/nodes/edge/cloud_node.py +710 -0
- kailash/nodes/edge/coordination.py +239 -0
- kailash/nodes/edge/docker_node.py +825 -0
- kailash/nodes/edge/edge_data.py +582 -0
- kailash/nodes/edge/edge_migration_node.py +392 -0
- kailash/nodes/edge/edge_monitoring_node.py +421 -0
- kailash/nodes/edge/edge_state.py +673 -0
- kailash/nodes/edge/edge_warming_node.py +393 -0
- kailash/nodes/edge/kubernetes_node.py +652 -0
- kailash/nodes/edge/platform_node.py +766 -0
- kailash/nodes/edge/resource_analyzer_node.py +378 -0
- kailash/nodes/edge/resource_optimizer_node.py +501 -0
- kailash/nodes/edge/resource_scaler_node.py +397 -0
- kailash/nodes/ports.py +676 -0
- kailash/runtime/local.py +344 -1
- kailash/runtime/validation/__init__.py +20 -0
- kailash/runtime/validation/connection_context.py +119 -0
- kailash/runtime/validation/enhanced_error_formatter.py +202 -0
- kailash/runtime/validation/error_categorizer.py +164 -0
- kailash/runtime/validation/metrics.py +380 -0
- kailash/runtime/validation/performance.py +615 -0
- kailash/runtime/validation/suggestion_engine.py +212 -0
- kailash/testing/fixtures.py +2 -2
- kailash/workflow/builder.py +234 -8
- kailash/workflow/contracts.py +418 -0
- kailash/workflow/edge_infrastructure.py +369 -0
- kailash/workflow/migration.py +3 -3
- kailash/workflow/type_inference.py +669 -0
- {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/METADATA +44 -27
- {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/RECORD +78 -28
- kailash/nexus/__init__.py +0 -21
- kailash/nexus/cli/__init__.py +0 -5
- kailash/nexus/cli/__main__.py +0 -6
- kailash/nexus/cli/main.py +0 -176
- kailash/nexus/factory.py +0 -413
- kailash/nexus/gateway.py +0 -545
- {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/WHEEL +0 -0
- {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/entry_points.txt +0 -0
- {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,591 @@
|
|
1
|
+
"""Predictive edge warming service for anticipating and pre-warming edge nodes.
|
2
|
+
|
3
|
+
This service analyzes usage patterns, predicts future needs, and pre-warms
|
4
|
+
edge nodes to reduce cold start latency.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import asyncio
|
8
|
+
import json
|
9
|
+
import time
|
10
|
+
from collections import defaultdict, deque
|
11
|
+
from dataclasses import dataclass
|
12
|
+
from datetime import datetime, timedelta
|
13
|
+
from enum import Enum
|
14
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
15
|
+
|
16
|
+
import numpy as np
|
17
|
+
from sklearn.linear_model import LinearRegression
|
18
|
+
from sklearn.preprocessing import StandardScaler
|
19
|
+
|
20
|
+
|
21
|
+
class PredictionStrategy(Enum):
|
22
|
+
"""Prediction strategies for edge warming."""
|
23
|
+
|
24
|
+
TIME_SERIES = "time_series" # Historical time-based patterns
|
25
|
+
GEOGRAPHIC = "geographic" # Location-based predictions
|
26
|
+
USER_BEHAVIOR = "user_behavior" # User-specific patterns
|
27
|
+
WORKLOAD = "workload" # Workload type patterns
|
28
|
+
HYBRID = "hybrid" # Combination of strategies
|
29
|
+
|
30
|
+
|
31
|
+
@dataclass
|
32
|
+
class UsagePattern:
|
33
|
+
"""Represents a usage pattern for prediction."""
|
34
|
+
|
35
|
+
timestamp: datetime
|
36
|
+
edge_node: str
|
37
|
+
user_id: Optional[str]
|
38
|
+
location: Optional[Tuple[float, float]] # lat, lon
|
39
|
+
workload_type: str
|
40
|
+
response_time: float
|
41
|
+
resource_usage: Dict[str, float] # cpu, memory, etc.
|
42
|
+
|
43
|
+
def to_features(self) -> List[float]:
|
44
|
+
"""Convert pattern to feature vector for ML."""
|
45
|
+
features = [
|
46
|
+
self.timestamp.hour,
|
47
|
+
self.timestamp.weekday(),
|
48
|
+
self.response_time,
|
49
|
+
self.resource_usage.get("cpu", 0),
|
50
|
+
self.resource_usage.get("memory", 0),
|
51
|
+
]
|
52
|
+
|
53
|
+
if self.location:
|
54
|
+
features.extend(self.location)
|
55
|
+
|
56
|
+
return features
|
57
|
+
|
58
|
+
|
59
|
+
@dataclass
|
60
|
+
class WarmingDecision:
|
61
|
+
"""Represents a decision to warm an edge node."""
|
62
|
+
|
63
|
+
edge_node: str
|
64
|
+
confidence: float
|
65
|
+
predicted_time: datetime
|
66
|
+
resources_needed: Dict[str, float]
|
67
|
+
strategy_used: PredictionStrategy
|
68
|
+
reasoning: str
|
69
|
+
|
70
|
+
|
71
|
+
class PredictiveWarmer:
|
72
|
+
"""Predictive edge warming service.
|
73
|
+
|
74
|
+
Analyzes usage patterns and pre-warms edge nodes to reduce latency.
|
75
|
+
"""
|
76
|
+
|
77
|
+
def __init__(
|
78
|
+
self,
|
79
|
+
history_window: int = 7 * 24 * 60 * 60, # 7 days in seconds
|
80
|
+
prediction_horizon: int = 300, # 5 minutes ahead
|
81
|
+
confidence_threshold: float = 0.7,
|
82
|
+
max_prewarmed_nodes: int = 10,
|
83
|
+
):
|
84
|
+
"""Initialize predictive warmer.
|
85
|
+
|
86
|
+
Args:
|
87
|
+
history_window: Time window for historical analysis
|
88
|
+
prediction_horizon: How far ahead to predict (seconds)
|
89
|
+
confidence_threshold: Minimum confidence for warming
|
90
|
+
max_prewarmed_nodes: Maximum nodes to keep warm
|
91
|
+
"""
|
92
|
+
self.history_window = history_window
|
93
|
+
self.prediction_horizon = prediction_horizon
|
94
|
+
self.confidence_threshold = confidence_threshold
|
95
|
+
self.max_prewarmed_nodes = max_prewarmed_nodes
|
96
|
+
|
97
|
+
# Pattern storage
|
98
|
+
self.usage_history: deque = deque(maxlen=10000)
|
99
|
+
self.pattern_cache: Dict[str, List[UsagePattern]] = defaultdict(list)
|
100
|
+
|
101
|
+
# ML models for prediction
|
102
|
+
self.time_series_model = LinearRegression()
|
103
|
+
self.scaler = StandardScaler()
|
104
|
+
self.model_trained = False
|
105
|
+
|
106
|
+
# Current state
|
107
|
+
self.warmed_nodes: Set[str] = set()
|
108
|
+
self.warming_decisions: List[WarmingDecision] = []
|
109
|
+
|
110
|
+
# Metrics
|
111
|
+
self.predictions_made = 0
|
112
|
+
self.successful_predictions = 0
|
113
|
+
self.false_positives = 0
|
114
|
+
self.missed_predictions = 0
|
115
|
+
|
116
|
+
self._running = False
|
117
|
+
self._prediction_task = None
|
118
|
+
|
119
|
+
async def start(self):
|
120
|
+
"""Start the predictive warming service."""
|
121
|
+
self._running = True
|
122
|
+
self._prediction_task = asyncio.create_task(self._prediction_loop())
|
123
|
+
|
124
|
+
async def stop(self):
|
125
|
+
"""Stop the predictive warming service."""
|
126
|
+
self._running = False
|
127
|
+
if self._prediction_task:
|
128
|
+
self._prediction_task.cancel()
|
129
|
+
try:
|
130
|
+
await self._prediction_task
|
131
|
+
except asyncio.CancelledError:
|
132
|
+
pass
|
133
|
+
|
134
|
+
async def record_usage(self, pattern: UsagePattern):
|
135
|
+
"""Record a usage pattern for analysis.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
pattern: Usage pattern to record
|
139
|
+
"""
|
140
|
+
self.usage_history.append(pattern)
|
141
|
+
|
142
|
+
# Cache by different dimensions
|
143
|
+
self.pattern_cache[pattern.edge_node].append(pattern)
|
144
|
+
if pattern.user_id:
|
145
|
+
self.pattern_cache[f"user_{pattern.user_id}"].append(pattern)
|
146
|
+
self.pattern_cache[pattern.workload_type].append(pattern)
|
147
|
+
|
148
|
+
# Retrain model periodically
|
149
|
+
if len(self.usage_history) % 100 == 0:
|
150
|
+
await self._train_models()
|
151
|
+
|
152
|
+
async def predict_warming_needs(
|
153
|
+
self, strategy: PredictionStrategy = PredictionStrategy.HYBRID
|
154
|
+
) -> List[WarmingDecision]:
|
155
|
+
"""Predict which edge nodes need warming.
|
156
|
+
|
157
|
+
Args:
|
158
|
+
strategy: Prediction strategy to use
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
List of warming decisions
|
162
|
+
"""
|
163
|
+
decisions = []
|
164
|
+
|
165
|
+
if strategy == PredictionStrategy.TIME_SERIES:
|
166
|
+
decisions.extend(await self._predict_time_series())
|
167
|
+
elif strategy == PredictionStrategy.GEOGRAPHIC:
|
168
|
+
decisions.extend(await self._predict_geographic())
|
169
|
+
elif strategy == PredictionStrategy.USER_BEHAVIOR:
|
170
|
+
decisions.extend(await self._predict_user_behavior())
|
171
|
+
elif strategy == PredictionStrategy.WORKLOAD:
|
172
|
+
decisions.extend(await self._predict_workload())
|
173
|
+
elif strategy == PredictionStrategy.HYBRID:
|
174
|
+
# Combine all strategies
|
175
|
+
all_decisions = []
|
176
|
+
all_decisions.extend(await self._predict_time_series())
|
177
|
+
all_decisions.extend(await self._predict_geographic())
|
178
|
+
all_decisions.extend(await self._predict_user_behavior())
|
179
|
+
all_decisions.extend(await self._predict_workload())
|
180
|
+
|
181
|
+
# Aggregate and rank decisions
|
182
|
+
decisions = self._aggregate_decisions(all_decisions)
|
183
|
+
|
184
|
+
# Filter by confidence and limit
|
185
|
+
decisions = [d for d in decisions if d.confidence >= self.confidence_threshold]
|
186
|
+
decisions.sort(key=lambda d: d.confidence, reverse=True)
|
187
|
+
|
188
|
+
return decisions[: self.max_prewarmed_nodes]
|
189
|
+
|
190
|
+
async def _prediction_loop(self):
|
191
|
+
"""Main prediction loop."""
|
192
|
+
while self._running:
|
193
|
+
try:
|
194
|
+
# Make predictions
|
195
|
+
decisions = await self.predict_warming_needs()
|
196
|
+
self.warming_decisions = decisions
|
197
|
+
|
198
|
+
# Execute warming decisions
|
199
|
+
for decision in decisions:
|
200
|
+
await self._execute_warming(decision)
|
201
|
+
|
202
|
+
# Wait before next prediction
|
203
|
+
await asyncio.sleep(0.1) # Fast prediction for tests
|
204
|
+
|
205
|
+
except Exception as e:
|
206
|
+
print(f"Prediction error: {e}")
|
207
|
+
await asyncio.sleep(0.1) # Fast retry for tests
|
208
|
+
|
209
|
+
async def _train_models(self):
|
210
|
+
"""Train ML models on historical data."""
|
211
|
+
if len(self.usage_history) < 100:
|
212
|
+
return # Not enough data
|
213
|
+
|
214
|
+
# Prepare training data
|
215
|
+
X = []
|
216
|
+
y = []
|
217
|
+
|
218
|
+
for i in range(len(self.usage_history) - 1):
|
219
|
+
pattern = self.usage_history[i]
|
220
|
+
next_pattern = self.usage_history[i + 1]
|
221
|
+
|
222
|
+
# Features from current pattern
|
223
|
+
features = pattern.to_features()
|
224
|
+
X.append(features)
|
225
|
+
|
226
|
+
# Target: will this edge be used in next time window?
|
227
|
+
time_diff = (next_pattern.timestamp - pattern.timestamp).total_seconds()
|
228
|
+
y.append(1 if time_diff < self.prediction_horizon else 0)
|
229
|
+
|
230
|
+
if X and y:
|
231
|
+
# Normalize features
|
232
|
+
X_scaled = self.scaler.fit_transform(X)
|
233
|
+
|
234
|
+
# Train model
|
235
|
+
self.time_series_model.fit(X_scaled, y)
|
236
|
+
self.model_trained = True
|
237
|
+
|
238
|
+
async def _predict_time_series(self) -> List[WarmingDecision]:
|
239
|
+
"""Predict based on time series patterns."""
|
240
|
+
decisions = []
|
241
|
+
|
242
|
+
if not self.model_trained or len(self.usage_history) < 10:
|
243
|
+
return decisions
|
244
|
+
|
245
|
+
# Analyze patterns for each edge node
|
246
|
+
edge_patterns = defaultdict(list)
|
247
|
+
for pattern in self.usage_history:
|
248
|
+
edge_patterns[pattern.edge_node].append(pattern)
|
249
|
+
|
250
|
+
current_time = datetime.now()
|
251
|
+
|
252
|
+
for edge_node, patterns in edge_patterns.items():
|
253
|
+
if len(patterns) < 5:
|
254
|
+
continue
|
255
|
+
|
256
|
+
# Extract time-based features
|
257
|
+
hourly_usage = defaultdict(int)
|
258
|
+
daily_usage = defaultdict(int)
|
259
|
+
|
260
|
+
for pattern in patterns[-100:]: # Last 100 patterns
|
261
|
+
hourly_usage[pattern.timestamp.hour] += 1
|
262
|
+
daily_usage[pattern.timestamp.weekday()] += 1
|
263
|
+
|
264
|
+
# Predict if node will be needed
|
265
|
+
current_hour = current_time.hour
|
266
|
+
current_day = current_time.weekday()
|
267
|
+
|
268
|
+
# Simple heuristic: high usage in current hour/day
|
269
|
+
hour_score = (
|
270
|
+
hourly_usage[current_hour] / max(hourly_usage.values())
|
271
|
+
if hourly_usage
|
272
|
+
else 0
|
273
|
+
)
|
274
|
+
day_score = (
|
275
|
+
daily_usage[current_day] / max(daily_usage.values())
|
276
|
+
if daily_usage
|
277
|
+
else 0
|
278
|
+
)
|
279
|
+
|
280
|
+
confidence = (hour_score + day_score) / 2
|
281
|
+
|
282
|
+
if confidence > self.confidence_threshold:
|
283
|
+
decisions.append(
|
284
|
+
WarmingDecision(
|
285
|
+
edge_node=edge_node,
|
286
|
+
confidence=confidence,
|
287
|
+
predicted_time=current_time
|
288
|
+
+ timedelta(seconds=self.prediction_horizon),
|
289
|
+
resources_needed=self._estimate_resources(patterns),
|
290
|
+
strategy_used=PredictionStrategy.TIME_SERIES,
|
291
|
+
reasoning=f"High usage at hour {current_hour} (score: {hour_score:.2f})",
|
292
|
+
)
|
293
|
+
)
|
294
|
+
|
295
|
+
return decisions
|
296
|
+
|
297
|
+
async def _predict_geographic(self) -> List[WarmingDecision]:
|
298
|
+
"""Predict based on geographic patterns."""
|
299
|
+
decisions = []
|
300
|
+
|
301
|
+
# Group patterns by location proximity
|
302
|
+
location_patterns = defaultdict(list)
|
303
|
+
|
304
|
+
for pattern in self.usage_history:
|
305
|
+
if pattern.location:
|
306
|
+
# Simple grid-based grouping
|
307
|
+
lat_grid = int(pattern.location[0] * 10) / 10
|
308
|
+
lon_grid = int(pattern.location[1] * 10) / 10
|
309
|
+
location_patterns[(lat_grid, lon_grid)].append(pattern)
|
310
|
+
|
311
|
+
# Find active locations
|
312
|
+
current_time = datetime.now()
|
313
|
+
active_locations = []
|
314
|
+
|
315
|
+
for location, patterns in location_patterns.items():
|
316
|
+
recent_patterns = [
|
317
|
+
p
|
318
|
+
for p in patterns
|
319
|
+
if (current_time - p.timestamp).total_seconds() < 3600 # Last hour
|
320
|
+
]
|
321
|
+
|
322
|
+
if len(recent_patterns) > 5:
|
323
|
+
active_locations.append(location)
|
324
|
+
|
325
|
+
# Predict edge nodes for active locations
|
326
|
+
for location in active_locations:
|
327
|
+
patterns = location_patterns[location]
|
328
|
+
|
329
|
+
# Find most used edge node for this location
|
330
|
+
edge_usage = defaultdict(int)
|
331
|
+
for pattern in patterns:
|
332
|
+
edge_usage[pattern.edge_node] += 1
|
333
|
+
|
334
|
+
if edge_usage:
|
335
|
+
best_edge = max(edge_usage, key=edge_usage.get)
|
336
|
+
confidence = edge_usage[best_edge] / len(patterns)
|
337
|
+
|
338
|
+
decisions.append(
|
339
|
+
WarmingDecision(
|
340
|
+
edge_node=best_edge,
|
341
|
+
confidence=confidence,
|
342
|
+
predicted_time=current_time
|
343
|
+
+ timedelta(seconds=self.prediction_horizon),
|
344
|
+
resources_needed=self._estimate_resources(patterns),
|
345
|
+
strategy_used=PredictionStrategy.GEOGRAPHIC,
|
346
|
+
reasoning=f"Active location {location} typically uses {best_edge}",
|
347
|
+
)
|
348
|
+
)
|
349
|
+
|
350
|
+
return decisions
|
351
|
+
|
352
|
+
async def _predict_user_behavior(self) -> List[WarmingDecision]:
|
353
|
+
"""Predict based on user behavior patterns."""
|
354
|
+
decisions = []
|
355
|
+
|
356
|
+
# Analyze per-user patterns
|
357
|
+
user_patterns = defaultdict(list)
|
358
|
+
|
359
|
+
for pattern in self.usage_history:
|
360
|
+
if pattern.user_id:
|
361
|
+
user_patterns[pattern.user_id].append(pattern)
|
362
|
+
|
363
|
+
current_time = datetime.now()
|
364
|
+
|
365
|
+
for user_id, patterns in user_patterns.items():
|
366
|
+
if len(patterns) < 10:
|
367
|
+
continue
|
368
|
+
|
369
|
+
# Find user's typical usage times
|
370
|
+
usage_times = [p.timestamp.hour for p in patterns]
|
371
|
+
avg_hour = sum(usage_times) / len(usage_times)
|
372
|
+
|
373
|
+
# Check if current time matches user's pattern
|
374
|
+
if abs(current_time.hour - avg_hour) < 2:
|
375
|
+
# Find user's preferred edge nodes
|
376
|
+
edge_usage = defaultdict(int)
|
377
|
+
for pattern in patterns:
|
378
|
+
edge_usage[pattern.edge_node] += 1
|
379
|
+
|
380
|
+
if edge_usage:
|
381
|
+
best_edge = max(edge_usage, key=edge_usage.get)
|
382
|
+
confidence = edge_usage[best_edge] / len(patterns)
|
383
|
+
|
384
|
+
decisions.append(
|
385
|
+
WarmingDecision(
|
386
|
+
edge_node=best_edge,
|
387
|
+
confidence=confidence
|
388
|
+
* 0.8, # Slightly lower confidence for user-based
|
389
|
+
predicted_time=current_time
|
390
|
+
+ timedelta(seconds=self.prediction_horizon),
|
391
|
+
resources_needed=self._estimate_resources(patterns),
|
392
|
+
strategy_used=PredictionStrategy.USER_BEHAVIOR,
|
393
|
+
reasoning=f"User {user_id} typically active at this time",
|
394
|
+
)
|
395
|
+
)
|
396
|
+
|
397
|
+
return decisions
|
398
|
+
|
399
|
+
async def _predict_workload(self) -> List[WarmingDecision]:
|
400
|
+
"""Predict based on workload patterns."""
|
401
|
+
decisions = []
|
402
|
+
|
403
|
+
# Analyze workload type patterns
|
404
|
+
workload_patterns = defaultdict(list)
|
405
|
+
|
406
|
+
for pattern in self.usage_history:
|
407
|
+
workload_patterns[pattern.workload_type].append(pattern)
|
408
|
+
|
409
|
+
current_time = datetime.now()
|
410
|
+
|
411
|
+
for workload_type, patterns in workload_patterns.items():
|
412
|
+
# Find recent surge in this workload type
|
413
|
+
recent_patterns = [
|
414
|
+
p
|
415
|
+
for p in patterns
|
416
|
+
if (current_time - p.timestamp).total_seconds() < 600 # Last 10 minutes
|
417
|
+
]
|
418
|
+
|
419
|
+
if len(recent_patterns) > 5:
|
420
|
+
# Increasing trend suggests more coming
|
421
|
+
edge_usage = defaultdict(int)
|
422
|
+
for pattern in recent_patterns:
|
423
|
+
edge_usage[pattern.edge_node] += 1
|
424
|
+
|
425
|
+
for edge_node, count in edge_usage.items():
|
426
|
+
confidence = count / len(recent_patterns)
|
427
|
+
|
428
|
+
decisions.append(
|
429
|
+
WarmingDecision(
|
430
|
+
edge_node=edge_node,
|
431
|
+
confidence=confidence,
|
432
|
+
predicted_time=current_time
|
433
|
+
+ timedelta(seconds=self.prediction_horizon),
|
434
|
+
resources_needed=self._estimate_resources(patterns),
|
435
|
+
strategy_used=PredictionStrategy.WORKLOAD,
|
436
|
+
reasoning=f"Surge in {workload_type} workload detected",
|
437
|
+
)
|
438
|
+
)
|
439
|
+
|
440
|
+
return decisions
|
441
|
+
|
442
|
+
def _aggregate_decisions(
|
443
|
+
self, decisions: List[WarmingDecision]
|
444
|
+
) -> List[WarmingDecision]:
|
445
|
+
"""Aggregate decisions from multiple strategies."""
|
446
|
+
# Group by edge node
|
447
|
+
node_decisions = defaultdict(list)
|
448
|
+
for decision in decisions:
|
449
|
+
node_decisions[decision.edge_node].append(decision)
|
450
|
+
|
451
|
+
# Combine confidences
|
452
|
+
aggregated = []
|
453
|
+
for edge_node, node_decisions_list in node_decisions.items():
|
454
|
+
# Weight different strategies
|
455
|
+
weights = {
|
456
|
+
PredictionStrategy.TIME_SERIES: 0.3,
|
457
|
+
PredictionStrategy.GEOGRAPHIC: 0.25,
|
458
|
+
PredictionStrategy.USER_BEHAVIOR: 0.25,
|
459
|
+
PredictionStrategy.WORKLOAD: 0.2,
|
460
|
+
}
|
461
|
+
|
462
|
+
total_confidence = 0
|
463
|
+
total_weight = 0
|
464
|
+
resources = {}
|
465
|
+
reasons = []
|
466
|
+
|
467
|
+
for decision in node_decisions_list:
|
468
|
+
weight = weights.get(decision.strategy_used, 0.25)
|
469
|
+
total_confidence += decision.confidence * weight
|
470
|
+
total_weight += weight
|
471
|
+
|
472
|
+
# Merge resource estimates
|
473
|
+
for resource, value in decision.resources_needed.items():
|
474
|
+
resources[resource] = max(resources.get(resource, 0), value)
|
475
|
+
|
476
|
+
reasons.append(f"{decision.strategy_used.value}: {decision.reasoning}")
|
477
|
+
|
478
|
+
if total_weight > 0:
|
479
|
+
aggregated.append(
|
480
|
+
WarmingDecision(
|
481
|
+
edge_node=edge_node,
|
482
|
+
confidence=total_confidence / total_weight,
|
483
|
+
predicted_time=datetime.now()
|
484
|
+
+ timedelta(seconds=self.prediction_horizon),
|
485
|
+
resources_needed=resources,
|
486
|
+
strategy_used=PredictionStrategy.HYBRID,
|
487
|
+
reasoning="; ".join(reasons),
|
488
|
+
)
|
489
|
+
)
|
490
|
+
|
491
|
+
return aggregated
|
492
|
+
|
493
|
+
def _estimate_resources(self, patterns: List[UsagePattern]) -> Dict[str, float]:
|
494
|
+
"""Estimate resource needs based on patterns."""
|
495
|
+
if not patterns:
|
496
|
+
return {"cpu": 0.1, "memory": 128}
|
497
|
+
|
498
|
+
# Average resource usage
|
499
|
+
cpu_usage = []
|
500
|
+
memory_usage = []
|
501
|
+
|
502
|
+
for pattern in patterns[-20:]: # Last 20 patterns
|
503
|
+
cpu_usage.append(pattern.resource_usage.get("cpu", 0))
|
504
|
+
memory_usage.append(pattern.resource_usage.get("memory", 0))
|
505
|
+
|
506
|
+
return {
|
507
|
+
"cpu": np.percentile(cpu_usage, 75) if cpu_usage else 0.1,
|
508
|
+
"memory": np.percentile(memory_usage, 75) if memory_usage else 128,
|
509
|
+
}
|
510
|
+
|
511
|
+
async def _execute_warming(self, decision: WarmingDecision):
|
512
|
+
"""Execute a warming decision."""
|
513
|
+
if decision.edge_node not in self.warmed_nodes:
|
514
|
+
# Simulate warming the edge node
|
515
|
+
print(
|
516
|
+
f"Warming edge node {decision.edge_node} "
|
517
|
+
f"(confidence: {decision.confidence:.2f}, "
|
518
|
+
f"reason: {decision.reasoning})"
|
519
|
+
)
|
520
|
+
|
521
|
+
self.warmed_nodes.add(decision.edge_node)
|
522
|
+
self.predictions_made += 1
|
523
|
+
|
524
|
+
# TODO: Actual edge node warming implementation
|
525
|
+
# This would involve:
|
526
|
+
# 1. Pre-allocating resources
|
527
|
+
# 2. Loading necessary data
|
528
|
+
# 3. Establishing connections
|
529
|
+
# 4. Running health checks
|
530
|
+
|
531
|
+
def evaluate_prediction(self, edge_node: str, was_used: bool):
|
532
|
+
"""Evaluate a prediction after the fact.
|
533
|
+
|
534
|
+
Args:
|
535
|
+
edge_node: Edge node that was predicted
|
536
|
+
was_used: Whether the node was actually used
|
537
|
+
"""
|
538
|
+
if edge_node in self.warmed_nodes:
|
539
|
+
if was_used:
|
540
|
+
self.successful_predictions += 1
|
541
|
+
else:
|
542
|
+
self.false_positives += 1
|
543
|
+
elif was_used:
|
544
|
+
self.missed_predictions += 1
|
545
|
+
|
546
|
+
def get_metrics(self) -> Dict[str, Any]:
|
547
|
+
"""Get prediction metrics."""
|
548
|
+
total_evaluated = (
|
549
|
+
self.successful_predictions + self.false_positives + self.missed_predictions
|
550
|
+
)
|
551
|
+
|
552
|
+
if total_evaluated == 0:
|
553
|
+
precision = recall = f1_score = 0
|
554
|
+
else:
|
555
|
+
precision = (
|
556
|
+
self.successful_predictions
|
557
|
+
/ (self.successful_predictions + self.false_positives)
|
558
|
+
if (self.successful_predictions + self.false_positives) > 0
|
559
|
+
else 0
|
560
|
+
)
|
561
|
+
recall = (
|
562
|
+
self.successful_predictions
|
563
|
+
/ (self.successful_predictions + self.missed_predictions)
|
564
|
+
if (self.successful_predictions + self.missed_predictions) > 0
|
565
|
+
else 0
|
566
|
+
)
|
567
|
+
f1_score = (
|
568
|
+
2 * precision * recall / (precision + recall)
|
569
|
+
if (precision + recall) > 0
|
570
|
+
else 0
|
571
|
+
)
|
572
|
+
|
573
|
+
return {
|
574
|
+
"predictions_made": self.predictions_made,
|
575
|
+
"successful_predictions": self.successful_predictions,
|
576
|
+
"false_positives": self.false_positives,
|
577
|
+
"missed_predictions": self.missed_predictions,
|
578
|
+
"precision": precision,
|
579
|
+
"recall": recall,
|
580
|
+
"f1_score": f1_score,
|
581
|
+
"warmed_nodes": list(self.warmed_nodes),
|
582
|
+
"current_decisions": [
|
583
|
+
{
|
584
|
+
"edge_node": d.edge_node,
|
585
|
+
"confidence": d.confidence,
|
586
|
+
"strategy": d.strategy_used.value,
|
587
|
+
"reasoning": d.reasoning,
|
588
|
+
}
|
589
|
+
for d in self.warming_decisions
|
590
|
+
],
|
591
|
+
}
|
@@ -0,0 +1,102 @@
|
|
1
|
+
"""Edge resource management components."""
|
2
|
+
|
3
|
+
from .cloud_integration import (
|
4
|
+
CloudInstance,
|
5
|
+
CloudIntegration,
|
6
|
+
CloudMetrics,
|
7
|
+
)
|
8
|
+
from .cloud_integration import CloudProvider as CloudProviderType
|
9
|
+
from .cloud_integration import (
|
10
|
+
InstanceSpec,
|
11
|
+
InstanceState,
|
12
|
+
)
|
13
|
+
from .cost_optimizer import (
|
14
|
+
CloudProvider,
|
15
|
+
CostMetric,
|
16
|
+
CostOptimization,
|
17
|
+
CostOptimizer,
|
18
|
+
InstanceType,
|
19
|
+
OptimizationStrategy,
|
20
|
+
)
|
21
|
+
from .docker_integration import (
|
22
|
+
ContainerMetrics,
|
23
|
+
ContainerSpec,
|
24
|
+
ContainerState,
|
25
|
+
DockerIntegration,
|
26
|
+
NetworkMode,
|
27
|
+
RestartPolicyType,
|
28
|
+
ServiceSpec,
|
29
|
+
)
|
30
|
+
|
31
|
+
# Phase 4.4 Integration & Testing components
|
32
|
+
from .kubernetes_integration import (
|
33
|
+
KubernetesIntegration,
|
34
|
+
KubernetesResource,
|
35
|
+
KubernetesResourceType,
|
36
|
+
PodScalingSpec,
|
37
|
+
ScalingPolicy,
|
38
|
+
)
|
39
|
+
from .platform_integration import (
|
40
|
+
PlatformConfig,
|
41
|
+
PlatformIntegration,
|
42
|
+
PlatformType,
|
43
|
+
ResourceAllocation,
|
44
|
+
)
|
45
|
+
from .platform_integration import ResourceRequest as PlatformResourceRequest
|
46
|
+
from .platform_integration import (
|
47
|
+
ResourceScope,
|
48
|
+
)
|
49
|
+
from .predictive_scaler import (
|
50
|
+
PredictionHorizon,
|
51
|
+
PredictiveScaler,
|
52
|
+
ScalingDecision,
|
53
|
+
ScalingPrediction,
|
54
|
+
ScalingStrategy,
|
55
|
+
)
|
56
|
+
from .resource_analyzer import ResourceAnalyzer, ResourceMetric, ResourceType
|
57
|
+
from .resource_pools import AllocationResult, ResourcePool, ResourceRequest
|
58
|
+
|
59
|
+
__all__ = [
|
60
|
+
"ResourceAnalyzer",
|
61
|
+
"ResourceMetric",
|
62
|
+
"ResourceType",
|
63
|
+
"ResourcePool",
|
64
|
+
"ResourceRequest",
|
65
|
+
"AllocationResult",
|
66
|
+
"PredictiveScaler",
|
67
|
+
"ScalingStrategy",
|
68
|
+
"PredictionHorizon",
|
69
|
+
"ScalingPrediction",
|
70
|
+
"ScalingDecision",
|
71
|
+
"CostOptimizer",
|
72
|
+
"CloudProvider",
|
73
|
+
"InstanceType",
|
74
|
+
"OptimizationStrategy",
|
75
|
+
"CostMetric",
|
76
|
+
"CostOptimization",
|
77
|
+
# Phase 4.4 components
|
78
|
+
"KubernetesIntegration",
|
79
|
+
"KubernetesResource",
|
80
|
+
"KubernetesResourceType",
|
81
|
+
"PodScalingSpec",
|
82
|
+
"ScalingPolicy",
|
83
|
+
"DockerIntegration",
|
84
|
+
"ContainerSpec",
|
85
|
+
"ServiceSpec",
|
86
|
+
"ContainerState",
|
87
|
+
"RestartPolicyType",
|
88
|
+
"NetworkMode",
|
89
|
+
"ContainerMetrics",
|
90
|
+
"CloudIntegration",
|
91
|
+
"CloudProviderType",
|
92
|
+
"InstanceSpec",
|
93
|
+
"InstanceState",
|
94
|
+
"CloudInstance",
|
95
|
+
"CloudMetrics",
|
96
|
+
"PlatformIntegration",
|
97
|
+
"PlatformType",
|
98
|
+
"ResourceScope",
|
99
|
+
"PlatformResourceRequest",
|
100
|
+
"ResourceAllocation",
|
101
|
+
"PlatformConfig",
|
102
|
+
]
|