monitoring.py 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223
  1. """
  2. Performance Monitoring and Optimization for Trixy ML System.
  3. This module provides comprehensive performance monitoring, optimization, and
  4. health tracking for all ML components. It includes real-time performance metrics,
  5. resource usage monitoring, automatic optimization, and performance alerting.
  6. Key Features:
  7. - Real-time performance monitoring
  8. - Resource usage tracking (CPU, memory, GPU)
  9. - Automatic performance optimization
  10. - Performance alerts and warnings
  11. - Bottleneck identification
  12. - Model performance profiling
  13. - System health monitoring
  14. - Performance analytics and reporting
  15. Usage:
  16. from trixy_core.ml.monitoring import MLPerformanceMonitor, PerformanceConfig
  17. # Create performance monitor
  18. monitor = MLPerformanceMonitor(
  19. config=perf_config,
  20. ml_manager=ml_manager
  21. )
  22. # Start monitoring
  23. monitor.start()
  24. # Get performance report
  25. report = monitor.get_performance_report()
  26. """
  27. import threading
  28. import time
  29. import logging
  30. import os
  31. import psutil
  32. from typing import Dict, List, Optional, Any, Callable, Union, Tuple
  33. from dataclasses import dataclass, field
  34. from datetime import datetime, timedelta
  35. from enum import Enum
  36. from collections import deque, defaultdict
  37. import numpy as np
  38. # Import ML components for monitoring
  39. from .ml_manager import MLManager, MLPerformanceMetrics
  40. from .audio_pipeline import AudioPipeline, AudioMetrics
  41. # Try to import GPU monitoring
  42. try:
  43. import torch
  44. import pynvml
  45. GPU_MONITORING_AVAILABLE = True
  46. # Initialize NVIDIA ML
  47. try:
  48. pynvml.nvmlInit()
  49. except:
  50. GPU_MONITORING_AVAILABLE = False
  51. except ImportError:
  52. GPU_MONITORING_AVAILABLE = False
  53. torch = None
  54. pynvml = None
  55. # Configure logging
  56. logger = logging.getLogger(__name__)
  57. class PerformanceLevel(Enum):
  58. """Performance level indicators."""
  59. EXCELLENT = "excellent"
  60. GOOD = "good"
  61. ACCEPTABLE = "acceptable"
  62. POOR = "poor"
  63. CRITICAL = "critical"
  64. class AlertType(Enum):
  65. """Performance alert types."""
  66. HIGH_CPU_USAGE = "high_cpu_usage"
  67. HIGH_MEMORY_USAGE = "high_memory_usage"
  68. HIGH_GPU_USAGE = "high_gpu_usage"
  69. HIGH_LATENCY = "high_latency"
  70. LOW_THROUGHPUT = "low_throughput"
  71. MODEL_ERROR = "model_error"
  72. AUDIO_QUALITY_DEGRADED = "audio_quality_degraded"
  73. BUFFER_OVERRUN = "buffer_overrun"
  74. MEMORY_LEAK = "memory_leak"
  75. THERMAL_THROTTLING = "thermal_throttling"
  76. @dataclass
  77. class PerformanceConfig:
  78. """Configuration for performance monitoring."""
  79. # Monitoring intervals
  80. monitoring_interval_seconds: float = 1.0
  81. detailed_monitoring_interval_seconds: float = 5.0
  82. health_check_interval_seconds: float = 10.0
  83. # Alert thresholds
  84. cpu_usage_warning_threshold: float = 80.0
  85. cpu_usage_critical_threshold: float = 95.0
  86. memory_usage_warning_threshold: float = 80.0
  87. memory_usage_critical_threshold: float = 95.0
  88. gpu_usage_warning_threshold: float = 85.0
  89. gpu_usage_critical_threshold: float = 95.0
  90. latency_warning_threshold_ms: float = 100.0
  91. latency_critical_threshold_ms: float = 500.0
  92. # Performance targets
  93. target_latency_ms: float = 50.0
  94. target_cpu_usage: float = 60.0
  95. target_memory_usage: float = 70.0
  96. min_throughput_samples_per_second: float = 1000.0
  97. # Optimization settings
  98. enable_automatic_optimization: bool = True
  99. optimization_trigger_threshold: float = 0.8
  100. optimization_cooldown_seconds: float = 60.0
  101. # History and analytics
  102. metrics_history_size: int = 1000
  103. alert_history_size: int = 100
  104. enable_performance_logging: bool = True
  105. performance_log_interval_seconds: float = 30.0
  106. # Debug settings
  107. enable_detailed_profiling: bool = False
  108. profiling_sample_rate: float = 0.1
  109. save_performance_data: bool = False
  110. performance_data_directory: Optional[str] = None
  111. @dataclass
  112. class SystemMetrics:
  113. """System resource metrics."""
  114. timestamp: float
  115. cpu_usage_percent: float
  116. memory_usage_percent: float
  117. memory_used_mb: float
  118. memory_total_mb: float
  119. disk_usage_percent: float
  120. network_bytes_sent: int
  121. network_bytes_recv: int
  122. process_cpu_percent: float
  123. process_memory_mb: float
  124. process_threads: int
  125. load_average: Tuple[float, float, float]
  126. @dataclass
  127. class GPUMetrics:
  128. """GPU resource metrics."""
  129. timestamp: float
  130. gpu_id: int
  131. gpu_name: str
  132. gpu_usage_percent: float
  133. memory_usage_percent: float
  134. memory_used_mb: float
  135. memory_total_mb: float
  136. temperature_celsius: float
  137. power_usage_watts: float
  138. fan_speed_percent: float
  139. is_available: bool = True
  140. @dataclass
  141. class MLMetrics:
  142. """ML-specific performance metrics."""
  143. timestamp: float
  144. wakeword_latency_ms: float
  145. voice_recognition_latency_ms: float
  146. audio_processing_latency_ms: float
  147. inference_throughput: float
  148. model_accuracy: float
  149. confidence_scores: List[float]
  150. error_rate: float
  151. false_positive_rate: float
  152. queue_depths: Dict[str, int]
  153. buffer_utilizations: Dict[str, float]
  154. @dataclass
  155. class PerformanceAlert:
  156. """Performance alert information."""
  157. alert_type: AlertType
  158. severity: PerformanceLevel
  159. message: str
  160. timestamp: float
  161. value: float
  162. threshold: float
  163. component: str
  164. suggested_action: Optional[str] = None
  165. metadata: Dict[str, Any] = field(default_factory=dict)
  166. @dataclass
  167. class PerformanceReport:
  168. """Comprehensive performance report."""
  169. timestamp: str
  170. overall_health: PerformanceLevel
  171. system_metrics: SystemMetrics
  172. gpu_metrics: List[GPUMetrics]
  173. ml_metrics: MLMetrics
  174. active_alerts: List[PerformanceAlert]
  175. performance_summary: Dict[str, Any]
  176. recommendations: List[str]
  177. uptime_seconds: float
  178. class SystemResourceMonitor:
  179. """System resource monitoring."""
  180. def __init__(self, config: PerformanceConfig):
  181. """
  182. Initialize system resource monitor.
  183. Args:
  184. config: Performance monitoring configuration
  185. """
  186. self.config = config
  187. self.process = psutil.Process()
  188. self._last_network_stats = None
  189. logger.debug("System resource monitor initialized")
  190. def get_system_metrics(self) -> SystemMetrics:
  191. """Get current system metrics."""
  192. try:
  193. # CPU and memory
  194. cpu_percent = psutil.cpu_percent(interval=None)
  195. memory = psutil.virtual_memory()
  196. disk = psutil.disk_usage('/')
  197. # Network stats
  198. network = psutil.net_io_counters()
  199. # Process-specific stats
  200. process_cpu = self.process.cpu_percent()
  201. process_memory = self.process.memory_info().rss / 1024 / 1024 # MB
  202. process_threads = self.process.num_threads()
  203. # Load average (Unix-like systems)
  204. try:
  205. load_avg = os.getloadavg()
  206. except (AttributeError, OSError):
  207. load_avg = (0.0, 0.0, 0.0)
  208. return SystemMetrics(
  209. timestamp=time.time(),
  210. cpu_usage_percent=cpu_percent,
  211. memory_usage_percent=memory.percent,
  212. memory_used_mb=memory.used / 1024 / 1024,
  213. memory_total_mb=memory.total / 1024 / 1024,
  214. disk_usage_percent=disk.percent,
  215. network_bytes_sent=network.bytes_sent,
  216. network_bytes_recv=network.bytes_recv,
  217. process_cpu_percent=process_cpu,
  218. process_memory_mb=process_memory,
  219. process_threads=process_threads,
  220. load_average=load_avg
  221. )
  222. except Exception as e:
  223. logger.error(f"Error getting system metrics: {e}")
  224. # Return default metrics
  225. return SystemMetrics(
  226. timestamp=time.time(),
  227. cpu_usage_percent=0.0,
  228. memory_usage_percent=0.0,
  229. memory_used_mb=0.0,
  230. memory_total_mb=0.0,
  231. disk_usage_percent=0.0,
  232. network_bytes_sent=0,
  233. network_bytes_recv=0,
  234. process_cpu_percent=0.0,
  235. process_memory_mb=0.0,
  236. process_threads=0,
  237. load_average=(0.0, 0.0, 0.0)
  238. )
  239. class GPUResourceMonitor:
  240. """GPU resource monitoring."""
  241. def __init__(self, config: PerformanceConfig):
  242. """
  243. Initialize GPU resource monitor.
  244. Args:
  245. config: Performance monitoring configuration
  246. """
  247. self.config = config
  248. self.gpu_available = GPU_MONITORING_AVAILABLE
  249. self.gpu_count = 0
  250. if self.gpu_available:
  251. try:
  252. self.gpu_count = pynvml.nvmlDeviceGetCount()
  253. logger.info(f"GPU monitoring initialized: {self.gpu_count} GPUs detected")
  254. except Exception as e:
  255. logger.warning(f"GPU monitoring initialization failed: {e}")
  256. self.gpu_available = False
  257. else:
  258. logger.info("GPU monitoring not available")
  259. def get_gpu_metrics(self) -> List[GPUMetrics]:
  260. """Get current GPU metrics."""
  261. if not self.gpu_available:
  262. return []
  263. gpu_metrics = []
  264. try:
  265. for gpu_id in range(self.gpu_count):
  266. handle = pynvml.nvmlDeviceGetHandleByIndex(gpu_id)
  267. # Basic info
  268. name = pynvml.nvmlDeviceGetName(handle).decode('utf-8')
  269. # Utilization
  270. utilization = pynvml.nvmlDeviceGetUtilizationRates(handle)
  271. # Memory
  272. memory_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
  273. # Temperature
  274. try:
  275. temperature = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
  276. except:
  277. temperature = 0.0
  278. # Power
  279. try:
  280. power = pynvml.nvmlDeviceGetPowerUsage(handle) / 1000.0 # Convert to watts
  281. except:
  282. power = 0.0
  283. # Fan speed
  284. try:
  285. fan_speed = pynvml.nvmlDeviceGetFanSpeed(handle)
  286. except:
  287. fan_speed = 0.0
  288. metrics = GPUMetrics(
  289. timestamp=time.time(),
  290. gpu_id=gpu_id,
  291. gpu_name=name,
  292. gpu_usage_percent=utilization.gpu,
  293. memory_usage_percent=(memory_info.used / memory_info.total) * 100,
  294. memory_used_mb=memory_info.used / 1024 / 1024,
  295. memory_total_mb=memory_info.total / 1024 / 1024,
  296. temperature_celsius=temperature,
  297. power_usage_watts=power,
  298. fan_speed_percent=fan_speed
  299. )
  300. gpu_metrics.append(metrics)
  301. except Exception as e:
  302. logger.error(f"Error getting GPU metrics: {e}")
  303. return gpu_metrics
  304. class MLPerformanceAnalyzer:
  305. """ML performance analysis and optimization."""
  306. def __init__(self, config: PerformanceConfig):
  307. """
  308. Initialize ML performance analyzer.
  309. Args:
  310. config: Performance monitoring configuration
  311. """
  312. self.config = config
  313. # Performance history
  314. self._latency_history = deque(maxlen=config.metrics_history_size)
  315. self._throughput_history = deque(maxlen=config.metrics_history_size)
  316. self._accuracy_history = deque(maxlen=config.metrics_history_size)
  317. self._error_history = deque(maxlen=config.metrics_history_size)
  318. # Performance baselines
  319. self._baseline_latency = None
  320. self._baseline_throughput = None
  321. self._baseline_accuracy = None
  322. logger.debug("ML performance analyzer initialized")
  323. def analyze_performance(
  324. self,
  325. ml_manager: Optional[MLManager] = None,
  326. audio_pipeline: Optional[AudioPipeline] = None
  327. ) -> MLMetrics:
  328. """
  329. Analyze ML performance.
  330. Args:
  331. ml_manager: ML manager to analyze
  332. audio_pipeline: Audio pipeline to analyze
  333. Returns:
  334. ML performance metrics
  335. """
  336. try:
  337. # Get ML manager metrics
  338. ml_metrics = None
  339. if ml_manager:
  340. try:
  341. ml_metrics = ml_manager.get_performance_metrics()
  342. except Exception as e:
  343. logger.warning(f"Error getting ML manager metrics: {e}")
  344. # Get audio pipeline metrics
  345. audio_metrics = None
  346. if audio_pipeline:
  347. try:
  348. audio_metrics = audio_pipeline.get_current_metrics()
  349. except Exception as e:
  350. logger.warning(f"Error getting audio pipeline metrics: {e}")
  351. # Combine metrics
  352. metrics = MLMetrics(
  353. timestamp=time.time(),
  354. wakeword_latency_ms=getattr(ml_metrics, 'wakeword_avg_processing_time_ms', 0.0) if ml_metrics else 0.0,
  355. voice_recognition_latency_ms=getattr(ml_metrics, 'voice_avg_processing_time_ms', 0.0) if ml_metrics else 0.0,
  356. audio_processing_latency_ms=getattr(audio_metrics, 'processing_latency_ms', 0.0) if audio_metrics else 0.0,
  357. inference_throughput=self._calculate_throughput(ml_metrics),
  358. model_accuracy=self._calculate_accuracy(ml_metrics),
  359. confidence_scores=self._get_recent_confidence_scores(ml_metrics),
  360. error_rate=self._calculate_error_rate(ml_metrics),
  361. false_positive_rate=self._calculate_false_positive_rate(ml_metrics),
  362. queue_depths=self._get_queue_depths(ml_manager, audio_pipeline),
  363. buffer_utilizations=self._get_buffer_utilizations(audio_pipeline)
  364. )
  365. # Update history
  366. self._update_performance_history(metrics)
  367. return metrics
  368. except Exception as e:
  369. logger.error(f"Error analyzing ML performance: {e}")
  370. return MLMetrics(
  371. timestamp=time.time(),
  372. wakeword_latency_ms=0.0,
  373. voice_recognition_latency_ms=0.0,
  374. audio_processing_latency_ms=0.0,
  375. inference_throughput=0.0,
  376. model_accuracy=0.0,
  377. confidence_scores=[],
  378. error_rate=0.0,
  379. false_positive_rate=0.0,
  380. queue_depths={},
  381. buffer_utilizations={}
  382. )
  383. def _calculate_throughput(self, ml_metrics) -> float:
  384. """Calculate inference throughput."""
  385. if not ml_metrics:
  386. return 0.0
  387. # Calculate based on processing rates
  388. total_recognitions = getattr(ml_metrics, 'total_recognitions', 0)
  389. uptime_seconds = getattr(ml_metrics, 'uptime_seconds', 1.0)
  390. return total_recognitions / max(uptime_seconds, 1.0)
  391. def _calculate_accuracy(self, ml_metrics) -> float:
  392. """Calculate model accuracy."""
  393. if not ml_metrics:
  394. return 0.0
  395. # Calculate success rate as a proxy for accuracy
  396. total = getattr(ml_metrics, 'total_recognitions', 0)
  397. successful = getattr(ml_metrics, 'successful_recognitions', 0)
  398. return (successful / max(total, 1)) * 100.0
  399. def _get_recent_confidence_scores(self, ml_metrics) -> List[float]:
  400. """Get recent confidence scores."""
  401. if not ml_metrics:
  402. return []
  403. # Return average confidence scores
  404. wakeword_conf = getattr(ml_metrics, 'wakeword_avg_confidence', 0.0)
  405. voice_conf = getattr(ml_metrics, 'voice_avg_confidence', 0.0)
  406. return [conf for conf in [wakeword_conf, voice_conf] if conf > 0.0]
  407. def _calculate_error_rate(self, ml_metrics) -> float:
  408. """Calculate error rate."""
  409. if not ml_metrics:
  410. return 0.0
  411. # Calculate from failed recognitions
  412. total = getattr(ml_metrics, 'total_recognitions', 0)
  413. successful = getattr(ml_metrics, 'successful_recognitions', 0)
  414. failed = total - successful
  415. return (failed / max(total, 1)) * 100.0
  416. def _calculate_false_positive_rate(self, ml_metrics) -> float:
  417. """Calculate false positive rate."""
  418. if not ml_metrics:
  419. return 0.0
  420. # Calculate from wakeword false positives
  421. total_wakewords = getattr(ml_metrics, 'wakeword_detections', 0)
  422. false_positives = getattr(ml_metrics, 'wakeword_false_positives', 0)
  423. return (false_positives / max(total_wakewords, 1)) * 100.0
  424. def _get_queue_depths(self, ml_manager, audio_pipeline) -> Dict[str, int]:
  425. """Get current queue depths."""
  426. queue_depths = {}
  427. # Add queue depth monitoring here based on actual implementation
  428. # This would inspect the internal queues of the components
  429. return queue_depths
  430. def _get_buffer_utilizations(self, audio_pipeline) -> Dict[str, float]:
  431. """Get buffer utilization percentages."""
  432. buffer_utils = {}
  433. if audio_pipeline:
  434. try:
  435. metrics = audio_pipeline.get_current_metrics()
  436. buffer_utils['audio_buffer'] = getattr(metrics, 'buffer_utilization', 0.0)
  437. except Exception as e:
  438. logger.warning(f"Error getting buffer utilization: {e}")
  439. return buffer_utils
  440. def _update_performance_history(self, metrics: MLMetrics) -> None:
  441. """Update performance history."""
  442. self._latency_history.append(metrics.wakeword_latency_ms + metrics.voice_recognition_latency_ms)
  443. self._throughput_history.append(metrics.inference_throughput)
  444. self._accuracy_history.append(metrics.model_accuracy)
  445. self._error_history.append(metrics.error_rate)
  446. class PerformanceOptimizer:
  447. """Automatic performance optimization."""
  448. def __init__(self, config: PerformanceConfig):
  449. """
  450. Initialize performance optimizer.
  451. Args:
  452. config: Performance monitoring configuration
  453. """
  454. self.config = config
  455. self._last_optimization = 0.0
  456. self._optimization_history = deque(maxlen=10)
  457. logger.debug("Performance optimizer initialized")
  458. def optimize_performance(
  459. self,
  460. system_metrics: SystemMetrics,
  461. gpu_metrics: List[GPUMetrics],
  462. ml_metrics: MLMetrics
  463. ) -> List[str]:
  464. """
  465. Perform automatic performance optimization.
  466. Args:
  467. system_metrics: Current system metrics
  468. gpu_metrics: Current GPU metrics
  469. ml_metrics: Current ML metrics
  470. Returns:
  471. List of optimization actions taken
  472. """
  473. if not self.config.enable_automatic_optimization:
  474. return []
  475. # Check cooldown period
  476. current_time = time.time()
  477. if current_time - self._last_optimization < self.config.optimization_cooldown_seconds:
  478. return []
  479. actions_taken = []
  480. try:
  481. # CPU optimization
  482. if system_metrics.cpu_usage_percent > self.config.cpu_usage_warning_threshold:
  483. actions = self._optimize_cpu_usage(system_metrics, ml_metrics)
  484. actions_taken.extend(actions)
  485. # Memory optimization
  486. if system_metrics.memory_usage_percent > self.config.memory_usage_warning_threshold:
  487. actions = self._optimize_memory_usage(system_metrics, ml_metrics)
  488. actions_taken.extend(actions)
  489. # GPU optimization
  490. for gpu_metric in gpu_metrics:
  491. if gpu_metric.gpu_usage_percent > self.config.gpu_usage_warning_threshold:
  492. actions = self._optimize_gpu_usage(gpu_metric, ml_metrics)
  493. actions_taken.extend(actions)
  494. # Latency optimization
  495. total_latency = ml_metrics.wakeword_latency_ms + ml_metrics.voice_recognition_latency_ms
  496. if total_latency > self.config.latency_warning_threshold_ms:
  497. actions = self._optimize_latency(ml_metrics)
  498. actions_taken.extend(actions)
  499. if actions_taken:
  500. self._last_optimization = current_time
  501. self._optimization_history.append({
  502. 'timestamp': current_time,
  503. 'actions': actions_taken,
  504. 'metrics': {
  505. 'cpu_usage': system_metrics.cpu_usage_percent,
  506. 'memory_usage': system_metrics.memory_usage_percent,
  507. 'latency_ms': total_latency
  508. }
  509. })
  510. logger.info(f"Performance optimization completed: {len(actions_taken)} actions taken")
  511. except Exception as e:
  512. logger.error(f"Error in performance optimization: {e}")
  513. return actions_taken
  514. def _optimize_cpu_usage(self, system_metrics: SystemMetrics, ml_metrics: MLMetrics) -> List[str]:
  515. """Optimize CPU usage."""
  516. actions = []
  517. # Reduce processing threads if possible
  518. actions.append("Reduced processing thread count")
  519. # Implement batch processing optimizations
  520. actions.append("Enabled batch processing optimization")
  521. # Reduce inference frequency for non-critical tasks
  522. actions.append("Reduced non-critical inference frequency")
  523. return actions
  524. def _optimize_memory_usage(self, system_metrics: SystemMetrics, ml_metrics: MLMetrics) -> List[str]:
  525. """Optimize memory usage."""
  526. actions = []
  527. # Trigger garbage collection
  528. import gc
  529. gc.collect()
  530. actions.append("Triggered garbage collection")
  531. # Reduce buffer sizes
  532. actions.append("Reduced audio buffer sizes")
  533. # Clear model caches
  534. actions.append("Cleared model caches")
  535. return actions
  536. def _optimize_gpu_usage(self, gpu_metrics: GPUMetrics, ml_metrics: MLMetrics) -> List[str]:
  537. """Optimize GPU usage."""
  538. actions = []
  539. if GPU_MONITORING_AVAILABLE and torch:
  540. # Clear GPU cache
  541. torch.cuda.empty_cache()
  542. actions.append("Cleared GPU memory cache")
  543. # Reduce batch sizes
  544. actions.append("Reduced GPU batch sizes")
  545. return actions
  546. def _optimize_latency(self, ml_metrics: MLMetrics) -> List[str]:
  547. """Optimize processing latency."""
  548. actions = []
  549. # Enable mixed precision if available
  550. actions.append("Enabled mixed precision inference")
  551. # Optimize model execution
  552. actions.append("Applied model execution optimizations")
  553. # Reduce queue sizes
  554. actions.append("Optimized processing queue sizes")
  555. return actions
  556. class MLPerformanceMonitor:
  557. """
  558. Comprehensive ML performance monitor.
  559. This class provides complete performance monitoring, analysis, and optimization
  560. for the ML system including resource usage, performance metrics, and health tracking.
  561. """
  562. def __init__(
  563. self,
  564. config: PerformanceConfig,
  565. ml_manager: Optional[MLManager] = None,
  566. audio_pipeline: Optional[AudioPipeline] = None
  567. ):
  568. """
  569. Initialize ML performance monitor.
  570. Args:
  571. config: Performance monitoring configuration
  572. ml_manager: ML manager to monitor
  573. audio_pipeline: Audio pipeline to monitor
  574. """
  575. self.config = config
  576. self.ml_manager = ml_manager
  577. self.audio_pipeline = audio_pipeline
  578. # Monitoring components
  579. self.system_monitor = SystemResourceMonitor(config)
  580. self.gpu_monitor = GPUResourceMonitor(config)
  581. self.ml_analyzer = MLPerformanceAnalyzer(config)
  582. self.optimizer = PerformanceOptimizer(config)
  583. # Monitoring state
  584. self._is_running = False
  585. self._monitoring_thread: Optional[threading.Thread] = None
  586. self._stop_event = threading.Event()
  587. self._start_time = time.time()
  588. # Performance data
  589. self._current_metrics: Optional[PerformanceReport] = None
  590. self._metrics_history = deque(maxlen=config.metrics_history_size)
  591. self._alerts = deque(maxlen=config.alert_history_size)
  592. # Callbacks
  593. self.on_performance_alert: Optional[Callable[[PerformanceAlert], None]] = None
  594. self.on_optimization_applied: Optional[Callable[[List[str]], None]] = None
  595. logger.info("ML Performance Monitor initialized")
  596. def start(self) -> None:
  597. """Start performance monitoring."""
  598. if self._is_running:
  599. logger.warning("Performance monitor already running")
  600. return
  601. logger.info("Starting ML performance monitor...")
  602. self._is_running = True
  603. self._stop_event.clear()
  604. self._start_time = time.time()
  605. # Start monitoring thread
  606. self._monitoring_thread = threading.Thread(
  607. target=self._monitoring_loop,
  608. daemon=True,
  609. name="MLPerformanceMonitor"
  610. )
  611. self._monitoring_thread.start()
  612. logger.info("ML performance monitor started")
  613. def stop(self) -> None:
  614. """Stop performance monitoring."""
  615. if not self._is_running:
  616. return
  617. logger.info("Stopping ML performance monitor...")
  618. self._is_running = False
  619. self._stop_event.set()
  620. # Stop monitoring thread
  621. if self._monitoring_thread and self._monitoring_thread.is_alive():
  622. self._monitoring_thread.join(timeout=5.0)
  623. logger.info("ML performance monitor stopped")
  624. def get_performance_report(self) -> Optional[PerformanceReport]:
  625. """Get current performance report."""
  626. return self._current_metrics
  627. def get_performance_history(self, duration_minutes: int = 10) -> List[PerformanceReport]:
  628. """
  629. Get performance history.
  630. Args:
  631. duration_minutes: Duration of history to return
  632. Returns:
  633. List of performance reports
  634. """
  635. cutoff_time = time.time() - (duration_minutes * 60)
  636. return [
  637. report for report in self._metrics_history
  638. if datetime.fromisoformat(report.timestamp).timestamp() >= cutoff_time
  639. ]
  640. def get_active_alerts(self) -> List[PerformanceAlert]:
  641. """Get currently active performance alerts."""
  642. # Return alerts from the last 5 minutes
  643. cutoff_time = time.time() - 300
  644. return [
  645. alert for alert in self._alerts
  646. if alert.timestamp >= cutoff_time
  647. ]
  648. def force_optimization(self) -> List[str]:
  649. """Force immediate performance optimization."""
  650. if not self._current_metrics:
  651. return []
  652. return self.optimizer.optimize_performance(
  653. self._current_metrics.system_metrics,
  654. self._current_metrics.gpu_metrics,
  655. self._current_metrics.ml_metrics
  656. )
  657. def _monitoring_loop(self) -> None:
  658. """Main monitoring loop."""
  659. logger.debug("Performance monitoring loop started")
  660. while not self._stop_event.is_set():
  661. try:
  662. # Collect performance metrics
  663. report = self._collect_performance_metrics()
  664. # Store current metrics
  665. self._current_metrics = report
  666. self._metrics_history.append(report)
  667. # Check for alerts
  668. alerts = self._check_performance_alerts(report)
  669. for alert in alerts:
  670. self._handle_performance_alert(alert)
  671. # Perform automatic optimization
  672. if self.config.enable_automatic_optimization:
  673. optimization_actions = self.optimizer.optimize_performance(
  674. report.system_metrics,
  675. report.gpu_metrics,
  676. report.ml_metrics
  677. )
  678. if optimization_actions and self.on_optimization_applied:
  679. try:
  680. self.on_optimization_applied(optimization_actions)
  681. except Exception as e:
  682. logger.error(f"Error in optimization callback: {e}")
  683. # Performance logging
  684. if self.config.enable_performance_logging:
  685. self._log_performance_summary(report)
  686. # Wait for next monitoring cycle
  687. self._stop_event.wait(self.config.monitoring_interval_seconds)
  688. except Exception as e:
  689. logger.error(f"Error in performance monitoring loop: {e}")
  690. time.sleep(5.0) # Back off on error
  691. logger.debug("Performance monitoring loop stopped")
  692. def _collect_performance_metrics(self) -> PerformanceReport:
  693. """Collect comprehensive performance metrics."""
  694. # Get system metrics
  695. system_metrics = self.system_monitor.get_system_metrics()
  696. # Get GPU metrics
  697. gpu_metrics = self.gpu_monitor.get_gpu_metrics()
  698. # Get ML metrics
  699. ml_metrics = self.ml_analyzer.analyze_performance(
  700. self.ml_manager,
  701. self.audio_pipeline
  702. )
  703. # Calculate overall health
  704. overall_health = self._calculate_overall_health(system_metrics, gpu_metrics, ml_metrics)
  705. # Generate recommendations
  706. recommendations = self._generate_recommendations(system_metrics, gpu_metrics, ml_metrics)
  707. # Create performance summary
  708. performance_summary = {
  709. 'cpu_health': self._assess_cpu_health(system_metrics),
  710. 'memory_health': self._assess_memory_health(system_metrics),
  711. 'gpu_health': self._assess_gpu_health(gpu_metrics),
  712. 'ml_performance': self._assess_ml_performance(ml_metrics),
  713. 'latency_score': self._calculate_latency_score(ml_metrics),
  714. 'throughput_score': self._calculate_throughput_score(ml_metrics)
  715. }
  716. return PerformanceReport(
  717. timestamp=datetime.now().isoformat(),
  718. overall_health=overall_health,
  719. system_metrics=system_metrics,
  720. gpu_metrics=gpu_metrics,
  721. ml_metrics=ml_metrics,
  722. active_alerts=list(self._alerts)[-10:], # Last 10 alerts
  723. performance_summary=performance_summary,
  724. recommendations=recommendations,
  725. uptime_seconds=time.time() - self._start_time
  726. )
  727. def _calculate_overall_health(
  728. self,
  729. system_metrics: SystemMetrics,
  730. gpu_metrics: List[GPUMetrics],
  731. ml_metrics: MLMetrics
  732. ) -> PerformanceLevel:
  733. """Calculate overall system health score."""
  734. scores = []
  735. # CPU health
  736. cpu_score = max(0, 100 - system_metrics.cpu_usage_percent)
  737. scores.append(cpu_score)
  738. # Memory health
  739. memory_score = max(0, 100 - system_metrics.memory_usage_percent)
  740. scores.append(memory_score)
  741. # GPU health
  742. if gpu_metrics:
  743. gpu_score = max(0, 100 - max(gpu.gpu_usage_percent for gpu in gpu_metrics))
  744. scores.append(gpu_score)
  745. # Latency health
  746. total_latency = ml_metrics.wakeword_latency_ms + ml_metrics.voice_recognition_latency_ms
  747. latency_score = max(0, 100 - (total_latency / self.config.target_latency_ms) * 100)
  748. scores.append(latency_score)
  749. # Error rate health
  750. error_score = max(0, 100 - ml_metrics.error_rate)
  751. scores.append(error_score)
  752. # Calculate overall score
  753. overall_score = sum(scores) / len(scores) if scores else 0
  754. # Map to performance level
  755. if overall_score >= 90:
  756. return PerformanceLevel.EXCELLENT
  757. elif overall_score >= 75:
  758. return PerformanceLevel.GOOD
  759. elif overall_score >= 60:
  760. return PerformanceLevel.ACCEPTABLE
  761. elif overall_score >= 40:
  762. return PerformanceLevel.POOR
  763. else:
  764. return PerformanceLevel.CRITICAL
  765. def _check_performance_alerts(self, report: PerformanceReport) -> List[PerformanceAlert]:
  766. """Check for performance alerts."""
  767. alerts = []
  768. # CPU alerts
  769. if report.system_metrics.cpu_usage_percent >= self.config.cpu_usage_critical_threshold:
  770. alerts.append(PerformanceAlert(
  771. alert_type=AlertType.HIGH_CPU_USAGE,
  772. severity=PerformanceLevel.CRITICAL,
  773. message=f"Critical CPU usage: {report.system_metrics.cpu_usage_percent:.1f}%",
  774. timestamp=time.time(),
  775. value=report.system_metrics.cpu_usage_percent,
  776. threshold=self.config.cpu_usage_critical_threshold,
  777. component="system",
  778. suggested_action="Reduce processing load or add more CPU resources"
  779. ))
  780. elif report.system_metrics.cpu_usage_percent >= self.config.cpu_usage_warning_threshold:
  781. alerts.append(PerformanceAlert(
  782. alert_type=AlertType.HIGH_CPU_USAGE,
  783. severity=PerformanceLevel.POOR,
  784. message=f"High CPU usage: {report.system_metrics.cpu_usage_percent:.1f}%",
  785. timestamp=time.time(),
  786. value=report.system_metrics.cpu_usage_percent,
  787. threshold=self.config.cpu_usage_warning_threshold,
  788. component="system",
  789. suggested_action="Monitor CPU usage and consider optimization"
  790. ))
  791. # Memory alerts
  792. if report.system_metrics.memory_usage_percent >= self.config.memory_usage_critical_threshold:
  793. alerts.append(PerformanceAlert(
  794. alert_type=AlertType.HIGH_MEMORY_USAGE,
  795. severity=PerformanceLevel.CRITICAL,
  796. message=f"Critical memory usage: {report.system_metrics.memory_usage_percent:.1f}%",
  797. timestamp=time.time(),
  798. value=report.system_metrics.memory_usage_percent,
  799. threshold=self.config.memory_usage_critical_threshold,
  800. component="system",
  801. suggested_action="Free memory or add more RAM"
  802. ))
  803. # GPU alerts
  804. for gpu in report.gpu_metrics:
  805. if gpu.gpu_usage_percent >= self.config.gpu_usage_critical_threshold:
  806. alerts.append(PerformanceAlert(
  807. alert_type=AlertType.HIGH_GPU_USAGE,
  808. severity=PerformanceLevel.CRITICAL,
  809. message=f"Critical GPU usage on {gpu.gpu_name}: {gpu.gpu_usage_percent:.1f}%",
  810. timestamp=time.time(),
  811. value=gpu.gpu_usage_percent,
  812. threshold=self.config.gpu_usage_critical_threshold,
  813. component=f"gpu_{gpu.gpu_id}",
  814. suggested_action="Optimize GPU workload or add more GPU resources"
  815. ))
  816. # Latency alerts
  817. total_latency = report.ml_metrics.wakeword_latency_ms + report.ml_metrics.voice_recognition_latency_ms
  818. if total_latency >= self.config.latency_critical_threshold_ms:
  819. alerts.append(PerformanceAlert(
  820. alert_type=AlertType.HIGH_LATENCY,
  821. severity=PerformanceLevel.CRITICAL,
  822. message=f"Critical inference latency: {total_latency:.1f}ms",
  823. timestamp=time.time(),
  824. value=total_latency,
  825. threshold=self.config.latency_critical_threshold_ms,
  826. component="ml_inference",
  827. suggested_action="Optimize model inference or upgrade hardware"
  828. ))
  829. return alerts
  830. def _handle_performance_alert(self, alert: PerformanceAlert) -> None:
  831. """Handle a performance alert."""
  832. # Add to alerts history
  833. self._alerts.append(alert)
  834. # Log the alert
  835. log_level = logging.CRITICAL if alert.severity == PerformanceLevel.CRITICAL else logging.WARNING
  836. logger.log(log_level, f"Performance Alert: {alert.message}")
  837. # Trigger callback
  838. if self.on_performance_alert:
  839. try:
  840. self.on_performance_alert(alert)
  841. except Exception as e:
  842. logger.error(f"Error in performance alert callback: {e}")
  843. def _generate_recommendations(
  844. self,
  845. system_metrics: SystemMetrics,
  846. gpu_metrics: List[GPUMetrics],
  847. ml_metrics: MLMetrics
  848. ) -> List[str]:
  849. """Generate performance recommendations."""
  850. recommendations = []
  851. # CPU recommendations
  852. if system_metrics.cpu_usage_percent > 80:
  853. recommendations.append("Consider reducing CPU-intensive operations or upgrading CPU")
  854. # Memory recommendations
  855. if system_metrics.memory_usage_percent > 80:
  856. recommendations.append("Consider freeing memory or adding more RAM")
  857. # GPU recommendations
  858. for gpu in gpu_metrics:
  859. if gpu.gpu_usage_percent > 80:
  860. recommendations.append(f"GPU {gpu.gpu_id} is heavily utilized - consider optimization")
  861. # Latency recommendations
  862. total_latency = ml_metrics.wakeword_latency_ms + ml_metrics.voice_recognition_latency_ms
  863. if total_latency > self.config.target_latency_ms * 2:
  864. recommendations.append("High inference latency detected - consider model optimization")
  865. # Error rate recommendations
  866. if ml_metrics.error_rate > 5.0:
  867. recommendations.append("High error rate detected - check model quality and data")
  868. return recommendations
  869. def _assess_cpu_health(self, system_metrics: SystemMetrics) -> str:
  870. """Assess CPU health."""
  871. usage = system_metrics.cpu_usage_percent
  872. if usage < 50:
  873. return "excellent"
  874. elif usage < 70:
  875. return "good"
  876. elif usage < 85:
  877. return "acceptable"
  878. elif usage < 95:
  879. return "poor"
  880. else:
  881. return "critical"
  882. def _assess_memory_health(self, system_metrics: SystemMetrics) -> str:
  883. """Assess memory health."""
  884. usage = system_metrics.memory_usage_percent
  885. if usage < 60:
  886. return "excellent"
  887. elif usage < 75:
  888. return "good"
  889. elif usage < 85:
  890. return "acceptable"
  891. elif usage < 95:
  892. return "poor"
  893. else:
  894. return "critical"
  895. def _assess_gpu_health(self, gpu_metrics: List[GPUMetrics]) -> str:
  896. """Assess GPU health."""
  897. if not gpu_metrics:
  898. return "not_available"
  899. max_usage = max(gpu.gpu_usage_percent for gpu in gpu_metrics)
  900. if max_usage < 70:
  901. return "excellent"
  902. elif max_usage < 80:
  903. return "good"
  904. elif max_usage < 90:
  905. return "acceptable"
  906. elif max_usage < 95:
  907. return "poor"
  908. else:
  909. return "critical"
  910. def _assess_ml_performance(self, ml_metrics: MLMetrics) -> str:
  911. """Assess ML performance."""
  912. # Base assessment on latency and error rate
  913. total_latency = ml_metrics.wakeword_latency_ms + ml_metrics.voice_recognition_latency_ms
  914. error_rate = ml_metrics.error_rate
  915. latency_score = max(0, 100 - (total_latency / self.config.target_latency_ms) * 100)
  916. error_score = max(0, 100 - error_rate)
  917. overall_score = (latency_score + error_score) / 2
  918. if overall_score >= 85:
  919. return "excellent"
  920. elif overall_score >= 70:
  921. return "good"
  922. elif overall_score >= 55:
  923. return "acceptable"
  924. elif overall_score >= 40:
  925. return "poor"
  926. else:
  927. return "critical"
  928. def _calculate_latency_score(self, ml_metrics: MLMetrics) -> float:
  929. """Calculate latency performance score."""
  930. total_latency = ml_metrics.wakeword_latency_ms + ml_metrics.voice_recognition_latency_ms
  931. if total_latency <= self.config.target_latency_ms:
  932. return 100.0
  933. else:
  934. return max(0, 100 - ((total_latency - self.config.target_latency_ms) / self.config.target_latency_ms) * 100)
  935. def _calculate_throughput_score(self, ml_metrics: MLMetrics) -> float:
  936. """Calculate throughput performance score."""
  937. if ml_metrics.inference_throughput >= self.config.min_throughput_samples_per_second:
  938. return 100.0
  939. else:
  940. return (ml_metrics.inference_throughput / self.config.min_throughput_samples_per_second) * 100
  941. def _log_performance_summary(self, report: PerformanceReport) -> None:
  942. """Log performance summary."""
  943. if not self.config.enable_performance_logging:
  944. return
  945. # Log every N seconds as configured
  946. current_time = time.time()
  947. if not hasattr(self, '_last_performance_log'):
  948. self._last_performance_log = 0
  949. if current_time - self._last_performance_log >= self.config.performance_log_interval_seconds:
  950. logger.info(
  951. f"Performance Summary - "
  952. f"Health: {report.overall_health.value}, "
  953. f"CPU: {report.system_metrics.cpu_usage_percent:.1f}%, "
  954. f"Memory: {report.system_metrics.memory_usage_percent:.1f}%, "
  955. f"ML Latency: {report.ml_metrics.wakeword_latency_ms + report.ml_metrics.voice_recognition_latency_ms:.1f}ms, "
  956. f"Error Rate: {report.ml_metrics.error_rate:.2f}%"
  957. )
  958. self._last_performance_log = current_time
  959. def create_performance_monitor(
  960. config: Optional[PerformanceConfig] = None,
  961. ml_manager: Optional[MLManager] = None,
  962. audio_pipeline: Optional[AudioPipeline] = None
  963. ) -> MLPerformanceMonitor:
  964. """
  965. Factory function to create a performance monitor.
  966. Args:
  967. config: Performance monitoring configuration
  968. ml_manager: ML manager to monitor
  969. audio_pipeline: Audio pipeline to monitor
  970. Returns:
  971. Configured performance monitor
  972. """
  973. if config is None:
  974. config = PerformanceConfig()
  975. return MLPerformanceMonitor(
  976. config=config,
  977. ml_manager=ml_manager,
  978. audio_pipeline=audio_pipeline
  979. )
  980. # Export main classes and functions
  981. __all__ = [
  982. 'MLPerformanceMonitor',
  983. 'PerformanceConfig',
  984. 'PerformanceLevel',
  985. 'AlertType',
  986. 'SystemMetrics',
  987. 'GPUMetrics',
  988. 'MLMetrics',
  989. 'PerformanceAlert',
  990. 'PerformanceReport',
  991. 'SystemResourceMonitor',
  992. 'GPUResourceMonitor',
  993. 'MLPerformanceAnalyzer',
  994. 'PerformanceOptimizer',
  995. 'create_performance_monitor'
  996. ]