File size: 15,827 Bytes
90c6b42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
"""
ATOM Communication Memory Production Monitoring System
Real-time monitoring, alerting, and performance tracking
"""

import asyncio
from dataclasses import asdict, dataclass
from datetime import datetime, timedelta
import json
import logging
import time
from typing import Any, Dict, List, Optional

from integrations.atom_communication_ingestion_pipeline import ingestion_pipeline, memory_manager

logger = logging.getLogger(__name__)

@dataclass
class MonitoringMetric:
    """Monitoring metric data structure"""
    name: str
    value: float
    unit: str
    timestamp: datetime
    tags: Dict[str, str]
    threshold: Optional[float] = None

@dataclass
class Alert:
    """Alert data structure"""
    id: str
    severity: str  # info, warning, error, critical
    title: str
    message: str
    timestamp: datetime
    resolved: bool = False
    resolved_at: Optional[datetime] = None
    tags: Dict[str, str]

class AtomCommunicationMemoryMonitoring:
    """Production monitoring system for ATOM communication memory"""
    
    def __init__(self):
        self.metrics: List[MonitoringMetric] = []
        self.alerts: List[Alert] = []
        self.is_running = False
        self.monitoring_interval = 60  # seconds
        self.alert_thresholds = {
            'ingestion_rate': 0.1,  # messages per second
            'error_rate': 0.05,  # 5% error rate
            'memory_usage': 0.8,  # 80% memory usage
            'search_latency': 1.0,  # 1 second
            'database_size': 100_000_000_000  # 100GB
        }
    
    async def start_monitoring(self):
        """Start the monitoring system"""
        self.is_running = True
        logger.info("Starting ATOM communication memory monitoring")
        
        while self.is_running:
            try:
                await self.collect_metrics()
                await self.check_alerts()
                await asyncio.sleep(self.monitoring_interval)
            except Exception as e:
                logger.error(f"Error in monitoring loop: {str(e)}")
                await asyncio.sleep(60)  # Wait longer on error
    
    def stop_monitoring(self):
        """Stop the monitoring system"""
        self.is_running = False
        logger.info("Stopping ATOM communication memory monitoring")
    
    async def collect_metrics(self):
        """Collect monitoring metrics"""
        try:
            timestamp = datetime.now()
            
            # Get ingestion stats
            stats = ingestion_pipeline.get_ingestion_stats()
            
            # Database metrics
            db_metrics = await self._collect_database_metrics(timestamp)
            
            # Ingestion metrics
            ingestion_metrics = await self._collect_ingestion_metrics(stats, timestamp)
            
            # Performance metrics
            performance_metrics = await self._collect_performance_metrics(timestamp)
            
            # Add all metrics
            self.metrics.extend(db_metrics + ingestion_metrics + performance_metrics)
            
            # Keep only last 24 hours of metrics
            cutoff_time = timestamp - timedelta(hours=24)
            self.metrics = [m for m in self.metrics if m.timestamp > cutoff_time]
            
            logger.info(f"Collected {len(db_metrics + ingestion_metrics + performance_metrics)} metrics")
            
        except Exception as e:
            logger.error(f"Error collecting metrics: {str(e)}")
    
    async def _collect_database_metrics(self, timestamp: datetime) -> List[MonitoringMetric]:
        """Collect database-related metrics"""
        metrics = []
        
        try:
            if memory_manager.connections_table:
                # Get record count
                df = memory_manager.connections_table.to_pandas()
                record_count = len(df)
                
                metrics.append(MonitoringMetric(
                    name="database_record_count",
                    value=record_count,
                    unit="records",
                    timestamp=timestamp,
                    tags={"table": "atom_communications"},
                    threshold=self.alert_thresholds['database_size']
                ))
                
                # Get database size (estimated)
                estimated_size = record_count * 1024  # Estimate 1KB per record
                metrics.append(MonitoringMetric(
                    name="database_size",
                    value=estimated_size,
                    unit="bytes",
                    timestamp=timestamp,
                    tags={"table": "atom_communications"},
                    threshold=self.alert_thresholds['database_size']
                ))
                
                # App distribution
                app_dist = df["app_type"].value_counts().to_dict()
                for app, count in app_dist.items():
                    metrics.append(MonitoringMetric(
                        name=f"records_{app}",
                        value=count,
                        unit="records",
                        timestamp=timestamp,
                        tags={"app": app, "metric": "record_count"}
                    ))
        
        except Exception as e:
            logger.error(f"Error collecting database metrics: {str(e)}")
        
        return metrics
    
    async def _collect_ingestion_metrics(self, stats: Dict[str, Any], timestamp: datetime) -> List[MonitoringMetric]:
        """Collect ingestion-related metrics"""
        metrics = []
        
        try:
            # Total messages
            total_messages = stats.get('total_messages', 0)
            metrics.append(MonitoringMetric(
                name="total_messages_ingested",
                value=total_messages,
                unit="messages",
                timestamp=timestamp,
                tags={"metric": "total_ingestion"}
            ))
            
            # Active streams
            active_streams = len(stats.get('active_streams', []))
            metrics.append(MonitoringMetric(
                name="active_real_time_streams",
                value=active_streams,
                unit="streams",
                timestamp=timestamp,
                tags={"metric": "active_streams"}
            ))
            
            # Configured apps
            configured_apps = len(stats.get('configured_apps', []))
            metrics.append(MonitoringMetric(
                name="configured_apps",
                value=configured_apps,
                unit="apps",
                timestamp=timestamp,
                tags={"metric": "configured_apps"}
            ))
            
        except Exception as e:
            logger.error(f"Error collecting ingestion metrics: {str(e)}")
        
        return metrics
    
    async def _collect_performance_metrics(self, timestamp: datetime) -> List[MonitoringMetric]:
        """Collect performance-related metrics"""
        metrics = []
        
        try:
            # Ingestion rate (simplified)
            recent_metrics = [m for m in self.metrics 
                             if m.name == "total_messages_ingested" 
                             and (timestamp - m.timestamp).total_seconds() < 300]  # Last 5 minutes
            
            if len(recent_metrics) >= 2:
                recent_metrics.sort(key=lambda x: x.timestamp)
                latest_count = recent_metrics[-1].value
                earliest_count = recent_metrics[0].value
                time_diff = (recent_metrics[-1].timestamp - recent_metrics[0].timestamp).total_seconds()
                
                if time_diff > 0:
                    ingestion_rate = (latest_count - earliest_count) / time_diff
                    metrics.append(MonitoringMetric(
                        name="ingestion_rate",
                        value=ingestion_rate,
                        unit="messages/second",
                        timestamp=timestamp,
                        tags={"metric": "performance"},
                        threshold=self.alert_thresholds['ingestion_rate']
                    ))
            
            # Memory usage (simplified - would need actual monitoring)
            import psutil
            memory_percent = psutil.virtual_memory().percent / 100
            metrics.append(MonitoringMetric(
                name="memory_usage",
                value=memory_percent,
                unit="fraction",
                timestamp=timestamp,
                tags={"metric": "performance"},
                threshold=self.alert_thresholds['memory_usage']
            ))
            
        except Exception as e:
            logger.error(f"Error collecting performance metrics: {str(e)}")
        
        return metrics
    
    async def check_alerts(self):
        """Check thresholds and generate alerts"""
        try:
            timestamp = datetime.now()
            
            # Get latest metrics for each metric name
            latest_metrics = {}
            for metric in self.metrics:
                if metric.name not in latest_metrics or metric.timestamp > latest_metrics[metric.name].timestamp:
                    latest_metrics[metric.name] = metric
            
            # Check thresholds
            for metric_name, metric in latest_metrics.items():
                if metric.threshold and metric.value > metric.threshold:
                    await self._create_alert(
                        severity="warning",
                        title=f"Threshold exceeded for {metric_name}",
                        message=f"{metric_name}: {metric.value:.2f} {metric.unit} (threshold: {metric.threshold})",
                        timestamp=timestamp,
                        tags=metric.tags
                    )
            
            # Check for system health
            if not memory_manager.db:
                await self._create_alert(
                    severity="critical",
                    title="Database connection lost",
                    message="LanceDB database connection is not available",
                    timestamp=timestamp,
                    tags={"component": "database"}
                )
            
        except Exception as e:
            logger.error(f"Error checking alerts: {str(e)}")
    
    async def _create_alert(self, severity: str, title: str, message: str, 
                           timestamp: datetime, tags: Dict[str, str]):
        """Create a new alert"""
        alert_id = f"alert_{int(timestamp.timestamp())}_{len(self.alerts)}"
        
        # Check if similar alert already exists
        existing_alert = next((a for a in self.alerts if not a.resolved and a.title == title), None)
        
        if existing_alert:
            # Update existing alert
            existing_alert.timestamp = timestamp
            existing_alert.message = message
        else:
            # Create new alert
            alert = Alert(
                id=alert_id,
                severity=severity,
                title=title,
                message=message,
                timestamp=timestamp,
                tags=tags
            )
            
            self.alerts.append(alert)
            logger.warning(f"Alert created: {severity} - {title}")
    
    def get_metrics_summary(self, time_window: int = 3600) -> Dict[str, Any]:
        """Get summary of metrics for the last N seconds"""
        try:
            cutoff_time = datetime.now() - timedelta(seconds=time_window)
            recent_metrics = [m for m in self.metrics if m.timestamp > cutoff_time]
            
            # Group metrics by name
            metrics_by_name = {}
            for metric in recent_metrics:
                if metric.name not in metrics_by_name:
                    metrics_by_name[metric.name] = []
                metrics_by_name[metric.name].append(metric)
            
            # Calculate summaries
            summary = {
                "time_window": time_window,
                "metric_count": len(recent_metrics),
                "metrics": {}
            }
            
            for name, metric_list in metrics_by_name.items():
                values = [m.value for m in metric_list]
                summary["metrics"][name] = {
                    "latest": values[-1] if values else None,
                    "average": sum(values) / len(values) if values else None,
                    "min": min(values) if values else None,
                    "max": max(values) if values else None,
                    "count": len(values),
                    "unit": metric_list[0].unit if metric_list else None
                }
            
            return summary
            
        except Exception as e:
            logger.error(f"Error getting metrics summary: {str(e)}")
            return {"error": str(e)}
    
    def get_alerts_summary(self, include_resolved: bool = False) -> Dict[str, Any]:
        """Get summary of alerts"""
        try:
            alerts = self.alerts if include_resolved else [a for a in self.alerts if not a.resolved]
            
            # Count by severity
            severity_counts = {}
            for alert in alerts:
                severity_counts[alert.severity] = severity_counts.get(alert.severity, 0) + 1
            
            return {
                "total_alerts": len(alerts),
                "unresolved_alerts": len([a for a in alerts if not a.resolved]),
                "severity_distribution": severity_counts,
                "recent_alerts": [
                    {
                        "id": alert.id,
                        "severity": alert.severity,
                        "title": alert.title,
                        "message": alert.message,
                        "timestamp": alert.timestamp.isoformat(),
                        "resolved": alert.resolved
                    }
                    for alert in sorted(alerts, key=lambda x: x.timestamp, reverse=True)[:10]
                ]
            }
            
        except Exception as e:
            logger.error(f"Error getting alerts summary: {str(e)}")
            return {"error": str(e)}
    
    def get_health_status(self) -> Dict[str, Any]:
        """Get overall system health status"""
        try:
            # Check critical components
            health_checks = {
                "database": memory_manager.db is not None,
                "ingestion_pipeline": len(ingestion_pipeline.ingestion_configs) > 0,
                "monitoring": self.is_running
            }
            
            # Check recent errors
            recent_alerts = [a for a in self.alerts 
                             if not a.resolved 
                             and a.severity in ["error", "critical"]
                             and (datetime.now() - a.timestamp).total_seconds() < 3600]
            
            overall_status = "healthy"
            if not all(health_checks.values()):
                overall_status = "unhealthy"
            elif recent_alerts:
                overall_status = "degraded"
            
            return {
                "overall_status": overall_status,
                "timestamp": datetime.now().isoformat(),
                "health_checks": health_checks,
                "recent_critical_alerts": len(recent_alerts),
                "monitoring_active": self.is_running
            }
            
        except Exception as e:
            logger.error(f"Error getting health status: {str(e)}")
            return {"error": str(e), "overall_status": "unknown"}

# Create global monitoring instance
atom_memory_monitoring = AtomCommunicationMemoryMonitoring()

# Export for use
__all__ = [
    'AtomCommunicationMemoryMonitoring',
    'atom_memory_monitoring',
    'MonitoringMetric',
    'Alert'
]