1#![allow(non_snake_case)]
19
20use chrono::Utc;
92use std::collections::HashMap as FxHashMap;
93use std::sync::Arc;
94use tokio::sync::RwLock;
95
96#[cfg(feature = "pyo3")]
97use pyo3::prelude::*;
98
99#[cfg(feature = "pyo3")]
100use pyo3::PyResult;
101
102use super::core::{RiDevice, RiDeviceCapabilities, RiDeviceStatus, RiDeviceType, RiDeviceControlConfig, RiNetworkDeviceInfo};
103use super::discovery::{RiDeviceDiscovery, DiscoveryConfig};
104use crate::core::RiResult;
105use crate::prelude::RiMetricsRegistry;
106#[cfg(not(target_os = "macos"))]
107use crate::prelude::RiError;
108#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
112pub struct RiDeviceController {
113 devices: FxHashMap<String, Arc<RwLock<RiDevice>>>,
114 device_type_index: FxHashMap<RiDeviceType, Vec<String>>,
115 allocation_map: FxHashMap<String, String>,
116 discovery: Option<Arc<RiDeviceDiscovery>>,
117}
118
119#[cfg(feature = "pyo3")]
120#[pymethods]
121impl RiDeviceController {
122 #[new]
123 fn py_new() -> Self {
124 Self::new()
125 }
126
127 #[staticmethod]
128 fn default_controller() -> Self {
129 Self::default()
130 }
131
132 #[pyo3(name = "discover_devices")]
133 fn discover_devices_impl(&mut self) -> PyResult<super::RiDiscoveryResult> {
134 let rt = crate::py_runtime::py_runtime();
135
136 rt.block_on(self.discover_devices()).map_err(|e| {
137 pyo3::exceptions::PyRuntimeError::new_err(format!("Device discovery failed: {}", e))
138 })
139 }
140
141 #[pyo3(name = "discover_system_devices")]
142 fn discover_system_devices_impl(&mut self, config: &RiDeviceControlConfig) -> PyResult<()> {
143 let rt = crate::py_runtime::py_runtime();
144
145 rt.block_on(self.discover_system_devices(config)).map_err(|e| {
146 pyo3::exceptions::PyRuntimeError::new_err(format!("System device discovery failed: {}", e))
147 })
148 }
149
150 #[pyo3(name = "find_suitable_device")]
151 fn find_suitable_device_impl(&self, device_type: &RiDeviceType, requirements: &RiDeviceCapabilities) -> PyResult<Option<RiDevice>> {
152 let rt = crate::py_runtime::py_runtime();
153
154 rt.block_on(self.find_suitable_device(device_type, requirements)).map_err(|e| {
155 pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to find suitable device: {}", e))
156 })
157 }
158
159 #[pyo3(name = "allocate_device")]
160 fn allocate_device_impl(&mut self, device_id: &str, allocation_id: &str) -> PyResult<()> {
161 let rt = crate::py_runtime::py_runtime();
162
163 rt.block_on(self.allocate_device(device_id, allocation_id)).map_err(|e| {
164 pyo3::exceptions::PyRuntimeError::new_err(format!("Device allocation failed: {}", e))
165 })
166 }
167
168 #[pyo3(name = "release_device_by_allocation")]
169 fn release_device_by_allocation_impl(&mut self, allocation_id: &str) -> PyResult<()> {
170 let rt = crate::py_runtime::py_runtime();
171
172 rt.block_on(self.release_device_by_allocation(allocation_id)).map_err(|e| {
173 pyo3::exceptions::PyRuntimeError::new_err(format!("Device release failed: {}", e))
174 })
175 }
176
177 #[pyo3(name = "get_all_devices")]
178 fn get_all_devices_impl(&self) -> Vec<RiDevice> {
179 self.get_all_devices()
180 }
181
182 #[pyo3(name = "release_all_devices")]
183 fn release_all_devices_impl(&mut self) -> PyResult<()> {
184 self.release_all_devices().map_err(|e| {
185 pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to release all devices: {}", e))
186 })
187 }
188
189 #[pyo3(name = "perform_health_checks")]
190 fn perform_health_checks_impl(&mut self) -> PyResult<Vec<(String, u8)>> {
191 let rt = crate::py_runtime::py_runtime();
192
193 rt.block_on(self.perform_health_checks()).map_err(|e| {
194 pyo3::exceptions::PyRuntimeError::new_err(format!("Health checks failed: {}", e))
195 })
196 }
197
198 #[pyo3(name = "get_device_health")]
199 fn get_device_health_impl(&self, device_id: &str) -> PyResult<super::core::RiDeviceHealthMetrics> {
200 let rt = crate::py_runtime::py_runtime();
201
202 rt.block_on(self.get_device_health(device_id)).map_err(|e| {
203 pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to get device health: {}", e))
204 })
205 }
206
207 #[pyo3(name = "get_all_device_health")]
208 fn get_all_device_health_impl(&self) -> PyResult<FxHashMap<String, super::core::RiDeviceHealthMetrics>> {
209 let rt = crate::py_runtime::py_runtime();
210
211 rt.block_on(self.get_all_device_health()).map_err(|e| {
212 pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to get all device health: {}", e))
213 })
214 }
215
216 #[pyo3(name = "device_count")]
217 fn device_count_impl(&self) -> usize {
218 self.devices.len()
219 }
220
221 #[pyo3(name = "get_devices_by_type")]
222 fn get_devices_by_type_impl(&self, device_type: &RiDeviceType) -> Vec<RiDevice> {
223 let device_ids = match self.device_type_index.get(device_type) {
224 Some(ids) => ids.clone(),
225 None => return Vec::new(),
226 };
227
228 let mut devices = Vec::with_capacity(4);
229 for device_id in device_ids {
230 if let Some(device_lock) = self.devices.get(&device_id) {
231 if let Ok(device) = device_lock.try_read() {
232 devices.push(device.clone());
233 }
234 }
235 }
236 devices
237 }
238
239 #[pyo3(name = "start_health_checks")]
240 fn start_health_checks_impl(&self, interval_secs: u64) -> PyResult<String> {
241 let _handle = self.start_health_checks(interval_secs);
242 Ok(format!("Health check task started with interval {} seconds", interval_secs))
243 }
244}
245
246impl Default for RiDeviceController {
247 fn default() -> Self {
248 Self::new()
249 }
250}
251
252impl RiDeviceController {
253 pub fn new() -> Self {
254 Self {
255 devices: FxHashMap::default(),
256 device_type_index: FxHashMap::default(),
257 allocation_map: FxHashMap::default(),
258 discovery: None,
259 }
260 }
261
262 pub async fn with_discovery(discovery: Arc<RiDeviceDiscovery>) -> Self {
264 Self {
265 devices: FxHashMap::default(),
266 device_type_index: FxHashMap::default(),
267 allocation_map: FxHashMap::default(),
268 discovery: Some(discovery),
269 }
270 }
271
272 pub async fn init_discovery(&mut self) -> RiResult<()> {
274 let config = DiscoveryConfig::default();
275 let discovery = Arc::new(RiDeviceDiscovery::new(config).await?);
276 self.discovery = Some(discovery);
277 Ok(())
278 }
279
280 pub async fn discover_devices(&mut self) -> RiResult<super::RiDiscoveryResult> {
282 let max_retries = 3;
287 let retry_delay = std::time::Duration::from_millis(500);
288
289 for attempt in 0..max_retries {
290 match self.perform_device_discovery().await {
291 Ok(result) => return Ok(result),
292 Err(e) => {
293 if attempt == max_retries - 1 {
294 return Err(e);
296 }
297
298 let error_msg = format!("Device discovery attempt {} failed: {}, retrying in {}ms",
300 attempt + 1, e, retry_delay.as_millis());
301 log::warn!("{error_msg}");
302
303 tokio::time::sleep(retry_delay).await;
305 }
306 }
307 }
308
309 Err(crate::core::RiError::Other("Device discovery failed after maximum retries".to_string()))
311 }
312
313 async fn perform_device_discovery(&mut self) -> RiResult<super::RiDiscoveryResult> {
319 let mut discovered_devices = Vec::with_capacity(4);
320 let mut updated_devices = Vec::with_capacity(4);
321 let mut removed_devices = Vec::with_capacity(4);
322
323 for device_lock in self.devices.values() {
325 match device_lock.try_write() {
326 Ok(mut device) => {
327 device.update_last_seen();
328
329 let health_metrics = device.health_metrics().clone();
331 let device_type = device.device_type();
332
333 match device_type {
335 RiDeviceType::CPU => {
336 if health_metrics.cpu_usage_percent > 95.0 || health_metrics.temperature_celsius > 90.0 {
338 device.set_status(RiDeviceStatus::Degraded);
339 } else if health_metrics.cpu_usage_percent > 80.0 || health_metrics.temperature_celsius > 80.0 {
340 device.set_status(RiDeviceStatus::Busy);
341 } else if device.status() != RiDeviceStatus::Allocated {
342 device.set_status(RiDeviceStatus::Available);
343 }
344 },
345 RiDeviceType::GPU => {
346 if health_metrics.cpu_usage_percent > 95.0 || health_metrics.temperature_celsius > 95.0 {
348 device.set_status(RiDeviceStatus::Degraded);
349 } else if health_metrics.cpu_usage_percent > 85.0 || health_metrics.temperature_celsius > 85.0 {
350 device.set_status(RiDeviceStatus::Busy);
351 } else if device.status() != RiDeviceStatus::Allocated {
352 device.set_status(RiDeviceStatus::Available);
353 }
354 },
355 RiDeviceType::Network => {
356 if health_metrics.network_latency_ms > 200.0 {
358 device.set_status(RiDeviceStatus::Degraded);
359 } else if health_metrics.network_latency_ms > 100.0 {
360 device.set_status(RiDeviceStatus::Busy);
361 } else if device.status() != RiDeviceStatus::Allocated {
362 device.set_status(RiDeviceStatus::Available);
363 }
364 },
365 RiDeviceType::Storage => {
366 if health_metrics.response_time_ms > 100.0 {
368 device.set_status(RiDeviceStatus::Degraded);
369 } else if health_metrics.response_time_ms > 50.0 {
370 device.set_status(RiDeviceStatus::Busy);
371 } else if device.status() != RiDeviceStatus::Allocated {
372 device.set_status(RiDeviceStatus::Available);
373 }
374 },
375 _ => {
376 if health_metrics.error_count > 5 {
378 device.set_status(RiDeviceStatus::Degraded);
379 } else if device.status() != RiDeviceStatus::Allocated {
380 device.set_status(RiDeviceStatus::Available);
381 }
382 }
383 }
384
385 updated_devices.push(device.clone());
386 },
387 Err(_) => {
388 continue;
390 }
391 }
392 }
393
394 let new_hardware_devices = self.discover_hardware_devices().await?;
396
397 for device in new_hardware_devices {
399 let device_id = device.id().to_string();
400
401 if !self.devices.contains_key(&device_id) {
403 self.devices.insert(device_id.clone(), Arc::new(RwLock::new(device.clone())));
404 self.device_type_index
405 .entry(device.device_type())
406 .or_default()
407 .push(device_id);
408
409 discovered_devices.push(device);
410 }
411 }
412
413 if rand::random::<f64>() < 0.05 {
415 let new_device = self.create_mock_device_for_discovery();
417 let device_id = new_device.id().to_string();
418
419 self.devices
420 .insert(device_id.clone(), Arc::new(RwLock::new(new_device.clone())));
421 self.device_type_index
422 .entry(new_device.device_type())
423 .or_default()
424 .push(device_id);
425
426 discovered_devices.push(new_device);
427 }
428
429 let timeout = chrono::TimeDelta::minutes(5);
431 let now = Utc::now();
432
433 let mut to_remove = Vec::with_capacity(4);
434 for (device_id, device_lock) in &self.devices {
435 match device_lock.try_read() {
436 Ok(device) => {
437 if now.signed_duration_since(device.last_seen()) > timeout {
438 to_remove.push(device_id.clone());
439 }
440 },
441 Err(_) => {
442 continue;
444 }
445 }
446 }
447
448 for device_id in &to_remove {
449 self.remove_device(device_id).await?;
450 removed_devices.push(device_id.to_string());
451 }
452
453 Ok(super::RiDiscoveryResult {
454 discovered_devices,
455 updated_devices,
456 removed_devices,
457 total_devices: self.devices.len(),
458 })
459 }
460
461 pub async fn discover_system_devices(&mut self, config: &RiDeviceControlConfig) -> RiResult<()> {
463 self.discover_cpu_devices(config).await?;
465
466 self.discover_gpu_devices(config).await?;
468
469 self.discover_memory_devices(config).await?;
471
472 self.discover_storage_devices(config).await?;
474
475 self.discover_network_devices(config).await?;
477
478 Ok(())
479 }
480
481 async fn discover_gpu_devices(&mut self, _config: &RiDeviceControlConfig) -> RiResult<()> {
483 #[cfg(target_os = "windows")]
484 {
485 if let Ok(nvidia_output) = std::process::Command::new("nvidia-smi")
487 .args(["--query-gpu=name,memory.total", "--format=csv,noheader"])
488 .output()
489 {
490 let gpu_info = String::from_utf8_lossy(&nvidia_output.stdout);
491
492 for (index, line) in gpu_info.lines().enumerate() {
493 let parts: Vec<&str> = line.split(',').collect();
494 if parts.len() >= 2 {
495 let name = parts[0].trim();
496 let memory_mb = parts[1].trim().replace(" MiB", "").parse::<f64>().unwrap_or(0.0);
497 let memory_gb = memory_mb / 1024.0;
498
499 let gpu_device = RiDevice::new(
500 format!("GPU-{}-{}", index + 1, name),
501 RiDeviceType::GPU
502 ).with_capabilities(
503 RiDeviceCapabilities::new()
504 .with_compute_units(1000) .with_memory_gb(memory_gb)
506 );
507
508 self.add_device(gpu_device, "NVIDIA GPU".to_string()).await?;
509 }
510 }
511 }
512 }
513
514 #[cfg(target_os = "linux")]
515 {
516 if let Ok(nvidia_output) = std::process::Command::new("nvidia-smi")
518 .args(&["--query-gpu=name,memory.total", "--format=csv,noheader"])
519 .output()
520 {
521 let gpu_info = String::from_utf8_lossy(&nvidia_output.stdout);
522
523 for (index, line) in gpu_info.lines().enumerate() {
524 let parts: Vec<&str> = line.split(',').collect();
525 if parts.len() >= 2 {
526 let name = parts[0].trim();
527 let memory_mb = parts[1].trim().replace(" MiB", "").parse::<f64>().unwrap_or(0.0);
528 let memory_gb = memory_mb / 1024.0;
529
530 let gpu_device = RiDevice::new(
531 format!("GPU-{}-{}", index + 1, name),
532 RiDeviceType::GPU
533 ).with_capabilities(
534 RiDeviceCapabilities::new()
535 .with_compute_units(1000) .with_memory_gb(memory_gb)
537 );
538
539 self.add_device(gpu_device, "NVIDIA GPU".to_string()).await?;
540 }
541 }
542 }
543 }
544
545 Ok(())
546 }
547
548 #[allow(dead_code)]
550 async fn discover_storage_devices_impl(&mut self, _config: &RiDeviceControlConfig) -> RiResult<Vec<RiDevice>> {
551 #[cfg(target_os = "windows")]
552 {
553 if let Ok(nvidia_output) = std::process::Command::new("nvidia-smi")
555 .args(["--query-gpu=name,memory.total", "--format=csv,noheader"])
556 .output()
557 {
558 let gpu_info = String::from_utf8_lossy(&nvidia_output.stdout);
559
560 for (index, line) in gpu_info.lines().enumerate() {
561 let parts: Vec<&str> = line.split(',').collect();
562 if parts.len() >= 2 {
563 let name = parts[0].trim();
564 let memory_mb = parts[1].trim().replace(" MiB", "").parse::<f64>().unwrap_or(0.0);
565 let memory_gb = memory_mb / 1024.0;
566
567 let gpu_device = RiDevice::new(
568 format!("GPU-{}-{}", index + 1, name),
569 RiDeviceType::GPU
570 ).with_capabilities(
571 RiDeviceCapabilities::new()
572 .with_compute_units(1000) .with_memory_gb(memory_gb)
574 );
575
576 self.add_device(gpu_device, "NVIDIA GPU".to_string()).await?;
577 }
578 }
579 }
580 }
581
582 #[cfg(target_os = "linux")]
583 {
584 if let Ok(nvidia_output) = std::process::Command::new("nvidia-smi")
586 .args(&["--query-gpu=name,memory.total", "--format=csv,noheader"])
587 .output()
588 {
589 let gpu_info = String::from_utf8_lossy(&nvidia_output.stdout);
590
591 for (index, line) in gpu_info.lines().enumerate() {
592 let parts: Vec<&str> = line.split(',').collect();
593 if parts.len() >= 2 {
594 let name = parts[0].trim();
595 let memory_mb = parts[1].trim().replace(" MiB", "").parse::<f64>().unwrap_or(0.0);
596 let memory_gb = memory_mb / 1024.0;
597
598 let gpu_device = RiDevice::new(
599 format!("GPU-{}-{}", index + 1, name),
600 RiDeviceType::GPU
601 ).with_capabilities(
602 RiDeviceCapabilities::new()
603 .with_compute_units(1000) .with_memory_gb(memory_gb)
605 );
606
607 self.add_device(gpu_device, "NVIDIA GPU".to_string()).await?;
608 }
609 }
610 }
611 }
612
613 Ok(vec![])
614 }
615
616 async fn discover_memory_devices(&mut self, _config: &RiDeviceControlConfig) -> RiResult<()> {
618 #[cfg(target_os = "windows")]
619 {
620 let output = std::process::Command::new("powershell")
621 .args(["-NoProfile", "-Command", "Get-CimInstance Win32_PhysicalMemory | ForEach-Object { 'Capacity=' + $_.Capacity; 'Speed=' + $_.Speed }"])
622 .output()
623 .map_err(|e| RiError::DeviceError(format!("Failed to query memory info: {e}")))?;
624
625 let memory_info = String::from_utf8_lossy(&output.stdout);
626
627 let mut total_capacity_gb = 0.0;
628 let mut memory_modules = 0;
629
630 for line in memory_info.lines() {
631 if line.starts_with("Capacity=") {
632 if let Some(capacity_bytes) = line.split('=').nth(1).and_then(|s| s.trim().parse::<u64>().ok()) {
633 total_capacity_gb += capacity_bytes as f64 / (1024.0 * 1024.0 * 1024.0);
634 memory_modules += 1;
635 }
636 }
637 }
638
639 if memory_modules > 0 {
640 let memory_device = RiDevice::new(
641 format!("Memory-{}GB-total", total_capacity_gb.round() as u32),
642 RiDeviceType::Memory
643 ).with_capabilities(
644 RiDeviceCapabilities::new()
645 .with_memory_gb(total_capacity_gb)
646 .with_bandwidth_gbps(25.6) );
648
649 self.add_device(memory_device, "System Memory".to_string()).await?;
650 }
651 }
652
653 #[cfg(target_os = "linux")]
654 {
655 if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
656 for line in meminfo.lines() {
657 if line.starts_with("MemTotal:") {
658 if let Some(kb_str) = line.split_whitespace().nth(1) {
659 if let Ok(kb) = kb_str.parse::<f64>() {
660 let total_gb = kb / (1024.0 * 1024.0);
661
662 let memory_device = RiDevice::new(
663 format!("Memory-{}GB-total", total_gb.round() as u32),
664 RiDeviceType::Memory
665 ).with_capabilities(
666 RiDeviceCapabilities::new()
667 .with_memory_gb(total_gb)
668 .with_bandwidth_gbps(25.6) );
670
671 self.add_device(memory_device, "System Memory".to_string()).await?;
672 break;
673 }
674 }
675 }
676 }
677 }
678 }
679
680 Ok(())
681 }
682
683 async fn discover_cpu_devices(&mut self, _config: &RiDeviceControlConfig) -> RiResult<()> {
685 #[cfg(target_os = "windows")]
686 {
687 let output = std::process::Command::new("powershell")
688 .args(["-NoProfile", "-Command", "Get-CimInstance Win32_Processor | ForEach-Object { 'Name=' + $_.Name; 'NumberOfCores=' + $_.NumberOfCores; 'NumberOfLogicalProcessors=' + $_.NumberOfLogicalProcessors }"])
689 .output()
690 .map_err(|e| RiError::DeviceError(format!("Failed to query CPU info: {e}")))?;
691
692 let cpu_info = String::from_utf8_lossy(&output.stdout);
693
694 let total_memory_gb: f64 = std::process::Command::new("powershell")
698 .args(["-NoProfile", "-Command", "[math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 1)"])
699 .output()
700 .ok()
701 .and_then(|o| String::from_utf8_lossy(&o.stdout).trim().parse::<f64>().ok())
702 .unwrap_or(0.0);
703
704 let mut cpu_count = 0;
705 let mut total_cores = 0;
706 let mut total_threads = 0;
707
708 for line in cpu_info.lines() {
709 if line.starts_with("Name=") {
710 cpu_count += 1;
711 } else if line.starts_with("NumberOfCores=") {
712 if let Some(cores) = line.split('=').nth(1).and_then(|s| s.trim().parse::<usize>().ok()) {
713 total_cores += cores;
714 }
715 } else if line.starts_with("NumberOfLogicalProcessors=") {
716 if let Some(threads) = line.split('=').nth(1).and_then(|s| s.trim().parse::<usize>().ok()) {
717 total_threads += threads;
718 }
719 }
720 }
721
722 if cpu_count > 0 {
723 let cpu_device = RiDevice::new(
724 format!("CPU-{total_cores}-cores-{total_threads}-threads"),
725 RiDeviceType::CPU
726 ).with_capabilities(
727 RiDeviceCapabilities::new()
728 .with_compute_units(total_cores)
729 .with_memory_gb(total_memory_gb)
730 );
731
732 self.add_device(cpu_device, "System Hardware".to_string()).await?;
733 }
734 }
735
736 #[cfg(target_os = "linux")]
737 {
738 let cpu_info = std::fs::read_to_string("/proc/cpuinfo")
740 .map_err(|e| RiError::DeviceError(format!("Failed to read cpuinfo: {}", e)))?;
741
742 let mut cpu_count = 0;
743 let mut total_cores = 0;
744
745 for line in cpu_info.lines() {
746 if line.starts_with("processor\t") {
747 cpu_count += 1;
748 } else if line.starts_with("cpu cores\t") {
749 if let Some(cores) = line.split(':').nth(1).and_then(|s| s.trim().parse::<usize>().ok()) {
750 total_cores = cores;
751 }
752 }
753 }
754
755 let total_threads = cpu_count; let total_memory_gb: f64 = std::fs::read_to_string("/proc/meminfo")
759 .ok()
760 .and_then(|mem| {
761 mem.lines().find(|l| l.starts_with("MemTotal:")).and_then(|l| {
762 l.split_whitespace().nth(1)
763 .and_then(|kb| kb.parse::<f64>().ok())
764 .map(|kb| (kb / 1024.0 / 1024.0 * 10.0).round() / 10.0)
765 })
766 })
767 .unwrap_or(0.0);
768
769 if cpu_count > 0 {
770 let cpu_device = RiDevice::new(
771 format!("CPU-{}-cores-{}-threads", total_cores, total_threads),
772 RiDeviceType::CPU
773 ).with_capabilities(
774 RiDeviceCapabilities::new()
775 .with_compute_units(total_cores)
776 .with_memory_gb(total_memory_gb)
777 );
778
779 self.add_device(cpu_device, "System Hardware".to_string()).await?;
780 }
781 }
782
783 Ok(())
784 }
785
786 async fn discover_storage_devices(&mut self, config: &RiDeviceControlConfig) -> RiResult<()> {
788 self.discover_storage_devices_impl2(config).await
790 }
791
792 async fn discover_storage_devices_impl2(&mut self, _config: &RiDeviceControlConfig) -> RiResult<()> {
794 #[cfg(target_os = "windows")]
795 {
796 let output = std::process::Command::new("powershell")
797 .args(["-NoProfile", "-Command", "Get-CimInstance Win32_DiskDrive | ForEach-Object { 'Model=' + $_.Model; 'Size=' + $_.Size }"])
798 .output()
799 .map_err(|e| RiError::DeviceError(format!("Failed to query disk info: {e}")))?;
800
801 let disk_info = String::from_utf8_lossy(&output.stdout);
802
803 let mut disk_counter = 0;
804 let lines: Vec<&str> = disk_info.lines().collect();
806
807 for (disk_index, line) in lines.iter().enumerate() {
808 if line.starts_with("Model=") {
809 let model = line.split('=').nth(1).unwrap_or("Unknown").trim();
810 disk_counter += 1;
811
812 if disk_index + 1 < lines.len() && lines[disk_index + 1].starts_with("Size=") {
814 let size_line = lines[disk_index + 1];
815 if let Some(size_bytes) = size_line.split('=').nth(1).and_then(|s| s.trim().parse::<u64>().ok()) {
816 let size_gb = size_bytes as f64 / (1024.0 * 1024.0 * 1024.0);
817
818 let storage_device = RiDevice::new(
819 format!("Storage-{disk_counter}-{model}"),
820 RiDeviceType::Storage
821 ).with_capabilities(
822 RiDeviceCapabilities::new()
823 .with_storage_gb(size_gb)
824 .with_bandwidth_gbps(6.0) );
826
827 self.add_device(storage_device, "System Storage".to_string()).await?;
828 }
829 }
830 }
831 }
832 }
833
834 #[cfg(target_os = "linux")]
835 {
836 if let Ok(entries) = std::fs::read_dir("/sys/block") {
838 for (index, entry) in entries.enumerate() {
839 if let Ok(entry) = entry {
840 let device_name = entry.file_name().to_string_lossy().to_string();
841
842 if device_name.starts_with("loop") || device_name.starts_with("ram") {
844 continue;
845 }
846
847 let size_path = entry.path().join("size");
849 if let Ok(size_str) = std::fs::read_to_string(&size_path) {
850 if let Ok(size_sectors) = size_str.trim().parse::<u64>() {
851 let size_gb = (size_sectors * 512) as f64 / (1024.0 * 1024.0 * 1024.0);
852
853 let storage_device = RiDevice::new(
854 format!("Storage-{}-{}", index + 1, device_name),
855 RiDeviceType::Storage
856 ).with_capabilities(
857 RiDeviceCapabilities::new()
858 .with_storage_gb(size_gb)
859 .with_bandwidth_gbps(6.0) );
861
862 self.add_device(storage_device, "System Storage".to_string()).await?;
863 }
864 }
865 }
866 }
867 }
868 }
869
870 Ok(())
871 }
872
873 async fn discover_hardware_devices(&mut self) -> RiResult<Vec<RiDevice>> {
875 if let Some(discovery) = &self.discovery {
877 let devices = discovery.discover_all().await?;
878 return Ok(devices);
879 }
880
881 let mut temp_controller = RiDeviceController::new();
883 let config = RiDeviceControlConfig::default();
884 temp_controller.discover_system_devices(&config).await?;
885 Ok(temp_controller.get_all_devices())
886 }
887
888 async fn discover_network_devices(&mut self, _config: &RiDeviceControlConfig) -> RiResult<()> {
890 #[cfg(target_os = "windows")]
891 {
892 let output = std::process::Command::new("powershell")
893 .args(["-NoProfile", "-Command", "Get-CimInstance Win32_NetworkAdapter -Filter 'NetEnabled=true' | ForEach-Object { 'Name=' + $_.Name; 'Speed=' + $_.Speed }"])
894 .output()
895 .map_err(|e| RiError::DeviceError(format!("Failed to query network info: {e}")))?;
896
897 let network_info = String::from_utf8_lossy(&output.stdout);
898
899 let mut network_counter = 0;
900 let lines: Vec<&str> = network_info.lines().collect();
902
903 for (network_index, line) in lines.iter().enumerate() {
904 if line.starts_with("Name=") {
905 let name = line.split('=').nth(1).unwrap_or("Unknown").trim();
906 network_counter += 1;
907
908 if let Some(speed_line) = lines.iter().skip(network_index + 1).find(|l| l.starts_with("Speed=")) {
910 if let Some(speed_bps) = speed_line.split('=').nth(1).and_then(|s| s.trim().parse::<u64>().ok()) {
911 let speed_gbps = speed_bps as f64 / (1000.0 * 1000.0 * 1000.0);
912
913 let network_device = RiDevice::new(
914 format!("Network-{network_counter}-{name}"),
915 RiDeviceType::Network
916 ).with_capabilities(
917 RiDeviceCapabilities::new()
918 .with_bandwidth_gbps(speed_gbps)
919 );
920
921 self.add_device(network_device, "System Network".to_string()).await?;
922 }
923 }
924 }
925 }
926 }
927
928 #[cfg(target_os = "linux")]
929 {
930 if let Ok(entries) = std::fs::read_dir("/sys/class/net") {
932 for (index, entry) in entries.enumerate() {
933 if let Ok(entry) = entry {
934 let interface_name = entry.file_name().to_string_lossy().to_string();
935
936 if interface_name == "lo" {
938 continue;
939 }
940
941 let speed_path = entry.path().join("speed");
943 if let Ok(speed_str) = std::fs::read_to_string(&speed_path) {
944 if let Ok(speed_mbps) = speed_str.trim().parse::<f64>() {
945 let speed_gbps = speed_mbps / 1000.0;
946
947 let network_device = RiDevice::new(
948 format!("Network-{}-{}", index + 1, interface_name),
949 RiDeviceType::Network
950 ).with_capabilities(
951 RiDeviceCapabilities::new()
952 .with_bandwidth_gbps(speed_gbps)
953 );
954
955 self.add_device(network_device, "System Network".to_string()).await?;
956 }
957 }
958 }
959 }
960 }
961 }
962
963 Ok(())
964 }
965
966 #[allow(dead_code)]
968 pub async fn add_device(&mut self, mut device: RiDevice, location: String) -> RiResult<()> {
969 device.set_status(RiDeviceStatus::Available);
970 device.set_location(location);
971
972 let device_id = device.id().to_string();
973 let device_type = device.device_type();
974
975 self.devices.insert(device_id.clone(), Arc::new(RwLock::new(device)));
976 self.device_type_index
977 .entry(device_type)
978 .or_default()
979 .push(device_id);
980
981 Ok(())
982 }
983
984 #[allow(dead_code)]
986 fn create_discovered_device(&self, device_info: &RiNetworkDeviceInfo) -> RiDevice {
987 let device_type_enum = match device_info.device_type.as_str() {
988 "CPU" => RiDeviceType::CPU,
989 "GPU" => RiDeviceType::GPU,
990 "Memory" => RiDeviceType::Memory,
991 "Storage" => RiDeviceType::Storage,
992 "Network" => RiDeviceType::Network,
993 _ => RiDeviceType::Custom,
994 };
995
996 let name = format!("Discovered-{}-{}", device_info.device_type, device_info.id);
997 let mut device = RiDevice::new(name, device_type_enum);
998
999 let mut capabilities = RiDeviceCapabilities::new();
1001
1002 match device_type_enum {
1003 RiDeviceType::CPU => {
1004 capabilities = capabilities
1005 .with_compute_units(device_info.compute_units.unwrap_or(8))
1006 .with_memory_gb(device_info.memory_gb.unwrap_or(16.0));
1007 }
1008 RiDeviceType::GPU => {
1009 capabilities = capabilities
1010 .with_compute_units(device_info.compute_units.unwrap_or(1000))
1011 .with_memory_gb(device_info.memory_gb.unwrap_or(8.0));
1012 }
1013 RiDeviceType::Memory => {
1014 capabilities = capabilities
1015 .with_memory_gb(device_info.memory_gb.unwrap_or(64.0))
1016 .with_bandwidth_gbps(device_info.bandwidth_gbps.unwrap_or(25.6));
1017 }
1018 RiDeviceType::Storage => {
1019 capabilities = capabilities
1020 .with_storage_gb(device_info.storage_gb.unwrap_or(1000.0))
1021 .with_bandwidth_gbps(device_info.bandwidth_gbps.unwrap_or(6.0));
1022 }
1023 RiDeviceType::Network => {
1024 capabilities = capabilities
1025 .with_bandwidth_gbps(device_info.bandwidth_gbps.unwrap_or(1.0));
1026 }
1027 _ => {}
1028 }
1029
1030 device = device.with_capabilities(capabilities);
1031 device.set_status(RiDeviceStatus::Available);
1032 device.set_location(format!("Network Discovery: {}", device_info.source));
1033
1034 device
1035 }
1036
1037 pub async fn find_suitable_device(
1039 &self,
1040 device_type: &RiDeviceType,
1041 requirements: &RiDeviceCapabilities,
1042 ) -> RiResult<Option<RiDevice>> {
1043 let device_ids = match self.device_type_index.get(device_type) {
1044 Some(ids) => ids.clone(),
1045 None => return Ok(None),
1046 };
1047
1048 let mut best_device: Option<RiDevice> = None;
1050 let mut best_score = 0u32;
1051
1052 for device_id in device_ids {
1053 if let Some(device_lock) = self.devices.get(&device_id) {
1054 let device = device_lock.read().await;
1055
1056 if device.is_available() && device.capabilities().meets_requirements(requirements)
1057 {
1058 let score = self.calculate_device_score(&device);
1059
1060 if score > best_score || best_device.is_none() {
1061 best_device = Some(device.clone());
1062 best_score = score;
1063 }
1064 }
1065 }
1066 }
1067
1068 Ok(best_device)
1069 }
1070
1071 pub fn initialize_metrics(&mut self, metrics_registry: &RiMetricsRegistry) -> RiResult<()> {
1073 use crate::observability::{RiMetric, RiMetricConfig, RiMetricType};
1074 use std::sync::Arc;
1075
1076 let device_total_config = RiMetricConfig {
1078 metric_type: RiMetricType::Gauge,
1079 name: "dms_devices_total".to_string(),
1080 help: "Total number of discovered devices".to_string(),
1081 buckets: vec![],
1082 quantiles: vec![],
1083 max_age: std::time::Duration::from_secs(300),
1084 age_buckets: 5,
1085 };
1086 let device_total_metric = Arc::new(RiMetric::new(device_total_config));
1087 metrics_registry.register(device_total_metric.clone())?;
1088
1089 for device_type in self.device_type_index.keys() {
1091 let device_type_config = RiMetricConfig {
1092 metric_type: RiMetricType::Gauge,
1093 name: format!("dms_devices_{}_total", device_type.to_string().to_lowercase()),
1094 help: format!("Total number of {device_type} devices"),
1095 buckets: vec![],
1096 quantiles: vec![],
1097 max_age: std::time::Duration::from_secs(300),
1098 age_buckets: 5,
1099 };
1100 let device_type_metric = Arc::new(RiMetric::new(device_type_config));
1101 metrics_registry.register(device_type_metric.clone())?;
1102 }
1103
1104 Ok(())
1105 }
1106
1107 fn calculate_device_score(&self, device: &RiDevice) -> u32 {
1108 let mut score = device.health_score() as u32 * 100;
1109
1110 let capabilities = device.capabilities();
1112
1113 if let Some(compute_units) = capabilities.compute_units {
1114 score += compute_units as u32;
1115 }
1116
1117 if let Some(memory_gb) = capabilities.memory_gb {
1118 score += (memory_gb * 10.0) as u32;
1119 }
1120
1121 if let Some(storage_gb) = capabilities.storage_gb {
1122 score += (storage_gb * 5.0) as u32;
1123 }
1124
1125 if let Some(bandwidth_gbps) = capabilities.bandwidth_gbps {
1126 score += (bandwidth_gbps * 20.0) as u32;
1127 }
1128
1129 score
1130 }
1131
1132 pub async fn allocate_device(
1134 &mut self,
1135 device_id: &str,
1136 allocation_id: &str,
1137 ) -> RiResult<()> {
1138 if let Some(device_lock) = self.devices.get(device_id) {
1139 let mut device = device_lock.write().await;
1140
1141 if device.allocate(allocation_id) {
1142 self.allocation_map
1143 .insert(allocation_id.to_string(), device_id.to_string());
1144 Ok(())
1145 } else {
1146 Err(crate::core::RiError::DeviceAllocationFailed {
1147 device_id: device_id.to_string(),
1148 reason: "Device not available".to_string(),
1149 })
1150 }
1151 } else {
1152 Err(crate::core::RiError::DeviceNotFound {
1153 device_id: device_id.to_string(),
1154 })
1155 }
1156 }
1157
1158 pub async fn release_device_by_allocation(&mut self, allocation_id: &str) -> RiResult<()> {
1160 if let Some(device_id) = self.allocation_map.remove(allocation_id) {
1161 if let Some(device_lock) = self.devices.get(&device_id) {
1162 let mut device = device_lock.write().await;
1163 device.release();
1164 Ok(())
1165 } else {
1166 Err(crate::core::RiError::DeviceNotFound { device_id })
1167 }
1168 } else {
1169 Err(crate::core::RiError::AllocationNotFound {
1170 allocation_id: allocation_id.to_string(),
1171 })
1172 }
1173 }
1174
1175 pub async fn remove_device(&mut self, device_id: &str) -> RiResult<()> {
1177 if let Some(device_lock) = self.devices.remove(device_id) {
1178 let device = device_lock.read().await;
1179 let device_type = device.device_type();
1180
1181 if let Some(type_devices) = self.device_type_index.get_mut(&device_type) {
1183 type_devices.retain(|id| id != device_id);
1184 }
1185
1186 if let Some(allocation_id) = device.get_allocation_id() {
1188 self.allocation_map.remove(allocation_id);
1189 }
1190 }
1191
1192 Ok(())
1193 }
1194
1195 pub async fn get_device(&self, device_id: &str) -> Option<RiDevice> {
1197 if let Some(device_lock) = self.devices.get(device_id) {
1198 let device = device_lock.read().await;
1199 Some(device.clone())
1200 } else {
1201 None
1202 }
1203 }
1204
1205 pub fn get_all_devices(&self) -> Vec<RiDevice> {
1207 let mut devices = Vec::with_capacity(4);
1208
1209 for device_lock in self.devices.values() {
1211 if let Ok(device) = device_lock.try_read() {
1212 devices.push(device.clone());
1213 }
1214 }
1215
1216 devices
1217 }
1218
1219 pub fn release_all_devices(&mut self) -> RiResult<()> {
1221 self.allocation_map.clear();
1223
1224 for device_lock in self.devices.values() {
1226 if let Ok(mut device) = device_lock.try_write() {
1227 device.release();
1228 }
1229 }
1230
1231 Ok(())
1232 }
1233
1234 pub async fn perform_health_checks(&mut self) -> RiResult<Vec<(String, u8)>> {
1236 let mut results = Vec::with_capacity(4);
1237
1238 for (device_id, device_lock) in &self.devices {
1239 let mut device = device_lock.write().await;
1240
1241 let mut health_metrics = device.health_metrics().clone();
1243
1244 health_metrics.cpu_usage_percent = rand::random::<f64>() * 100.0;
1246 health_metrics.memory_usage_percent = rand::random::<f64>() * 100.0;
1247
1248 health_metrics.temperature_celsius = rand::random::<f64>() * 50.0 + 30.0;
1250
1251 if rand::random::<f64>() < 0.01 {
1253 health_metrics.error_count += 1;
1255 }
1256
1257 health_metrics.throughput = rand::random::<u64>() % 1000;
1259
1260 health_metrics.network_latency_ms = rand::random::<f64>() * 200.0;
1262
1263 health_metrics.disk_iops = (rand::random::<f64>() * 500.0) as u64;
1265
1266 health_metrics.battery_level_percent = rand::random::<f64>() * 100.0;
1268
1269 health_metrics.response_time_ms = rand::random::<f64>() * 150.0;
1271
1272 health_metrics.uptime_seconds += 30;
1274
1275 device.update_health_metrics(health_metrics);
1277
1278 let health_score = device.dynamic_health_score(device.health_metrics());
1280
1281 if health_score < 20 {
1283 device.set_status(RiDeviceStatus::Error);
1284 } else if health_score < 50 {
1285 device.set_status(RiDeviceStatus::Maintenance);
1286 } else if health_score < 70 {
1287 device.set_status(RiDeviceStatus::Degraded);
1288 } else if device.status() == RiDeviceStatus::Error
1289 || device.status() == RiDeviceStatus::Maintenance
1290 || device.status() == RiDeviceStatus::Degraded
1291 {
1292 device.set_status(RiDeviceStatus::Available);
1293 }
1294
1295 results.push((device_id.to_string(), health_score));
1296 }
1297
1298 Ok(results)
1299 }
1300
1301 pub async fn start_health_checks(&self, interval_secs: u64) -> tokio::task::JoinHandle<()> {
1303 let devices = self.devices.clone();
1304
1305 tokio::spawn(async move {
1306 let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(interval_secs));
1307
1308 loop {
1309 interval.tick().await;
1310
1311 for device_lock in devices.values() {
1312 let mut device = device_lock.write().await;
1313
1314 let mut health_metrics = device.health_metrics().clone();
1316
1317 health_metrics.cpu_usage_percent = rand::random::<f64>() * 100.0;
1319 health_metrics.memory_usage_percent = rand::random::<f64>() * 100.0;
1320
1321 health_metrics.temperature_celsius = rand::random::<f64>() * 50.0 + 30.0;
1323
1324 if rand::random::<f64>() < 0.01 {
1326 health_metrics.error_count += 1;
1328 }
1329
1330 health_metrics.throughput = rand::random::<u64>() % 1000;
1332
1333 health_metrics.network_latency_ms = rand::random::<f64>() * 200.0;
1335
1336 health_metrics.disk_iops = (rand::random::<f64>() * 500.0) as u64;
1338
1339 health_metrics.battery_level_percent = rand::random::<f64>() * 100.0;
1341
1342 health_metrics.response_time_ms = rand::random::<f64>() * 150.0;
1344
1345 health_metrics.uptime_seconds += interval_secs as u64;
1347
1348 device.update_health_metrics(health_metrics);
1350
1351 let health_score = device.dynamic_health_score(device.health_metrics());
1353
1354 if health_score < 20 {
1356 device.set_status(RiDeviceStatus::Error);
1357 } else if health_score < 50 {
1358 device.set_status(RiDeviceStatus::Maintenance);
1359 } else if health_score < 70 {
1360 device.set_status(RiDeviceStatus::Degraded);
1361 } else if device.status() == RiDeviceStatus::Error
1362 || device.status() == RiDeviceStatus::Maintenance
1363 || device.status() == RiDeviceStatus::Degraded
1364 {
1365 device.set_status(RiDeviceStatus::Available);
1366 }
1367 }
1368 }
1369 })
1370 }
1371
1372 pub async fn get_device_health(
1374 &self,
1375 device_id: &str,
1376 ) -> RiResult<super::core::RiDeviceHealthMetrics> {
1377 if let Some(device_lock) = self.devices.get(device_id) {
1378 let device = device_lock.read().await;
1379 Ok(device.health_metrics().clone())
1380 } else {
1381 Err(crate::core::RiError::DeviceNotFound {
1382 device_id: device_id.to_string(),
1383 })
1384 }
1385 }
1386
1387 pub async fn get_all_device_health(
1389 &self,
1390 ) -> RiResult<FxHashMap<String, super::core::RiDeviceHealthMetrics>> {
1391 let mut health_map = FxHashMap::default();
1392
1393 for (device_id, device_lock) in &self.devices {
1394 let device = device_lock.read().await;
1395 health_map.insert(device_id.to_string(), device.health_metrics().clone());
1396 }
1397
1398 Ok(health_map)
1399 }
1400
1401 fn create_mock_device_for_discovery(&self) -> RiDevice {
1403 use super::core::{RiDeviceCapabilities, RiDeviceType};
1404
1405 let device_types = [RiDeviceType::CPU,
1406 RiDeviceType::GPU,
1407 RiDeviceType::Memory,
1408 RiDeviceType::Storage,
1409 RiDeviceType::Network];
1410
1411 let device_type = device_types[rand::random::<usize>() % device_types.len()];
1412
1413 let device_name = match device_type {
1414 RiDeviceType::CPU => format!("CPU-{}-cores", rand::random::<usize>() % 32 + 1),
1415 RiDeviceType::GPU => format!("GPU-{}-GB", rand::random::<usize>() % 24 + 1),
1416 RiDeviceType::Memory => format!("Memory-{}-GB", rand::random::<usize>() % 64 + 1),
1417 RiDeviceType::Storage => format!("Storage-{}-TB", rand::random::<usize>() % 10 + 1),
1418 RiDeviceType::Network => format!("Network-{}-Gbps", rand::random::<usize>() % 100 + 1),
1419 RiDeviceType::Sensor => format!("Sensor-{}-units", rand::random::<usize>() % 100 + 1),
1420 RiDeviceType::Actuator => format!("Actuator-{}-actions", rand::random::<usize>() % 50 + 1),
1421 RiDeviceType::Custom => format!("Custom-{}-device", rand::random::<usize>() % 1000 + 1),
1422 };
1423
1424 let capabilities = RiDeviceCapabilities::new()
1425 .with_compute_units(rand::random::<usize>() % 1000 + 100)
1426 .with_memory_gb(rand::random::<f64>() * 64.0 + 1.0);
1427
1428 RiDevice::new(device_name, device_type)
1429 .with_capabilities(capabilities)
1430 }
1431}