performance_monitor.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. """
  2. Performance monitoring and optimization for Trixy ML Integration.
  3. This module provides comprehensive performance monitoring, metrics collection,
  4. resource optimization, and performance analytics for all ML components.
  5. """
  6. import asyncio
  7. import logging
  8. import time
  9. import threading
  10. from collections import defaultdict, deque
  11. from dataclasses import dataclass, field
  12. from datetime import datetime, timedelta
  13. from enum import Enum
  14. from pathlib import Path
  15. from typing import Any, Dict, List, Optional, Tuple, Callable, Union
  16. import json
  17. import psutil
  18. import numpy as np
  19. from concurrent.futures import ThreadPoolExecutor
  20. class MetricType(Enum):
  21. """Types of performance metrics."""
  22. LATENCY = "latency"
  23. THROUGHPUT = "throughput"
  24. CPU_USAGE = "cpu_usage"
  25. MEMORY_USAGE = "memory_usage"
  26. AUDIO_QUALITY = "audio_quality"
  27. MODEL_ACCURACY = "model_accuracy"
  28. ERROR_RATE = "error_rate"
  29. BUFFER_UTILIZATION = "buffer_utilization"
  30. NETWORK_LATENCY = "network_latency"
  31. PROCESSING_TIME = "processing_time"
  32. class AlertLevel(Enum):
  33. """Alert severity levels."""
  34. INFO = "info"
  35. WARNING = "warning"
  36. CRITICAL = "critical"
  37. EMERGENCY = "emergency"
  38. @dataclass
  39. class PerformanceMetric:
  40. """Individual performance metric data point."""
  41. timestamp: datetime
  42. component: str
  43. metric_type: MetricType
  44. value: Union[float, int]
  45. unit: str
  46. metadata: Dict[str, Any] = field(default_factory=dict)
  47. @dataclass
  48. class PerformanceThreshold:
  49. """Performance threshold configuration."""
  50. metric_type: MetricType
  51. warning_threshold: float
  52. critical_threshold: float
  53. unit: str
  54. check_interval: float = 1.0 # seconds
  55. min_samples: int = 5
  56. @dataclass
  57. class OptimizationRule:
  58. """Optimization rule for automatic performance tuning."""
  59. name: str
  60. condition: Callable[[Dict[str, Any]], bool]
  61. action: Callable[[Dict[str, Any]], None]
  62. cooldown: float = 60.0 # seconds
  63. description: str = ""
  64. @dataclass
  65. class PerformanceReport:
  66. """Comprehensive performance report."""
  67. timestamp: datetime
  68. duration: timedelta
  69. component_stats: Dict[str, Dict[str, Any]]
  70. system_stats: Dict[str, Any]
  71. alerts: List[Dict[str, Any]]
  72. recommendations: List[str]
  73. class MetricsCollector:
  74. """Collects and aggregates performance metrics."""
  75. def __init__(self, max_history: int = 10000):
  76. self.max_history = max_history
  77. self.metrics: Dict[str, deque] = defaultdict(lambda: deque(maxlen=max_history))
  78. self._lock = threading.RLock()
  79. self.logger = logging.getLogger(__name__)
  80. def record_metric(self, metric: PerformanceMetric) -> None:
  81. """Record a performance metric."""
  82. with self._lock:
  83. key = f"{metric.component}:{metric.metric_type.value}"
  84. self.metrics[key].append(metric)
  85. def get_metrics(self, component: str, metric_type: MetricType,
  86. since: Optional[datetime] = None,
  87. limit: Optional[int] = None) -> List[PerformanceMetric]:
  88. """Get metrics for a component and type."""
  89. with self._lock:
  90. key = f"{component}:{metric_type.value}"
  91. metrics = list(self.metrics[key])
  92. if since:
  93. metrics = [m for m in metrics if m.timestamp >= since]
  94. if limit:
  95. metrics = metrics[-limit:]
  96. return metrics
  97. def get_aggregated_stats(self, component: str, metric_type: MetricType,
  98. window: timedelta) -> Dict[str, float]:
  99. """Get aggregated statistics for a time window."""
  100. since = datetime.now() - window
  101. metrics = self.get_metrics(component, metric_type, since=since)
  102. if not metrics:
  103. return {}
  104. values = [m.value for m in metrics]
  105. return {
  106. 'count': len(values),
  107. 'mean': np.mean(values),
  108. 'median': np.median(values),
  109. 'std': np.std(values),
  110. 'min': np.min(values),
  111. 'max': np.max(values),
  112. 'p95': np.percentile(values, 95),
  113. 'p99': np.percentile(values, 99)
  114. }
  115. def clear_old_metrics(self, older_than: timedelta) -> int:
  116. """Clear metrics older than specified time."""
  117. cutoff = datetime.now() - older_than
  118. removed_count = 0
  119. with self._lock:
  120. for key in self.metrics:
  121. original_length = len(self.metrics[key])
  122. self.metrics[key] = deque(
  123. (m for m in self.metrics[key] if m.timestamp >= cutoff),
  124. maxlen=self.max_history
  125. )
  126. removed_count += original_length - len(self.metrics[key])
  127. return removed_count
  128. class SystemMonitor:
  129. """Monitors system-level performance metrics."""
  130. def __init__(self, process_name: str = "trixy"):
  131. self.process_name = process_name
  132. self.process = None
  133. self._find_process()
  134. self.logger = logging.getLogger(__name__)
  135. def _find_process(self) -> None:
  136. """Find the Trixy process."""
  137. try:
  138. for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
  139. if self.process_name.lower() in proc.info['name'].lower():
  140. self.process = psutil.Process(proc.info['pid'])
  141. break
  142. except Exception as e:
  143. self.logger.warning(f"Could not find process {self.process_name}: {e}")
  144. def get_cpu_usage(self) -> float:
  145. """Get current CPU usage percentage."""
  146. try:
  147. if self.process and self.process.is_running():
  148. return self.process.cpu_percent()
  149. return psutil.cpu_percent()
  150. except Exception:
  151. return 0.0
  152. def get_memory_usage(self) -> Dict[str, float]:
  153. """Get memory usage statistics."""
  154. try:
  155. if self.process and self.process.is_running():
  156. memory_info = self.process.memory_info()
  157. return {
  158. 'rss': memory_info.rss / 1024 / 1024, # MB
  159. 'vms': memory_info.vms / 1024 / 1024, # MB
  160. 'percent': self.process.memory_percent()
  161. }
  162. memory = psutil.virtual_memory()
  163. return {
  164. 'used': memory.used / 1024 / 1024 / 1024, # GB
  165. 'available': memory.available / 1024 / 1024 / 1024, # GB
  166. 'percent': memory.percent
  167. }
  168. except Exception:
  169. return {'rss': 0.0, 'vms': 0.0, 'percent': 0.0}
  170. def get_disk_usage(self) -> Dict[str, float]:
  171. """Get disk usage statistics."""
  172. try:
  173. disk = psutil.disk_usage('/')
  174. return {
  175. 'used': disk.used / 1024 / 1024 / 1024, # GB
  176. 'free': disk.free / 1024 / 1024 / 1024, # GB
  177. 'percent': (disk.used / disk.total) * 100
  178. }
  179. except Exception:
  180. return {'used': 0.0, 'free': 0.0, 'percent': 0.0}
  181. def get_network_stats(self) -> Dict[str, float]:
  182. """Get network I/O statistics."""
  183. try:
  184. net_io = psutil.net_io_counters()
  185. return {
  186. 'bytes_sent': net_io.bytes_sent / 1024 / 1024, # MB
  187. 'bytes_recv': net_io.bytes_recv / 1024 / 1024, # MB
  188. 'packets_sent': net_io.packets_sent,
  189. 'packets_recv': net_io.packets_recv
  190. }
  191. except Exception:
  192. return {'bytes_sent': 0.0, 'bytes_recv': 0.0, 'packets_sent': 0, 'packets_recv': 0}
  193. class PerformanceOptimizer:
  194. """Automatic performance optimization engine."""
  195. def __init__(self, metrics_collector: MetricsCollector):
  196. self.metrics_collector = metrics_collector
  197. self.optimization_rules: List[OptimizationRule] = []
  198. self.last_optimization: Dict[str, datetime] = {}
  199. self.logger = logging.getLogger(__name__)
  200. self._setup_default_rules()
  201. def _setup_default_rules(self) -> None:
  202. """Setup default optimization rules."""
  203. # High CPU usage optimization
  204. def high_cpu_condition(context: Dict[str, Any]) -> bool:
  205. cpu_usage = context.get('cpu_usage', 0)
  206. return cpu_usage > 80.0
  207. def reduce_cpu_action(context: Dict[str, Any]) -> None:
  208. self.logger.info("High CPU detected, reducing audio processing frequency")
  209. # Implementation would adjust audio processing parameters
  210. self.optimization_rules.append(OptimizationRule(
  211. name="high_cpu_optimization",
  212. condition=high_cpu_condition,
  213. action=reduce_cpu_action,
  214. cooldown=30.0,
  215. description="Reduce processing load when CPU usage is high"
  216. ))
  217. # High memory usage optimization
  218. def high_memory_condition(context: Dict[str, Any]) -> bool:
  219. memory_percent = context.get('memory_percent', 0)
  220. return memory_percent > 85.0
  221. def reduce_memory_action(context: Dict[str, Any]) -> None:
  222. self.logger.info("High memory detected, clearing buffers and caches")
  223. # Implementation would clear audio buffers and model caches
  224. self.optimization_rules.append(OptimizationRule(
  225. name="high_memory_optimization",
  226. condition=high_memory_condition,
  227. action=reduce_memory_action,
  228. cooldown=60.0,
  229. description="Clear caches when memory usage is high"
  230. ))
  231. # High latency optimization
  232. def high_latency_condition(context: Dict[str, Any]) -> bool:
  233. avg_latency = context.get('avg_latency', 0)
  234. return avg_latency > 500.0 # 500ms
  235. def reduce_latency_action(context: Dict[str, Any]) -> None:
  236. self.logger.info("High latency detected, switching to faster models")
  237. # Implementation would switch to faster but less accurate models
  238. self.optimization_rules.append(OptimizationRule(
  239. name="high_latency_optimization",
  240. condition=high_latency_condition,
  241. action=reduce_latency_action,
  242. cooldown=120.0,
  243. description="Use faster models when latency is high"
  244. ))
  245. def add_rule(self, rule: OptimizationRule) -> None:
  246. """Add a custom optimization rule."""
  247. self.optimization_rules.append(rule)
  248. def run_optimization(self, context: Dict[str, Any]) -> List[str]:
  249. """Run optimization rules and return applied optimizations."""
  250. applied_optimizations = []
  251. current_time = datetime.now()
  252. for rule in self.optimization_rules:
  253. # Check if rule is in cooldown
  254. last_run = self.last_optimization.get(rule.name)
  255. if last_run and (current_time - last_run).total_seconds() < rule.cooldown:
  256. continue
  257. # Check if rule condition is met
  258. try:
  259. if rule.condition(context):
  260. rule.action(context)
  261. self.last_optimization[rule.name] = current_time
  262. applied_optimizations.append(rule.name)
  263. self.logger.info(f"Applied optimization: {rule.description}")
  264. except Exception as e:
  265. self.logger.error(f"Error applying optimization rule {rule.name}: {e}")
  266. return applied_optimizations
  267. class AlertManager:
  268. """Manages performance alerts and notifications."""
  269. def __init__(self, metrics_collector: MetricsCollector):
  270. self.metrics_collector = metrics_collector
  271. self.thresholds: Dict[str, PerformanceThreshold] = {}
  272. self.active_alerts: Dict[str, Dict[str, Any]] = {}
  273. self.alert_history: List[Dict[str, Any]] = []
  274. self.logger = logging.getLogger(__name__)
  275. self._setup_default_thresholds()
  276. def _setup_default_thresholds(self) -> None:
  277. """Setup default performance thresholds."""
  278. self.thresholds['cpu_usage'] = PerformanceThreshold(
  279. metric_type=MetricType.CPU_USAGE,
  280. warning_threshold=70.0,
  281. critical_threshold=90.0,
  282. unit="percent"
  283. )
  284. self.thresholds['memory_usage'] = PerformanceThreshold(
  285. metric_type=MetricType.MEMORY_USAGE,
  286. warning_threshold=80.0,
  287. critical_threshold=95.0,
  288. unit="percent"
  289. )
  290. self.thresholds['latency'] = PerformanceThreshold(
  291. metric_type=MetricType.LATENCY,
  292. warning_threshold=300.0,
  293. critical_threshold=1000.0,
  294. unit="milliseconds"
  295. )
  296. self.thresholds['error_rate'] = PerformanceThreshold(
  297. metric_type=MetricType.ERROR_RATE,
  298. warning_threshold=5.0,
  299. critical_threshold=15.0,
  300. unit="percent"
  301. )
  302. def add_threshold(self, name: str, threshold: PerformanceThreshold) -> None:
  303. """Add a custom performance threshold."""
  304. self.thresholds[name] = threshold
  305. def check_thresholds(self, component: str) -> List[Dict[str, Any]]:
  306. """Check all thresholds for a component."""
  307. alerts = []
  308. window = timedelta(minutes=5)
  309. for name, threshold in self.thresholds.items():
  310. stats = self.metrics_collector.get_aggregated_stats(
  311. component, threshold.metric_type, window
  312. )
  313. if not stats or stats['count'] < threshold.min_samples:
  314. continue
  315. current_value = stats['mean']
  316. alert_level = None
  317. if current_value >= threshold.critical_threshold:
  318. alert_level = AlertLevel.CRITICAL
  319. elif current_value >= threshold.warning_threshold:
  320. alert_level = AlertLevel.WARNING
  321. if alert_level:
  322. alert_key = f"{component}:{name}"
  323. alert = {
  324. 'key': alert_key,
  325. 'component': component,
  326. 'metric_type': threshold.metric_type.value,
  327. 'level': alert_level.value,
  328. 'current_value': current_value,
  329. 'threshold': threshold.critical_threshold if alert_level == AlertLevel.CRITICAL else threshold.warning_threshold,
  330. 'unit': threshold.unit,
  331. 'timestamp': datetime.now(),
  332. 'message': f"{component} {threshold.metric_type.value} is {current_value:.2f} {threshold.unit}"
  333. }
  334. alerts.append(alert)
  335. self.active_alerts[alert_key] = alert
  336. self.alert_history.append(alert)
  337. self.logger.warning(f"Performance alert: {alert['message']}")
  338. return alerts
  339. def clear_alert(self, alert_key: str) -> bool:
  340. """Clear an active alert."""
  341. if alert_key in self.active_alerts:
  342. del self.active_alerts[alert_key]
  343. return True
  344. return False
  345. def get_active_alerts(self, component: Optional[str] = None) -> List[Dict[str, Any]]:
  346. """Get currently active alerts."""
  347. alerts = list(self.active_alerts.values())
  348. if component:
  349. alerts = [a for a in alerts if a['component'] == component]
  350. return alerts
  351. class PerformanceMonitor:
  352. """Main performance monitoring system for Trixy ML Integration."""
  353. def __init__(self, config: Optional[Dict[str, Any]] = None):
  354. self.config = config or {}
  355. self.logger = logging.getLogger(__name__)
  356. # Initialize components
  357. self.metrics_collector = MetricsCollector(
  358. max_history=self.config.get('max_metrics_history', 10000)
  359. )
  360. self.system_monitor = SystemMonitor()
  361. self.optimizer = PerformanceOptimizer(self.metrics_collector)
  362. self.alert_manager = AlertManager(self.metrics_collector)
  363. # Monitoring state
  364. self.monitoring_active = False
  365. self.monitoring_interval = self.config.get('monitoring_interval', 1.0)
  366. self.monitoring_task: Optional[asyncio.Task] = None
  367. self.executor = ThreadPoolExecutor(max_workers=2)
  368. # Performance context
  369. self.start_time = datetime.now()
  370. self.component_timers: Dict[str, float] = {}
  371. self.logger.info("Performance monitor initialized")
  372. async def start_monitoring(self) -> None:
  373. """Start continuous performance monitoring."""
  374. if self.monitoring_active:
  375. return
  376. self.monitoring_active = True
  377. self.monitoring_task = asyncio.create_task(self._monitoring_loop())
  378. self.logger.info("Performance monitoring started")
  379. async def stop_monitoring(self) -> None:
  380. """Stop performance monitoring."""
  381. self.monitoring_active = False
  382. if self.monitoring_task:
  383. self.monitoring_task.cancel()
  384. try:
  385. await self.monitoring_task
  386. except asyncio.CancelledError:
  387. pass
  388. self.executor.shutdown(wait=True)
  389. self.logger.info("Performance monitoring stopped")
  390. async def _monitoring_loop(self) -> None:
  391. """Main monitoring loop."""
  392. while self.monitoring_active:
  393. try:
  394. await self._collect_system_metrics()
  395. await self._run_optimization()
  396. await self._check_alerts()
  397. await asyncio.sleep(self.monitoring_interval)
  398. except asyncio.CancelledError:
  399. break
  400. except Exception as e:
  401. self.logger.error(f"Error in monitoring loop: {e}")
  402. await asyncio.sleep(self.monitoring_interval)
  403. async def _collect_system_metrics(self) -> None:
  404. """Collect system-level metrics."""
  405. try:
  406. timestamp = datetime.now()
  407. # CPU usage
  408. cpu_usage = await asyncio.get_event_loop().run_in_executor(
  409. self.executor, self.system_monitor.get_cpu_usage
  410. )
  411. self.record_metric("system", MetricType.CPU_USAGE, cpu_usage, "percent")
  412. # Memory usage
  413. memory_stats = await asyncio.get_event_loop().run_in_executor(
  414. self.executor, self.system_monitor.get_memory_usage
  415. )
  416. self.record_metric("system", MetricType.MEMORY_USAGE,
  417. memory_stats.get('percent', 0), "percent")
  418. except Exception as e:
  419. self.logger.error(f"Error collecting system metrics: {e}")
  420. async def _run_optimization(self) -> None:
  421. """Run performance optimization."""
  422. try:
  423. # Build optimization context
  424. context = {
  425. 'cpu_usage': await asyncio.get_event_loop().run_in_executor(
  426. self.executor, self.system_monitor.get_cpu_usage
  427. ),
  428. 'memory_percent': self.system_monitor.get_memory_usage().get('percent', 0),
  429. }
  430. # Add latency metrics
  431. latency_stats = self.metrics_collector.get_aggregated_stats(
  432. "ml_manager", MetricType.LATENCY, timedelta(minutes=5)
  433. )
  434. context['avg_latency'] = latency_stats.get('mean', 0) if latency_stats else 0
  435. applied_optimizations = await asyncio.get_event_loop().run_in_executor(
  436. self.executor, self.optimizer.run_optimization, context
  437. )
  438. except Exception as e:
  439. self.logger.error(f"Error running optimization: {e}")
  440. async def _check_alerts(self) -> None:
  441. """Check performance alerts."""
  442. try:
  443. components = ['system', 'ml_manager', 'audio_pipeline', 'wakeword', 'voice_recognition']
  444. for component in components:
  445. await asyncio.get_event_loop().run_in_executor(
  446. self.executor, self.alert_manager.check_thresholds, component
  447. )
  448. except Exception as e:
  449. self.logger.error(f"Error checking alerts: {e}")
  450. def record_metric(self, component: str, metric_type: MetricType,
  451. value: Union[float, int], unit: str,
  452. metadata: Optional[Dict[str, Any]] = None) -> None:
  453. """Record a performance metric."""
  454. metric = PerformanceMetric(
  455. timestamp=datetime.now(),
  456. component=component,
  457. metric_type=metric_type,
  458. value=value,
  459. unit=unit,
  460. metadata=metadata or {}
  461. )
  462. self.metrics_collector.record_metric(metric)
  463. def start_timer(self, component: str, operation: str) -> str:
  464. """Start a timer for an operation."""
  465. timer_key = f"{component}:{operation}"
  466. self.component_timers[timer_key] = time.time()
  467. return timer_key
  468. def end_timer(self, timer_key: str) -> float:
  469. """End a timer and record the duration."""
  470. if timer_key not in self.component_timers:
  471. return 0.0
  472. duration = (time.time() - self.component_timers[timer_key]) * 1000 # milliseconds
  473. del self.component_timers[timer_key]
  474. # Extract component from timer key
  475. component = timer_key.split(':')[0]
  476. self.record_metric(component, MetricType.PROCESSING_TIME, duration, "milliseconds")
  477. return duration
  478. def get_performance_report(self, window: timedelta = timedelta(hours=1)) -> PerformanceReport:
  479. """Generate a comprehensive performance report."""
  480. timestamp = datetime.now()
  481. components = ['system', 'ml_manager', 'audio_pipeline', 'wakeword', 'voice_recognition']
  482. component_stats = {}
  483. for component in components:
  484. component_stats[component] = {}
  485. for metric_type in MetricType:
  486. stats = self.metrics_collector.get_aggregated_stats(
  487. component, metric_type, window
  488. )
  489. if stats:
  490. component_stats[component][metric_type.value] = stats
  491. # System stats
  492. system_stats = {
  493. 'uptime': (timestamp - self.start_time).total_seconds(),
  494. 'cpu_usage': self.system_monitor.get_cpu_usage(),
  495. 'memory_usage': self.system_monitor.get_memory_usage(),
  496. 'disk_usage': self.system_monitor.get_disk_usage(),
  497. 'network_stats': self.system_monitor.get_network_stats()
  498. }
  499. # Alerts
  500. alerts = self.alert_manager.get_active_alerts()
  501. # Recommendations
  502. recommendations = self._generate_recommendations(component_stats, system_stats)
  503. return PerformanceReport(
  504. timestamp=timestamp,
  505. duration=window,
  506. component_stats=component_stats,
  507. system_stats=system_stats,
  508. alerts=alerts,
  509. recommendations=recommendations
  510. )
  511. def _generate_recommendations(self, component_stats: Dict[str, Any],
  512. system_stats: Dict[str, Any]) -> List[str]:
  513. """Generate performance optimization recommendations."""
  514. recommendations = []
  515. # CPU recommendations
  516. cpu_usage = system_stats.get('cpu_usage', 0)
  517. if cpu_usage > 80:
  518. recommendations.append("Consider reducing audio processing frequency or using lower quality models")
  519. # Memory recommendations
  520. memory_percent = system_stats.get('memory_usage', {}).get('percent', 0)
  521. if memory_percent > 85:
  522. recommendations.append("Consider clearing audio buffers more frequently or reducing buffer sizes")
  523. # Latency recommendations
  524. ml_stats = component_stats.get('ml_manager', {})
  525. latency_stats = ml_stats.get('latency', {})
  526. if latency_stats and latency_stats.get('mean', 0) > 500:
  527. recommendations.append("Consider switching to faster models or optimizing audio preprocessing")
  528. # Error rate recommendations
  529. error_stats = ml_stats.get('error_rate', {})
  530. if error_stats and error_stats.get('mean', 0) > 10:
  531. recommendations.append("High error rate detected - check model configurations and input quality")
  532. return recommendations
  533. def export_metrics(self, file_path: str, window: timedelta = timedelta(hours=24)) -> None:
  534. """Export metrics to a JSON file."""
  535. try:
  536. report = self.get_performance_report(window)
  537. # Convert report to JSON-serializable format
  538. export_data = {
  539. 'timestamp': report.timestamp.isoformat(),
  540. 'duration_hours': report.duration.total_seconds() / 3600,
  541. 'component_stats': report.component_stats,
  542. 'system_stats': report.system_stats,
  543. 'alerts': [
  544. {**alert, 'timestamp': alert['timestamp'].isoformat()}
  545. for alert in report.alerts
  546. ],
  547. 'recommendations': report.recommendations
  548. }
  549. with open(file_path, 'w') as f:
  550. json.dump(export_data, f, indent=2, default=str)
  551. self.logger.info(f"Metrics exported to {file_path}")
  552. except Exception as e:
  553. self.logger.error(f"Error exporting metrics: {e}")