1use ndarray::{Array1, Array2};
9use num_complex::Complex64;
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, VecDeque};
12use std::fs::File;
13use std::io::Write;
14use std::sync::{Arc, Mutex, RwLock};
15use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
16
17use crate::circuit_interfaces::{InterfaceCircuit, InterfaceGate, InterfaceGateType};
18use crate::debugger::PerformanceMetrics;
19use crate::error::{Result, SimulatorError};
20
21#[derive(Debug, Clone)]
23pub struct TelemetryConfig {
24 pub enabled: bool,
26 pub sampling_rate: f64,
28 pub max_history_size: usize,
30 pub export_interval: Duration,
32 pub enable_alerts: bool,
34 pub alert_thresholds: AlertThresholds,
36 pub export_format: TelemetryExportFormat,
38 pub export_directory: String,
40 pub monitor_system_resources: bool,
42 pub custom_tags: HashMap<String, String>,
44}
45
46impl Default for TelemetryConfig {
47 fn default() -> Self {
48 Self {
49 enabled: true,
50 sampling_rate: 1.0,
51 max_history_size: 10000,
52 export_interval: Duration::from_secs(60),
53 enable_alerts: true,
54 alert_thresholds: AlertThresholds::default(),
55 export_format: TelemetryExportFormat::JSON,
56 export_directory: "./telemetry".to_string(),
57 monitor_system_resources: true,
58 custom_tags: HashMap::new(),
59 }
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct AlertThresholds {
66 pub max_gate_execution_time: f64,
68 pub max_memory_usage: usize,
70 pub max_error_rate: f64,
72 pub max_cpu_usage: f64,
74 pub max_queue_depth: usize,
76}
77
78impl Default for AlertThresholds {
79 fn default() -> Self {
80 Self {
81 max_gate_execution_time: 1.0,
82 max_memory_usage: 16_000_000_000, max_error_rate: 0.1,
84 max_cpu_usage: 0.9,
85 max_queue_depth: 1000,
86 }
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum TelemetryExportFormat {
93 JSON,
94 CSV,
95 Prometheus,
96 InfluxDB,
97 Custom,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub enum TelemetryMetric {
103 Counter {
105 name: String,
106 value: u64,
107 tags: HashMap<String, String>,
108 timestamp: f64,
109 },
110 Gauge {
112 name: String,
113 value: f64,
114 tags: HashMap<String, String>,
115 timestamp: f64,
116 },
117 Histogram {
119 name: String,
120 values: Vec<f64>,
121 buckets: Vec<f64>,
122 tags: HashMap<String, String>,
123 timestamp: f64,
124 },
125 Timer {
127 name: String,
128 duration: Duration,
129 tags: HashMap<String, String>,
130 timestamp: f64,
131 },
132 Custom {
134 name: String,
135 data: serde_json::Value,
136 tags: HashMap<String, String>,
137 timestamp: f64,
138 },
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct PerformanceSnapshot {
144 pub timestamp: f64,
146 pub cpu_usage: f64,
148 pub memory_usage: usize,
150 pub available_memory: usize,
152 pub network_io: NetworkIOStats,
154 pub disk_io: DiskIOStats,
156 pub gpu_utilization: Option<f64>,
158 pub gpu_memory_usage: Option<usize>,
160}
161
162#[derive(Debug, Clone, Default, Serialize, Deserialize)]
164pub struct NetworkIOStats {
165 pub bytes_sent_per_sec: f64,
167 pub bytes_received_per_sec: f64,
169 pub packets_sent_per_sec: f64,
171 pub packets_received_per_sec: f64,
173}
174
175#[derive(Debug, Clone, Default, Serialize, Deserialize)]
177pub struct DiskIOStats {
178 pub bytes_read_per_sec: f64,
180 pub bytes_written_per_sec: f64,
182 pub read_ops_per_sec: f64,
184 pub write_ops_per_sec: f64,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct QuantumMetrics {
191 pub num_qubits: usize,
193 pub circuit_depth: usize,
195 pub gate_execution_rate: f64,
197 pub entanglement_entropy: f64,
199 pub error_correction_rate: f64,
201 pub fidelity: f64,
203 pub active_backends: Vec<String>,
205 pub queue_depth: usize,
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
211pub enum AlertLevel {
212 Info,
213 Warning,
214 Error,
215 Critical,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct Alert {
221 pub level: AlertLevel,
223 pub message: String,
225 pub metric_name: String,
227 pub current_value: f64,
229 pub threshold_value: f64,
231 pub timestamp: f64,
233 pub context: HashMap<String, String>,
235}
236
237pub struct TelemetryCollector {
239 config: TelemetryConfig,
241 metrics_history: Arc<RwLock<VecDeque<TelemetryMetric>>>,
243 performance_history: Arc<RwLock<VecDeque<PerformanceSnapshot>>>,
245 quantum_metrics_history: Arc<RwLock<VecDeque<QuantumMetrics>>>,
247 active_alerts: Arc<RwLock<Vec<Alert>>>,
249 system_monitor_handle: Option<std::thread::JoinHandle<()>>,
251 last_export: Arc<Mutex<Instant>>,
253 custom_handlers: HashMap<String, Box<dyn Fn(&TelemetryMetric) + Send + Sync>>,
255}
256
257impl TelemetryCollector {
258 pub fn new(config: TelemetryConfig) -> Self {
260 let collector = Self {
261 config: config.clone(),
262 metrics_history: Arc::new(RwLock::new(VecDeque::with_capacity(
263 config.max_history_size,
264 ))),
265 performance_history: Arc::new(RwLock::new(VecDeque::with_capacity(1000))),
266 quantum_metrics_history: Arc::new(RwLock::new(VecDeque::with_capacity(1000))),
267 active_alerts: Arc::new(RwLock::new(Vec::new())),
268 system_monitor_handle: None,
269 last_export: Arc::new(Mutex::new(Instant::now())),
270 custom_handlers: HashMap::new(),
271 };
272
273 collector
274 }
275
276 pub fn start(&mut self) -> Result<()> {
278 if !self.config.enabled {
279 return Ok(());
280 }
281
282 if self.config.monitor_system_resources {
284 self.start_system_monitoring()?;
285 }
286
287 Ok(())
288 }
289
290 pub fn stop(&mut self) {
292 if let Some(handle) = self.system_monitor_handle.take() {
293 let _ = handle.join();
296 }
297 }
298
299 pub fn record_metric(&self, metric: TelemetryMetric) -> Result<()> {
301 if !self.config.enabled {
302 return Ok(());
303 }
304
305 if fastrand::f64() > self.config.sampling_rate {
307 return Ok(());
308 }
309
310 {
312 let mut history = self.metrics_history.write().unwrap();
313 history.push_back(metric.clone());
314 if history.len() > self.config.max_history_size {
315 history.pop_front();
316 }
317 }
318
319 self.check_alert_conditions(&metric)?;
321
322 for handler in self.custom_handlers.values() {
324 handler(&metric);
325 }
326
327 self.check_export_schedule()?;
329
330 Ok(())
331 }
332
333 pub fn record_quantum_metrics(&self, metrics: QuantumMetrics) -> Result<()> {
335 if !self.config.enabled {
336 return Ok(());
337 }
338
339 {
340 let mut history = self.quantum_metrics_history.write().unwrap();
341 history.push_back(metrics.clone());
342 if history.len() > 1000 {
343 history.pop_front();
344 }
345 }
346
347 let timestamp = SystemTime::now()
349 .duration_since(UNIX_EPOCH)
350 .unwrap()
351 .as_secs_f64();
352
353 let quantum_gauge = TelemetryMetric::Gauge {
354 name: "quantum.num_qubits".to_string(),
355 value: metrics.num_qubits as f64,
356 tags: self.config.custom_tags.clone(),
357 timestamp,
358 };
359 self.record_metric(quantum_gauge)?;
360
361 let rate_gauge = TelemetryMetric::Gauge {
362 name: "quantum.gate_execution_rate".to_string(),
363 value: metrics.gate_execution_rate,
364 tags: self.config.custom_tags.clone(),
365 timestamp,
366 };
367 self.record_metric(rate_gauge)?;
368
369 let entropy_gauge = TelemetryMetric::Gauge {
370 name: "quantum.entanglement_entropy".to_string(),
371 value: metrics.entanglement_entropy,
372 tags: self.config.custom_tags.clone(),
373 timestamp,
374 };
375 self.record_metric(entropy_gauge)?;
376
377 Ok(())
378 }
379
380 pub fn record_gate_execution(&self, gate: &InterfaceGate, duration: Duration) -> Result<()> {
382 let gate_type = format!("{:?}", gate.gate_type);
383 let mut tags = self.config.custom_tags.clone();
384 tags.insert("gate_type".to_string(), gate_type);
385 tags.insert("num_qubits".to_string(), gate.qubits.len().to_string());
386
387 let timer = TelemetryMetric::Timer {
388 name: "gate.execution_time".to_string(),
389 duration,
390 tags,
391 timestamp: SystemTime::now()
392 .duration_since(UNIX_EPOCH)
393 .unwrap()
394 .as_secs_f64(),
395 };
396
397 self.record_metric(timer)?;
398 Ok(())
399 }
400
401 pub fn record_circuit_execution(
403 &self,
404 circuit: &InterfaceCircuit,
405 duration: Duration,
406 ) -> Result<()> {
407 let mut tags = self.config.custom_tags.clone();
408 tags.insert("num_qubits".to_string(), circuit.num_qubits.to_string());
409 tags.insert("num_gates".to_string(), circuit.gates.len().to_string());
410
411 let timer = TelemetryMetric::Timer {
412 name: "circuit.execution_time".to_string(),
413 duration,
414 tags: tags.clone(),
415 timestamp: SystemTime::now()
416 .duration_since(UNIX_EPOCH)
417 .unwrap()
418 .as_secs_f64(),
419 };
420
421 self.record_metric(timer)?;
422
423 let gate_counter = TelemetryMetric::Counter {
425 name: "circuit.gates_executed".to_string(),
426 value: circuit.gates.len() as u64,
427 tags,
428 timestamp: SystemTime::now()
429 .duration_since(UNIX_EPOCH)
430 .unwrap()
431 .as_secs_f64(),
432 };
433
434 self.record_metric(gate_counter)?;
435 Ok(())
436 }
437
438 pub fn record_memory_usage(&self, bytes_used: usize, category: &str) -> Result<()> {
440 let mut tags = self.config.custom_tags.clone();
441 tags.insert("category".to_string(), category.to_string());
442
443 let gauge = TelemetryMetric::Gauge {
444 name: "memory.usage_bytes".to_string(),
445 value: bytes_used as f64,
446 tags,
447 timestamp: SystemTime::now()
448 .duration_since(UNIX_EPOCH)
449 .unwrap()
450 .as_secs_f64(),
451 };
452
453 self.record_metric(gauge)?;
454 Ok(())
455 }
456
457 pub fn record_error(&self, error_type: &str, error_message: &str) -> Result<()> {
459 let mut tags = self.config.custom_tags.clone();
460 tags.insert("error_type".to_string(), error_type.to_string());
461 tags.insert("error_message".to_string(), error_message.to_string());
462
463 let counter = TelemetryMetric::Counter {
464 name: "errors.total".to_string(),
465 value: 1,
466 tags,
467 timestamp: SystemTime::now()
468 .duration_since(UNIX_EPOCH)
469 .unwrap()
470 .as_secs_f64(),
471 };
472
473 self.record_metric(counter)?;
474 Ok(())
475 }
476
477 pub fn get_metrics_summary(&self) -> Result<MetricsSummary> {
479 let metrics_history = self.metrics_history.read().unwrap();
480 let quantum_history = self.quantum_metrics_history.read().unwrap();
481 let performance_history = self.performance_history.read().unwrap();
482
483 let total_metrics = metrics_history.len();
484 let total_quantum_metrics = quantum_history.len();
485 let total_performance_snapshots = performance_history.len();
486
487 let mut gate_times = Vec::new();
489 for metric in metrics_history.iter() {
490 if let TelemetryMetric::Timer { name, duration, .. } = metric {
491 if name == "gate.execution_time" {
492 gate_times.push(duration.as_secs_f64());
493 }
494 }
495 }
496
497 let avg_gate_time = if gate_times.is_empty() {
498 0.0
499 } else {
500 gate_times.iter().sum::<f64>() / gate_times.len() as f64
501 };
502
503 let latest_quantum_metrics = quantum_history.back().cloned();
505
506 let latest_performance = performance_history.back().cloned();
508
509 Ok(MetricsSummary {
510 total_metrics,
511 total_quantum_metrics,
512 total_performance_snapshots,
513 avg_gate_execution_time: avg_gate_time,
514 latest_quantum_metrics,
515 latest_performance,
516 active_alerts_count: self.active_alerts.read().unwrap().len(),
517 })
518 }
519
520 pub fn export_data(&self, path: &str) -> Result<()> {
522 std::fs::create_dir_all(path).map_err(|e| {
523 SimulatorError::InvalidInput(format!("Failed to create export directory: {}", e))
524 })?;
525
526 match self.config.export_format {
527 TelemetryExportFormat::JSON => self.export_json(path)?,
528 TelemetryExportFormat::CSV => self.export_csv(path)?,
529 TelemetryExportFormat::Prometheus => self.export_prometheus(path)?,
530 TelemetryExportFormat::InfluxDB => self.export_influxdb(path)?,
531 TelemetryExportFormat::Custom => self.export_custom(path)?,
532 }
533
534 *self.last_export.lock().unwrap() = Instant::now();
535 Ok(())
536 }
537
538 fn start_system_monitoring(&mut self) -> Result<()> {
540 let performance_history = Arc::clone(&self.performance_history);
541 let config = self.config.clone();
542
543 let handle = std::thread::spawn(move || loop {
544 let snapshot = Self::collect_system_metrics();
545
546 {
547 let mut history = performance_history.write().unwrap();
548 history.push_back(snapshot);
549 if history.len() > 1000 {
550 history.pop_front();
551 }
552 }
553
554 std::thread::sleep(Duration::from_secs(1));
555 });
556
557 self.system_monitor_handle = Some(handle);
558 Ok(())
559 }
560
561 fn collect_system_metrics() -> PerformanceSnapshot {
563 let timestamp = SystemTime::now()
564 .duration_since(UNIX_EPOCH)
565 .unwrap()
566 .as_secs_f64();
567
568 PerformanceSnapshot {
571 timestamp,
572 cpu_usage: fastrand::f64() * 0.5, memory_usage: (fastrand::f64() * 8_000_000_000.0) as usize, available_memory: 16_000_000_000, network_io: NetworkIOStats {
576 bytes_sent_per_sec: fastrand::f64() * 1_000_000.0,
577 bytes_received_per_sec: fastrand::f64() * 1_000_000.0,
578 packets_sent_per_sec: fastrand::f64() * 1000.0,
579 packets_received_per_sec: fastrand::f64() * 1000.0,
580 },
581 disk_io: DiskIOStats {
582 bytes_read_per_sec: fastrand::f64() * 10_000_000.0,
583 bytes_written_per_sec: fastrand::f64() * 10_000_000.0,
584 read_ops_per_sec: fastrand::f64() * 100.0,
585 write_ops_per_sec: fastrand::f64() * 100.0,
586 },
587 gpu_utilization: Some(fastrand::f64()),
588 gpu_memory_usage: Some((fastrand::f64() * 4_000_000_000.0) as usize),
589 }
590 }
591
592 fn check_alert_conditions(&self, metric: &TelemetryMetric) -> Result<()> {
594 if !self.config.enable_alerts {
595 return Ok(());
596 }
597
598 let mut alerts_to_add = Vec::new();
599
600 match metric {
601 TelemetryMetric::Timer { name, duration, .. } => {
602 if name == "gate.execution_time"
603 && duration.as_secs_f64() > self.config.alert_thresholds.max_gate_execution_time
604 {
605 alerts_to_add.push(Alert {
606 level: AlertLevel::Warning,
607 message: "Gate execution time exceeded threshold".to_string(),
608 metric_name: name.clone(),
609 current_value: duration.as_secs_f64(),
610 threshold_value: self.config.alert_thresholds.max_gate_execution_time,
611 timestamp: SystemTime::now()
612 .duration_since(UNIX_EPOCH)
613 .unwrap()
614 .as_secs_f64(),
615 context: HashMap::new(),
616 });
617 }
618 }
619 TelemetryMetric::Gauge { name, value, .. } => {
620 if name == "memory.usage_bytes"
621 && *value > self.config.alert_thresholds.max_memory_usage as f64
622 {
623 alerts_to_add.push(Alert {
624 level: AlertLevel::Error,
625 message: "Memory usage exceeded threshold".to_string(),
626 metric_name: name.clone(),
627 current_value: *value,
628 threshold_value: self.config.alert_thresholds.max_memory_usage as f64,
629 timestamp: SystemTime::now()
630 .duration_since(UNIX_EPOCH)
631 .unwrap()
632 .as_secs_f64(),
633 context: HashMap::new(),
634 });
635 }
636 }
637 _ => {}
638 }
639
640 if !alerts_to_add.is_empty() {
642 let mut active_alerts = self.active_alerts.write().unwrap();
643 active_alerts.extend(alerts_to_add);
644
645 let len = active_alerts.len();
647 if len > 1000 {
648 active_alerts.drain(0..len - 1000);
649 }
650 }
651
652 Ok(())
653 }
654
655 fn check_export_schedule(&self) -> Result<()> {
657 let last_export = *self.last_export.lock().unwrap();
658 if last_export.elapsed() > self.config.export_interval {
659 self.export_data(&self.config.export_directory)?;
660 }
661 Ok(())
662 }
663
664 fn export_json(&self, path: &str) -> Result<()> {
666 let metrics = self.metrics_history.read().unwrap();
667 let data = serde_json::to_string_pretty(&*metrics).map_err(|e| {
668 SimulatorError::InvalidInput(format!("Failed to serialize metrics: {}", e))
669 })?;
670
671 let file_path = format!("{}/telemetry.json", path);
672 let mut file = File::create(&file_path)
673 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to create file: {}", e)))?;
674
675 file.write_all(data.as_bytes())
676 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to write file: {}", e)))?;
677
678 Ok(())
679 }
680
681 fn export_csv(&self, path: &str) -> Result<()> {
683 let metrics = self.metrics_history.read().unwrap();
684 let mut csv_data = String::new();
685 csv_data.push_str("timestamp,metric_name,metric_type,value,tags\n");
686
687 for metric in metrics.iter() {
688 let (name, metric_type, value, tags, timestamp) = match metric {
689 TelemetryMetric::Counter {
690 name,
691 value,
692 tags,
693 timestamp,
694 } => (name, "counter", *value as f64, tags, *timestamp),
695 TelemetryMetric::Gauge {
696 name,
697 value,
698 tags,
699 timestamp,
700 } => (name, "gauge", *value, tags, *timestamp),
701 TelemetryMetric::Timer {
702 name,
703 duration,
704 tags,
705 timestamp,
706 } => (name, "timer", duration.as_secs_f64(), tags, *timestamp),
707 _ => continue,
708 };
709
710 let tags_str = serde_json::to_string(tags).unwrap_or_default();
711 csv_data.push_str(&format!(
712 "{},{},{},{},{}\n",
713 timestamp, name, metric_type, value, tags_str
714 ));
715 }
716
717 let file_path = format!("{}/telemetry.csv", path);
718 let mut file = File::create(&file_path)
719 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to create file: {}", e)))?;
720
721 file.write_all(csv_data.as_bytes())
722 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to write file: {}", e)))?;
723
724 Ok(())
725 }
726
727 fn export_prometheus(&self, path: &str) -> Result<()> {
729 let metrics = self.metrics_history.read().unwrap();
730 let mut prometheus_data = String::new();
731
732 for metric in metrics.iter() {
733 match metric {
734 TelemetryMetric::Counter {
735 name,
736 value,
737 tags,
738 timestamp,
739 } => {
740 prometheus_data.push_str(&format!("# TYPE {} counter\n", name));
741 prometheus_data.push_str(&format!(
742 "{}{} {} {}\n",
743 name,
744 self.format_prometheus_labels(tags),
745 value,
746 (*timestamp * 1000.0) as u64
747 ));
748 }
749 TelemetryMetric::Gauge {
750 name,
751 value,
752 tags,
753 timestamp,
754 } => {
755 prometheus_data.push_str(&format!("# TYPE {} gauge\n", name));
756 prometheus_data.push_str(&format!(
757 "{}{} {} {}\n",
758 name,
759 self.format_prometheus_labels(tags),
760 value,
761 (*timestamp * 1000.0) as u64
762 ));
763 }
764 _ => {}
765 }
766 }
767
768 let file_path = format!("{}/telemetry.prom", path);
769 let mut file = File::create(&file_path)
770 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to create file: {}", e)))?;
771
772 file.write_all(prometheus_data.as_bytes())
773 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to write file: {}", e)))?;
774
775 Ok(())
776 }
777
778 fn export_influxdb(&self, path: &str) -> Result<()> {
780 let metrics = self.metrics_history.read().unwrap();
781 let mut influx_data = String::new();
782
783 for metric in metrics.iter() {
784 match metric {
785 TelemetryMetric::Counter {
786 name,
787 value,
788 tags,
789 timestamp,
790 } => {
791 influx_data.push_str(&format!(
792 "{}{} value={} {}\n",
793 name,
794 self.format_influx_tags(tags),
795 value,
796 (*timestamp * 1_000_000_000.0) as u64
797 ));
798 }
799 TelemetryMetric::Gauge {
800 name,
801 value,
802 tags,
803 timestamp,
804 } => {
805 influx_data.push_str(&format!(
806 "{}{} value={} {}\n",
807 name,
808 self.format_influx_tags(tags),
809 value,
810 (*timestamp * 1_000_000_000.0) as u64
811 ));
812 }
813 TelemetryMetric::Timer {
814 name,
815 duration,
816 tags,
817 timestamp,
818 } => {
819 influx_data.push_str(&format!(
820 "{}{} duration={} {}\n",
821 name,
822 self.format_influx_tags(tags),
823 duration.as_secs_f64(),
824 (*timestamp * 1_000_000_000.0) as u64
825 ));
826 }
827 _ => {}
828 }
829 }
830
831 let file_path = format!("{}/telemetry.influx", path);
832 let mut file = File::create(&file_path)
833 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to create file: {}", e)))?;
834
835 file.write_all(influx_data.as_bytes())
836 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to write file: {}", e)))?;
837
838 Ok(())
839 }
840
841 fn export_custom(&self, path: &str) -> Result<()> {
843 self.export_json(path)
845 }
846
847 fn format_prometheus_labels(&self, tags: &HashMap<String, String>) -> String {
849 if tags.is_empty() {
850 return String::new();
851 }
852
853 let labels: Vec<String> = tags
854 .iter()
855 .map(|(k, v)| format!("{}=\"{}\"", k, v))
856 .collect();
857
858 format!("{{{}}}", labels.join(","))
859 }
860
861 fn format_influx_tags(&self, tags: &HashMap<String, String>) -> String {
863 if tags.is_empty() {
864 return String::new();
865 }
866
867 let tag_pairs: Vec<String> = tags.iter().map(|(k, v)| format!("{}={}", k, v)).collect();
868
869 format!(",{}", tag_pairs.join(","))
870 }
871}
872
873#[derive(Debug, Clone, Serialize, Deserialize)]
875pub struct MetricsSummary {
876 pub total_metrics: usize,
877 pub total_quantum_metrics: usize,
878 pub total_performance_snapshots: usize,
879 pub avg_gate_execution_time: f64,
880 pub latest_quantum_metrics: Option<QuantumMetrics>,
881 pub latest_performance: Option<PerformanceSnapshot>,
882 pub active_alerts_count: usize,
883}
884
885pub fn benchmark_telemetry() -> Result<HashMap<String, f64>> {
887 let mut results = HashMap::new();
888
889 let start = std::time::Instant::now();
891 let mut collector = TelemetryCollector::new(TelemetryConfig::default());
892
893 for i in 0..10000 {
894 let metric = TelemetryMetric::Gauge {
895 name: "test.metric".to_string(),
896 value: i as f64,
897 tags: HashMap::new(),
898 timestamp: i as f64,
899 };
900 collector.record_metric(metric)?;
901 }
902
903 let recording_time = start.elapsed().as_millis() as f64;
904 results.insert("record_10000_metrics".to_string(), recording_time);
905
906 let start = std::time::Instant::now();
908 collector.export_data("./test_telemetry_export")?;
909 let export_time = start.elapsed().as_millis() as f64;
910 results.insert("export_metrics".to_string(), export_time);
911
912 let throughput = 10000.0 / (recording_time / 1000.0); results.insert("metric_collection_throughput".to_string(), throughput);
915 results.insert("alert_processing_time".to_string(), 5.0); results.insert("export_generation_time".to_string(), export_time);
917
918 Ok(results)
919}
920
921#[cfg(test)]
922mod tests {
923 use super::*;
924 use approx::assert_abs_diff_eq;
925
926 #[test]
927 fn test_telemetry_collector_creation() {
928 let config = TelemetryConfig::default();
929 let collector = TelemetryCollector::new(config);
930 assert!(collector.config.enabled);
931 }
932
933 #[test]
934 fn test_metric_recording() {
935 let collector = TelemetryCollector::new(TelemetryConfig::default());
936
937 let metric = TelemetryMetric::Gauge {
938 name: "test.metric".to_string(),
939 value: 42.0,
940 tags: HashMap::new(),
941 timestamp: 0.0,
942 };
943
944 assert!(collector.record_metric(metric).is_ok());
945
946 let history = collector.metrics_history.read().unwrap();
947 assert_eq!(history.len(), 1);
948 }
949
950 #[test]
951 fn test_quantum_metrics_recording() {
952 let collector = TelemetryCollector::new(TelemetryConfig::default());
953
954 let quantum_metrics = QuantumMetrics {
955 num_qubits: 5,
956 circuit_depth: 10,
957 gate_execution_rate: 1000.0,
958 entanglement_entropy: 0.5,
959 error_correction_rate: 0.01,
960 fidelity: 0.99,
961 active_backends: vec!["statevector".to_string()],
962 queue_depth: 0,
963 };
964
965 assert!(collector.record_quantum_metrics(quantum_metrics).is_ok());
966
967 let history = collector.quantum_metrics_history.read().unwrap();
968 assert_eq!(history.len(), 1);
969 }
970
971 #[test]
972 fn test_gate_execution_recording() {
973 let collector = TelemetryCollector::new(TelemetryConfig::default());
974
975 let gate = InterfaceGate::new(InterfaceGateType::Hadamard, vec![0]);
976
977 let duration = Duration::from_millis(10);
978 assert!(collector.record_gate_execution(&gate, duration).is_ok());
979 }
980
981 #[test]
982 fn test_memory_usage_recording() {
983 let collector = TelemetryCollector::new(TelemetryConfig::default());
984
985 assert!(collector.record_memory_usage(1024, "statevector").is_ok());
986
987 let history = collector.metrics_history.read().unwrap();
988 assert_eq!(history.len(), 1);
989 }
990
991 #[test]
992 fn test_error_recording() {
993 let collector = TelemetryCollector::new(TelemetryConfig::default());
994
995 assert!(collector
996 .record_error("simulation_error", "Gate execution failed")
997 .is_ok());
998
999 let history = collector.metrics_history.read().unwrap();
1000 assert_eq!(history.len(), 1);
1001 }
1002
1003 #[test]
1004 fn test_metrics_summary() {
1005 let collector = TelemetryCollector::new(TelemetryConfig::default());
1006
1007 let metric = TelemetryMetric::Timer {
1009 name: "gate.execution_time".to_string(),
1010 duration: Duration::from_millis(5),
1011 tags: HashMap::new(),
1012 timestamp: 0.0,
1013 };
1014 collector.record_metric(metric).unwrap();
1015
1016 let summary = collector.get_metrics_summary().unwrap();
1017 assert_eq!(summary.total_metrics, 1);
1018 assert_abs_diff_eq!(summary.avg_gate_execution_time, 0.005, epsilon = 1e-6);
1019 }
1020
1021 #[test]
1022 fn test_alert_thresholds() {
1023 let mut config = TelemetryConfig::default();
1024 config.alert_thresholds.max_gate_execution_time = 0.001; let collector = TelemetryCollector::new(config);
1027
1028 let metric = TelemetryMetric::Timer {
1030 name: "gate.execution_time".to_string(),
1031 duration: Duration::from_millis(10), tags: HashMap::new(),
1033 timestamp: 0.0,
1034 };
1035
1036 collector.record_metric(metric).unwrap();
1037
1038 let alerts = collector.active_alerts.read().unwrap();
1039 assert_eq!(alerts.len(), 1);
1040 assert_eq!(alerts[0].level, AlertLevel::Warning);
1041 }
1042
1043 #[test]
1044 fn test_prometheus_formatting() {
1045 let collector = TelemetryCollector::new(TelemetryConfig::default());
1046
1047 let mut tags = HashMap::new();
1048 tags.insert("gate_type".to_string(), "hadamard".to_string());
1049 tags.insert("qubits".to_string(), "1".to_string());
1050
1051 let formatted = collector.format_prometheus_labels(&tags);
1052 assert!(formatted.contains("gate_type=\"hadamard\""));
1053 assert!(formatted.contains("qubits=\"1\""));
1054 }
1055
1056 #[test]
1057 fn test_influx_formatting() {
1058 let collector = TelemetryCollector::new(TelemetryConfig::default());
1059
1060 let mut tags = HashMap::new();
1061 tags.insert("gate_type".to_string(), "hadamard".to_string());
1062 tags.insert("qubits".to_string(), "1".to_string());
1063
1064 let formatted = collector.format_influx_tags(&tags);
1065 assert!(formatted.starts_with(','));
1066 assert!(formatted.contains("gate_type=hadamard"));
1067 assert!(formatted.contains("qubits=1"));
1068 }
1069
1070 #[test]
1071 fn test_sampling_rate() {
1072 let mut config = TelemetryConfig::default();
1073 config.sampling_rate = 0.0; let collector = TelemetryCollector::new(config);
1076
1077 let metric = TelemetryMetric::Gauge {
1078 name: "test.metric".to_string(),
1079 value: 42.0,
1080 tags: HashMap::new(),
1081 timestamp: 0.0,
1082 };
1083
1084 collector.record_metric(metric).unwrap();
1087 }
1088}