1mod core;
117mod controller;
118pub mod scheduler;
119pub mod pool;
120pub mod discovery_scheduler;
121pub mod discovery;
122
123use std::sync::Arc;
124
125use serde::{Serialize, Deserialize};
126use tokio::sync::RwLock;
127use std::collections::HashMap as FxHashMap;
128
129use crate::observability::{RiMetricsRegistry, RiMetric, RiMetricConfig, RiMetricType};
130
131#[cfg(feature = "pyo3")]
132use pyo3::prelude::*;
133
134
135pub use core::{RiDevice, RiDeviceType, RiDeviceStatus, RiDeviceCapabilities, RiDeviceControlConfig, RiDeviceConfig, RiNetworkDeviceInfo, RiDeviceHealthMetrics};
136pub use controller::RiDeviceController;
137pub use pool::{RiResourcePool, RiResourcePoolManager, RiConnectionPoolStatistics, RiResourcePoolConfig};
138pub use scheduler::RiDeviceScheduler;
139pub use discovery_scheduler::{RiDeviceDiscoveryEngine, RiResourceScheduler};
140
141pub use discovery::{
143 RiDeviceDiscovery,
144 DiscoveryConfig,
145 DiscoveryStats,
146 DiscoveryStrategy,
147 HardwareCategory,
148 PlatformInfo,
149 PlatformType,
150 Architecture,
151 PlatformCompatibility,
152 ProviderRegistry,
153 RiHardwareProvider,
154 PluginRegistry,
155 RiHardwareDiscoveryPlugin,
156 PluginMetadata,
157 PluginStatus,
158 PluginError,
159 AsyncDiscovery,
160};
161
162use crate::core::{RiResult, RiServiceContext};
163
164
165#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
170pub struct RiDeviceControlModule {
171 controller: Arc<RwLock<RiDeviceController>>,
173 scheduler: Arc<RwLock<RiDeviceScheduler>>,
175 discovery_engine: Arc<RwLock<RiResourceScheduler>>,
177 resource_pools: FxHashMap<String, Arc<RiResourcePool>>,
179 pool_manager: Arc<RwLock<RiResourcePoolManager>>,
184 config: RiDeviceControlConfig,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
193#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
194pub struct RiDeviceSchedulingConfig {
195 pub discovery_enabled: bool,
197 pub discovery_interval_secs: u64,
199 pub auto_scheduling_enabled: bool,
201 pub max_concurrent_tasks: usize,
203 pub resource_allocation_timeout_secs: u64,
205}
206
207impl Default for RiDeviceSchedulingConfig {
208 fn default() -> Self {
209 Self {
210 discovery_enabled: true,
211 discovery_interval_secs: 30,
212 auto_scheduling_enabled: true,
213 max_concurrent_tasks: 100,
214 resource_allocation_timeout_secs: 60,
215 }
216 }
217}
218
219#[cfg(feature = "pyo3")]
220#[pymethods]
221impl RiDeviceSchedulingConfig {
222 #[new]
223 fn py_new() -> Self {
224 Self::default()
225 }
226
227 #[staticmethod]
228 fn default_config() -> Self {
229 Self::default()
230 }
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
238#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
239pub struct RiDiscoveryResult {
240 pub discovered_devices: Vec<RiDevice>,
242 pub updated_devices: Vec<RiDevice>,
244 pub removed_devices: Vec<String>, pub total_devices: usize,
248}
249
250#[cfg(feature = "pyo3")]
251#[pymethods]
252impl RiDiscoveryResult {
253 #[new]
254 fn py_new() -> Self {
255 Self {
256 discovered_devices: Vec::new(),
257 updated_devices: Vec::new(),
258 removed_devices: Vec::new(),
259 total_devices: 0,
260 }
261 }
262
263 #[staticmethod]
264 fn default_result() -> Self {
265 Self::default()
266 }
267
268 fn discovered_devices_impl(&self) -> Vec<RiDevice> {
269 self.discovered_devices.clone()
270 }
271
272 fn updated_devices_impl(&self) -> Vec<RiDevice> {
273 self.updated_devices.clone()
274 }
275
276 fn removed_devices_impl(&self) -> Vec<String> {
277 self.removed_devices.clone()
278 }
279
280 fn total_devices_impl(&self) -> usize {
281 self.total_devices
282 }
283
284 fn __str__(&self) -> String {
285 format!("RiDiscoveryResult(discovered: {}, updated: {}, removed: {}, total: {})",
286 self.discovered_devices.len(), self.updated_devices.len(),
287 self.removed_devices.len(), self.total_devices)
288 }
289}
290
291impl Default for RiDiscoveryResult {
292 fn default() -> Self {
293 Self {
294 discovered_devices: Vec::new(),
295 updated_devices: Vec::new(),
296 removed_devices: Vec::new(),
297 total_devices: 0,
298 }
299 }
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
308#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
309pub struct RiResourceRequest {
310 pub request_id: String,
312 pub device_type: RiDeviceType,
314 pub required_capabilities: RiDeviceCapabilities,
316 pub priority: u8, pub timeout_secs: u64,
320 pub sla_class: Option<RiRequestSlaClass>,
322 pub resource_weights: Option<RiResourceWeights>,
324 pub affinity: Option<RiAffinityRules>,
326 pub anti_affinity: Option<RiAffinityRules>,
328}
329
330#[cfg(feature = "pyo3")]
331#[pymethods]
332impl RiResourceRequest {
333 #[new]
334 #[pyo3(signature = (request_id, device_type, required_capabilities, priority=5, timeout_secs=60))]
335 fn py_new(request_id: String, device_type: RiDeviceType, required_capabilities: RiDeviceCapabilities, priority: u8, timeout_secs: u64) -> Self {
336 Self {
337 request_id,
338 device_type,
339 required_capabilities,
340 priority,
341 timeout_secs,
342 sla_class: None,
343 resource_weights: None,
344 affinity: None,
345 anti_affinity: None,
346 }
347 }
348
349 #[pyo3(name = "request_id")]
350 fn request_id_impl(&self) -> String {
351 self.request_id.clone()
352 }
353
354 #[pyo3(name = "device_type")]
355 fn device_type_impl(&self) -> RiDeviceType {
356 self.device_type
357 }
358
359 #[pyo3(name = "required_capabilities")]
360 fn required_capabilities_impl(&self) -> RiDeviceCapabilities {
361 self.required_capabilities.clone()
362 }
363
364 #[pyo3(name = "priority")]
365 fn priority_impl(&self) -> u8 {
366 self.priority
367 }
368
369 #[pyo3(name = "timeout_secs")]
370 fn timeout_secs_impl(&self) -> u64 {
371 self.timeout_secs
372 }
373
374 #[pyo3(name = "sla_class")]
375 fn sla_class_impl(&self) -> Option<RiRequestSlaClass> {
376 self.sla_class
377 }
378
379 #[pyo3(name = "resource_weights")]
380 fn resource_weights_impl(&self) -> Option<RiResourceWeights> {
381 self.resource_weights.clone()
382 }
383
384 #[pyo3(name = "affinity")]
385 fn affinity_impl(&self) -> Option<RiAffinityRules> {
386 self.affinity.clone()
387 }
388
389 #[pyo3(name = "anti_affinity")]
390 fn anti_affinity_impl(&self) -> Option<RiAffinityRules> {
391 self.anti_affinity.clone()
392 }
393
394 #[pyo3(name = "set_priority")]
395 fn set_priority_impl(&mut self, priority: u8) {
396 self.priority = priority;
397 }
398
399 #[pyo3(name = "set_timeout_secs")]
400 fn set_timeout_secs_impl(&mut self, timeout_secs: u64) {
401 self.timeout_secs = timeout_secs;
402 }
403
404 #[pyo3(name = "set_sla_class")]
405 fn set_sla_class_impl(&mut self, sla_class: Option<RiRequestSlaClass>) {
406 self.sla_class = sla_class;
407 }
408
409 #[pyo3(name = "set_resource_weights")]
410 fn set_resource_weights_impl(&mut self, resource_weights: Option<RiResourceWeights>) {
411 self.resource_weights = resource_weights;
412 }
413
414 #[pyo3(name = "set_affinity")]
415 fn set_affinity_impl(&mut self, affinity: Option<RiAffinityRules>) {
416 self.affinity = affinity;
417 }
418
419 #[pyo3(name = "set_anti_affinity")]
420 fn set_anti_affinity_impl(&mut self, anti_affinity: Option<RiAffinityRules>) {
421 self.anti_affinity = anti_affinity;
422 }
423
424 fn __str__(&self) -> String {
425 format!("RiResourceRequest(id: {}, type: {:?}, priority: {}, timeout: {}s)",
426 self.request_id, self.device_type, self.priority, self.timeout_secs)
427 }
428}
429
430#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
435#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
436pub enum RiRequestSlaClass {
437 Critical,
439 High,
441 Medium,
443 Low,
445}
446
447#[cfg(feature = "pyo3")]
448#[pymethods]
449impl RiRequestSlaClass {
450 fn __str__(&self) -> String {
451 match self {
452 RiRequestSlaClass::Critical => "Critical".to_string(),
453 RiRequestSlaClass::High => "High".to_string(),
454 RiRequestSlaClass::Medium => "Medium".to_string(),
455 RiRequestSlaClass::Low => "Low".to_string(),
456 }
457 }
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize)]
466#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
467pub struct RiResourceWeights {
468 pub compute_weight: f64,
470 pub memory_weight: f64,
472 pub storage_weight: f64,
474 pub bandwidth_weight: f64,
476}
477
478#[cfg(feature = "pyo3")]
479#[pymethods]
480impl RiResourceWeights {
481 #[new]
482 #[pyo3(signature = (compute_weight=1.0, memory_weight=1.0, storage_weight=1.0, bandwidth_weight=1.0))]
483 fn py_new(compute_weight: f64, memory_weight: f64, storage_weight: f64, bandwidth_weight: f64) -> Self {
484 Self {
485 compute_weight,
486 memory_weight,
487 storage_weight,
488 bandwidth_weight,
489 }
490 }
491
492 #[staticmethod]
493 fn default_weights() -> Self {
494 Self::default()
495 }
496
497 #[pyo3(name = "compute_weight")]
498 fn compute_weight_impl(&self) -> f64 { self.compute_weight }
499 #[pyo3(name = "memory_weight")]
500 fn memory_weight_impl(&self) -> f64 { self.memory_weight }
501 #[pyo3(name = "storage_weight")]
502 fn storage_weight_impl(&self) -> f64 { self.storage_weight }
503 #[pyo3(name = "bandwidth_weight")]
504 fn bandwidth_weight_impl(&self) -> f64 { self.bandwidth_weight }
505
506 #[pyo3(name = "set_compute_weight")]
507 fn set_compute_weight_impl(&mut self, weight: f64) { self.compute_weight = weight; }
508 #[pyo3(name = "set_memory_weight")]
509 fn set_memory_weight_impl(&mut self, weight: f64) { self.memory_weight = weight; }
510 #[pyo3(name = "set_storage_weight")]
511 fn set_storage_weight_impl(&mut self, weight: f64) { self.storage_weight = weight; }
512 #[pyo3(name = "set_bandwidth_weight")]
513 fn set_bandwidth_weight_impl(&mut self, weight: f64) { self.bandwidth_weight = weight; }
514
515 fn __str__(&self) -> String {
516 format!("RiResourceWeights(compute: {}, memory: {}, storage: {}, bandwidth: {})",
517 self.compute_weight, self.memory_weight, self.storage_weight, self.bandwidth_weight)
518 }
519}
520
521impl Default for RiResourceWeights {
522 fn default() -> Self {
523 Self {
524 compute_weight: 1.0,
525 memory_weight: 1.0,
526 storage_weight: 1.0,
527 bandwidth_weight: 1.0,
528 }
529 }
530}
531
532#[derive(Debug, Clone, Serialize, Deserialize)]
537#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
538pub struct RiAffinityRules {
539 pub required_labels: FxHashMap<String, String>,
541 pub preferred_labels: FxHashMap<String, String>,
543 pub forbidden_labels: FxHashMap<String, String>,
545}
546
547#[cfg(feature = "pyo3")]
548#[pymethods]
549impl RiAffinityRules {
550 #[new]
551 fn py_new() -> Self {
552 Self {
553 required_labels: FxHashMap::default(),
554 preferred_labels: FxHashMap::default(),
555 forbidden_labels: FxHashMap::default(),
556 }
557 }
558
559 #[staticmethod]
560 fn default_rules() -> Self {
561 Self::default()
562 }
563
564 #[pyo3(name = "required_labels")]
565 fn required_labels_impl(&self) -> FxHashMap<String, String> {
566 self.required_labels.clone()
567 }
568
569 #[pyo3(name = "preferred_labels")]
570 fn preferred_labels_impl(&self) -> FxHashMap<String, String> {
571 self.preferred_labels.clone()
572 }
573
574 #[pyo3(name = "forbidden_labels")]
575 fn forbidden_labels_impl(&self) -> FxHashMap<String, String> {
576 self.forbidden_labels.clone()
577 }
578
579 fn __str__(&self) -> String {
580 format!("RiAffinityRules(required: {}, preferred: {}, forbidden: {})",
581 self.required_labels.len(), self.preferred_labels.len(), self.forbidden_labels.len())
582 }
583}
584
585impl Default for RiAffinityRules {
586 fn default() -> Self {
587 Self {
588 required_labels: FxHashMap::default(),
589 preferred_labels: FxHashMap::default(),
590 forbidden_labels: FxHashMap::default(),
591 }
592 }
593}
594
595#[derive(Debug, Clone, Serialize, Deserialize)]
600#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
601pub struct RiResourceAllocation {
602 pub allocation_id: String,
604 pub device_id: String,
606 pub device_name: String,
608 pub allocated_at: chrono::DateTime<chrono::Utc>,
610 pub expires_at: chrono::DateTime<chrono::Utc>,
612 pub request: RiResourceRequest,
614}
615
616#[cfg(feature = "pyo3")]
617#[pymethods]
618impl RiResourceAllocation {
619 #[new]
620 fn py_new(allocation_id: String, device_id: String, device_name: String, request: RiResourceRequest) -> Self {
621 let now = chrono::Utc::now();
622 let expires_at = now + chrono::TimeDelta::seconds(request.timeout_secs as i64);
623
624 Self {
625 allocation_id,
626 device_id,
627 device_name,
628 allocated_at: now,
629 expires_at,
630 request,
631 }
632 }
633
634 #[pyo3(name = "allocation_id")]
635 fn allocation_id_impl(&self) -> String {
636 self.allocation_id.clone()
637 }
638
639 #[pyo3(name = "device_id")]
640 fn device_id_impl(&self) -> String {
641 self.device_id.clone()
642 }
643
644 #[pyo3(name = "device_name")]
645 fn device_name_impl(&self) -> String {
646 self.device_name.clone()
647 }
648
649 #[pyo3(name = "allocated_at")]
650 fn allocated_at_impl(&self) -> String {
651 self.allocated_at.to_rfc3339()
652 }
653
654 #[pyo3(name = "expires_at")]
655 fn expires_at_impl(&self) -> String {
656 self.expires_at.to_rfc3339()
657 }
658
659 #[pyo3(name = "request")]
660 fn request_impl(&self) -> RiResourceRequest {
661 self.request.clone()
662 }
663
664 #[pyo3(name = "is_expired")]
665 fn is_expired_impl(&self) -> bool {
666 chrono::Utc::now() > self.expires_at
667 }
668
669 #[pyo3(name = "remaining_time")]
670 fn remaining_time_impl(&self) -> i64 {
671 (self.expires_at - chrono::Utc::now()).num_seconds()
672 }
673
674 fn __str__(&self) -> String {
675 format!("RiResourceAllocation(id: {}, device: {} ({}), expires: {})",
676 self.allocation_id, self.device_name, self.device_id, self.expires_at)
677 }
678}
679
680impl Default for RiDeviceControlModule {
681 fn default() -> Self {
682 Self::new()
683 }
684}
685
686impl RiDeviceControlModule {
687 pub fn new() -> Self {
693 let controller = Arc::new(RwLock::new(RiDeviceController::new()));
694 let resource_pool_manager = Arc::new(RwLock::new(RiResourcePoolManager::new()));
695 let scheduler = Arc::new(RwLock::new(RiDeviceScheduler::new(resource_pool_manager.clone())));
696 let discovery_engine = Arc::new(RwLock::new(RiResourceScheduler::new()));
697
698 Self {
699 controller,
700 scheduler,
701 discovery_engine,
702 resource_pools: FxHashMap::default(),
703 pool_manager: resource_pool_manager,
704 config: crate::device::core::RiDeviceControlConfig::default(),
705 }
706 }
707
708 pub fn with_config(mut self, config: crate::device::core::RiDeviceControlConfig) -> Self {
718 self.config = config;
719 self
720 }
721
722 pub async fn discover_devices(&self) -> RiResult<RiDiscoveryResult> {
731 if !self.config.enable_cpu_discovery && !self.config.enable_gpu_discovery &&
732 !self.config.enable_memory_discovery && !self.config.enable_storage_discovery &&
733 !self.config.enable_network_discovery {
734 return Ok(RiDiscoveryResult {
735 discovered_devices: vec![],
736 updated_devices: vec![],
737 removed_devices: vec![],
738 total_devices: 0,
739 });
740 }
741
742 let result = {
743 let mut controller = self.controller.write().await;
744 controller.discover_devices().await
745 };
746
747 if result.is_ok() {
755 let all_devices = {
756 let controller = self.controller.read().await;
757 controller.get_all_devices()
758 };
759
760 if !all_devices.is_empty() {
761 let mut by_type: FxHashMap<RiDeviceType, Vec<RiDevice>> = FxHashMap::default();
762 for mut device in all_devices {
763 if device.status() == crate::device::core::RiDeviceStatus::Unknown {
764 device.set_status(crate::device::core::RiDeviceStatus::Available);
765 }
766 by_type.entry(device.device_type()).or_default().push(device);
767 }
768
769 let mut manager = self.pool_manager.write().await;
770 for (device_type, devices) in by_type {
771 let pool_name = format!("{:?}_pool", device_type).to_lowercase();
772 manager.remove_pool(&pool_name);
774 let mut pool = RiResourcePool::new(RiResourcePoolConfig {
775 name: pool_name,
776 device_type,
777 ..Default::default()
778 });
779 for device in devices {
780 pool.add_device(Arc::new(device));
781 }
782 manager.register_pool(pool);
783 }
784 }
785 }
786
787 result
788 }
789
790 pub async fn allocate_resource(&self, request: RiResourceRequest) -> RiResult<Option<RiResourceAllocation>> {
804 let scheduling_enabled = match request.device_type {
806 RiDeviceType::CPU => self.config.enable_cpu_discovery,
807 RiDeviceType::GPU => self.config.enable_gpu_discovery,
808 RiDeviceType::Memory => self.config.enable_memory_discovery,
809 RiDeviceType::Storage => self.config.enable_storage_discovery,
810 RiDeviceType::Network => self.config.enable_network_discovery,
811 _ => true, };
813
814 if !scheduling_enabled {
815 return Ok(None);
816 }
817
818 let allocation_request = crate::device::scheduler::RiAllocationRequest {
819 device_type: request.device_type,
820 capabilities: request.required_capabilities,
821 priority: request.priority as u32,
822 timeout_secs: request.timeout_secs,
823 sla_class: request.sla_class,
824 resource_weights: request.resource_weights,
825 affinity: request.affinity,
826 anti_affinity: request.anti_affinity,
827 };
828
829 let scheduler = self.scheduler.write().await;
830 let device = scheduler.select_device(&allocation_request).await;
831
832 if let Some(device) = device {
833 let allocation = RiResourceAllocation {
834 allocation_id: uuid::Uuid::new_v4().to_string(),
835 device_id: device.id().to_string(),
836 device_name: device.name().to_string(),
837 allocated_at: chrono::Utc::now(),
838 expires_at: chrono::Utc::now() + chrono::Duration::seconds(allocation_request.timeout_secs as i64),
839 request: RiResourceRequest {
840 request_id: request.request_id,
841 device_type: allocation_request.device_type,
842 required_capabilities: allocation_request.capabilities,
843 priority: request.priority,
844 timeout_secs: allocation_request.timeout_secs,
845 sla_class: allocation_request.sla_class,
846 resource_weights: allocation_request.resource_weights,
847 affinity: allocation_request.affinity,
848 anti_affinity: allocation_request.anti_affinity,
849 },
850 };
851
852 let mut controller = self.controller.write().await;
854 controller.allocate_device(&allocation.device_id, &allocation.allocation_id).await?;
855
856 Ok(Some(allocation))
857 } else {
858 Ok(None)
859 }
860 }
861
862 pub async fn release_resource(&self, allocation_id: &str) -> RiResult<()> {
874 let mut controller = self.controller.write().await;
875 controller.release_device_by_allocation(allocation_id).await
876 }
877
878 pub async fn get_device_status(&self) -> RiResult<Vec<RiDevice>> {
886 let controller = self.controller.read().await;
887 Ok(controller.get_all_devices())
888 }
889
890 pub fn get_resource_pool_status(&self) -> FxHashMap<String, RiResourcePoolStatus> {
898 let mut status = FxHashMap::default();
899 for (pool_name, pool) in &self.resource_pools {
900 status.insert(pool_name.clone(), pool.get_status());
901 }
902 status
903 }
904
905 #[allow(dead_code)]
924 fn create_device_metrics(&self, registry: Arc<RiMetricsRegistry>) -> RiResult<()> {
925 let device_total_config = RiMetricConfig {
927 metric_type: RiMetricType::Gauge,
928 name: "dms_device_total".to_string(),
929 help: "Total number of devices by type and status".to_string(),
930 buckets: vec![],
931 quantiles: vec![],
932 max_age: std::time::Duration::from_secs(300),
933 age_buckets: 5,
934 };
935 let device_total_metric = Arc::new(RiMetric::new(device_total_config));
936 registry.register(device_total_metric)?;
937
938 let allocation_attempts_config = RiMetricConfig {
940 metric_type: RiMetricType::Counter,
941 name: "dms_device_allocation_attempts_total".to_string(),
942 help: "Total number of device allocation attempts".to_string(),
943 buckets: vec![],
944 quantiles: vec![],
945 max_age: std::time::Duration::from_secs(0),
946 age_buckets: 0,
947 };
948 let allocation_attempts_metric = Arc::new(RiMetric::new(allocation_attempts_config));
949 registry.register(allocation_attempts_metric)?;
950
951 let allocation_success_config = RiMetricConfig {
953 metric_type: RiMetricType::Counter,
954 name: "dms_device_allocation_success_total".to_string(),
955 help: "Total number of successful device allocations".to_string(),
956 buckets: vec![],
957 quantiles: vec![],
958 max_age: std::time::Duration::from_secs(0),
959 age_buckets: 0,
960 };
961 let allocation_success_metric = Arc::new(RiMetric::new(allocation_success_config));
962 registry.register(allocation_success_metric)?;
963
964 let allocation_failure_config = RiMetricConfig {
966 metric_type: RiMetricType::Counter,
967 name: "dms_device_allocation_failure_total".to_string(),
968 help: "Total number of failed device allocations".to_string(),
969 buckets: vec![],
970 quantiles: vec![],
971 max_age: std::time::Duration::from_secs(0),
972 age_buckets: 0,
973 };
974 let allocation_failure_metric = Arc::new(RiMetric::new(allocation_failure_config));
975 registry.register(allocation_failure_metric)?;
976
977 let discovery_attempts_config = RiMetricConfig {
979 metric_type: RiMetricType::Counter,
980 name: "dms_device_discovery_attempts_total".to_string(),
981 help: "Total number of device discovery attempts".to_string(),
982 buckets: vec![],
983 quantiles: vec![],
984 max_age: std::time::Duration::from_secs(0),
985 age_buckets: 0,
986 };
987 let discovery_attempts_metric = Arc::new(RiMetric::new(discovery_attempts_config));
988 registry.register(discovery_attempts_metric)?;
989
990 let discovery_success_config = RiMetricConfig {
992 metric_type: RiMetricType::Counter,
993 name: "dms_device_discovery_success_total".to_string(),
994 help: "Total number of successful device discoveries".to_string(),
995 buckets: vec![],
996 quantiles: vec![],
997 max_age: std::time::Duration::from_secs(0),
998 age_buckets: 0,
999 };
1000 let discovery_success_metric = Arc::new(RiMetric::new(discovery_success_config));
1001 registry.register(discovery_success_metric)?;
1002
1003 let resource_utilization_config = RiMetricConfig {
1005 metric_type: RiMetricType::Gauge,
1006 name: "dms_device_resource_utilization".to_string(),
1007 help: "Resource utilization by device type".to_string(),
1008 buckets: vec![],
1009 quantiles: vec![],
1010 max_age: std::time::Duration::from_secs(300),
1011 age_buckets: 5,
1012 };
1013 let resource_utilization_metric = Arc::new(RiMetric::new(resource_utilization_config));
1014 registry.register(resource_utilization_metric)?;
1015
1016 Ok(())
1017 }
1018}
1019
1020#[cfg(feature = "pyo3")]
1021#[pyo3::prelude::pymethods]
1022impl RiDeviceControlModule {
1023 fn get_config(&self) -> String {
1024 format!("{:?}", self.config)
1025 }
1026}
1027
1028#[derive(Debug, Clone, Serialize, Deserialize)]
1033#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(get_all, set_all))]
1034pub struct RiResourcePoolStatus {
1035 pub total_capacity: usize,
1037 pub available_capacity: usize,
1039 pub allocated_capacity: usize,
1041 pub pending_requests: usize,
1043 pub utilization_rate: f64,
1045}
1046
1047#[cfg(feature = "pyo3")]
1048#[pyo3::prelude::pymethods]
1049impl RiResourcePoolStatus {
1050 #[new]
1051 #[pyo3(signature = (total_capacity=0, available_capacity=0, allocated_capacity=0, pending_requests=0, utilization_rate=0.0))]
1052 fn py_new(total_capacity: usize, available_capacity: usize, allocated_capacity: usize, pending_requests: usize, utilization_rate: f64) -> Self {
1053 Self {
1054 total_capacity,
1055 available_capacity,
1056 allocated_capacity,
1057 pending_requests,
1058 utilization_rate,
1059 }
1060 }
1061
1062 #[staticmethod]
1063 fn default_status() -> Self {
1064 Self::default()
1065 }
1066
1067 #[pyo3(name = "total_capacity")]
1068 fn total_capacity_impl(&self) -> usize {
1069 self.total_capacity
1070 }
1071
1072 #[pyo3(name = "available_capacity")]
1073 fn available_capacity_impl(&self) -> usize {
1074 self.available_capacity
1075 }
1076
1077 #[pyo3(name = "allocated_capacity")]
1078 fn allocated_capacity_impl(&self) -> usize {
1079 self.allocated_capacity
1080 }
1081
1082 #[pyo3(name = "pending_requests")]
1083 fn pending_requests_impl(&self) -> usize {
1084 self.pending_requests
1085 }
1086
1087 #[pyo3(name = "utilization_rate")]
1088 fn utilization_rate_impl(&self) -> f64 {
1089 self.utilization_rate
1090 }
1091
1092 fn __str__(&self) -> String {
1093 format!("RiResourcePoolStatus(total: {}, available: {}, allocated: {}, utilization: {:.2}%)",
1094 self.total_capacity, self.available_capacity, self.allocated_capacity, self.utilization_rate)
1095 }
1096}
1097
1098impl Default for RiResourcePoolStatus {
1099 fn default() -> Self {
1100 Self {
1101 total_capacity: 0,
1102 available_capacity: 0,
1103 allocated_capacity: 0,
1104 pending_requests: 0,
1105 utilization_rate: 0.0,
1106 }
1107 }
1108}
1109
1110#[async_trait::async_trait]
1111impl crate::core::RiModule for RiDeviceControlModule {
1112 fn name(&self) -> &str {
1118 "Ri.DeviceControl"
1119 }
1120
1121 fn is_critical(&self) -> bool {
1131 false }
1133
1134 async fn init(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
1141 let binding = ctx.config();
1142 let cfg = binding.config();
1143 let device_config = parse_device_config(cfg.get("device"));
1144
1145 let mut controller = self.controller.write().await;
1146 controller.discover_system_devices(&device_config).await?;
1147 drop(controller);
1148
1149 let discovery_engine = RiResourceScheduler::new();
1150
1151 let mut discovery_guard = self.discovery_engine.write().await;
1152 *discovery_guard = discovery_engine;
1153
1154 if let Some(metrics_registry) = ctx.metrics_registry() {
1155 let mut controller = self.controller.write().await;
1156 controller.initialize_metrics(&metrics_registry)?;
1157 drop(controller);
1158 }
1159
1160 let logger = ctx.logger();
1161 logger.info("Ri.DeviceControl", "Device control module initialized with real hardware discovery")?;
1162 Ok(())
1163 }
1164}
1165
1166impl crate::core::ServiceModule for RiDeviceControlModule {
1167 fn name(&self) -> &str {
1168 "Ri.DeviceControl"
1169 }
1170
1171 fn is_critical(&self) -> bool {
1172 false
1173 }
1174
1175 fn priority(&self) -> i32 {
1176 25
1177 }
1178
1179 fn dependencies(&self) -> Vec<&str> {
1180 vec![]
1181 }
1182
1183 fn init(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
1184 Ok(())
1185 }
1186
1187 fn start(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
1188 Ok(())
1189 }
1190
1191 fn shutdown(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
1192 Ok(())
1193 }
1194}
1195
1196fn parse_device_config(config_str: Option<&String>) -> crate::device::core::RiDeviceControlConfig {
1197 match config_str {
1198 Some(config) => {
1199 let trimmed = config.trim();
1200 if trimmed.starts_with('{') {
1201 if let Ok(result) = serde_json::from_str::<crate::device::core::RiDeviceControlConfig>(trimmed) {
1202 return result;
1203 }
1204 }
1205 if trimmed.starts_with("---") || trimmed.contains("discovery_enabled:") {
1206 if let Ok(result) = serde_yaml::from_str::<crate::device::core::RiDeviceControlConfig>(trimmed) {
1207 return result;
1208 }
1209 }
1210 if trimmed.contains('[') || trimmed.contains("discovery_enabled") {
1211 if let Ok(result) = toml::from_str::<crate::device::core::RiDeviceControlConfig>(trimmed) {
1212 return result;
1213 }
1214 }
1215 serde_json::from_str::<crate::device::core::RiDeviceControlConfig>(trimmed)
1216 .unwrap_or_default()
1217 }
1218 None => crate::device::core::RiDeviceControlConfig::default(),
1219 }
1220}