1use scirs2_core::ndarray::{Array1, Array2};
9use scirs2_core::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 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
274 pub fn start(&mut self) -> Result<()> {
276 if !self.config.enabled {
277 return Ok(());
278 }
279
280 if self.config.monitor_system_resources {
282 self.start_system_monitoring()?;
283 }
284
285 Ok(())
286 }
287
288 pub fn stop(&mut self) {
290 if let Some(handle) = self.system_monitor_handle.take() {
291 let _ = handle.join();
294 }
295 }
296
297 pub fn record_metric(&self, metric: TelemetryMetric) -> Result<()> {
299 if !self.config.enabled {
300 return Ok(());
301 }
302
303 if fastrand::f64() > self.config.sampling_rate {
305 return Ok(());
306 }
307
308 {
310 let mut history = self.metrics_history.write().unwrap();
311 history.push_back(metric.clone());
312 if history.len() > self.config.max_history_size {
313 history.pop_front();
314 }
315 }
316
317 self.check_alert_conditions(&metric)?;
319
320 for handler in self.custom_handlers.values() {
322 handler(&metric);
323 }
324
325 self.check_export_schedule()?;
327
328 Ok(())
329 }
330
331 pub fn record_quantum_metrics(&self, metrics: QuantumMetrics) -> Result<()> {
333 if !self.config.enabled {
334 return Ok(());
335 }
336
337 {
338 let mut history = self.quantum_metrics_history.write().unwrap();
339 history.push_back(metrics.clone());
340 if history.len() > 1000 {
341 history.pop_front();
342 }
343 }
344
345 let timestamp = SystemTime::now()
347 .duration_since(UNIX_EPOCH)
348 .unwrap()
349 .as_secs_f64();
350
351 let quantum_gauge = TelemetryMetric::Gauge {
352 name: "quantum.num_qubits".to_string(),
353 value: metrics.num_qubits as f64,
354 tags: self.config.custom_tags.clone(),
355 timestamp,
356 };
357 self.record_metric(quantum_gauge)?;
358
359 let rate_gauge = TelemetryMetric::Gauge {
360 name: "quantum.gate_execution_rate".to_string(),
361 value: metrics.gate_execution_rate,
362 tags: self.config.custom_tags.clone(),
363 timestamp,
364 };
365 self.record_metric(rate_gauge)?;
366
367 let entropy_gauge = TelemetryMetric::Gauge {
368 name: "quantum.entanglement_entropy".to_string(),
369 value: metrics.entanglement_entropy,
370 tags: self.config.custom_tags.clone(),
371 timestamp,
372 };
373 self.record_metric(entropy_gauge)?;
374
375 Ok(())
376 }
377
378 pub fn record_gate_execution(&self, gate: &InterfaceGate, duration: Duration) -> Result<()> {
380 let gate_type = format!("{:?}", gate.gate_type);
381 let mut tags = self.config.custom_tags.clone();
382 tags.insert("gate_type".to_string(), gate_type);
383 tags.insert("num_qubits".to_string(), gate.qubits.len().to_string());
384
385 let timer = TelemetryMetric::Timer {
386 name: "gate.execution_time".to_string(),
387 duration,
388 tags,
389 timestamp: SystemTime::now()
390 .duration_since(UNIX_EPOCH)
391 .unwrap()
392 .as_secs_f64(),
393 };
394
395 self.record_metric(timer)?;
396 Ok(())
397 }
398
399 pub fn record_circuit_execution(
401 &self,
402 circuit: &InterfaceCircuit,
403 duration: Duration,
404 ) -> Result<()> {
405 let mut tags = self.config.custom_tags.clone();
406 tags.insert("num_qubits".to_string(), circuit.num_qubits.to_string());
407 tags.insert("num_gates".to_string(), circuit.gates.len().to_string());
408
409 let timer = TelemetryMetric::Timer {
410 name: "circuit.execution_time".to_string(),
411 duration,
412 tags: tags.clone(),
413 timestamp: SystemTime::now()
414 .duration_since(UNIX_EPOCH)
415 .unwrap()
416 .as_secs_f64(),
417 };
418
419 self.record_metric(timer)?;
420
421 let gate_counter = TelemetryMetric::Counter {
423 name: "circuit.gates_executed".to_string(),
424 value: circuit.gates.len() as u64,
425 tags,
426 timestamp: SystemTime::now()
427 .duration_since(UNIX_EPOCH)
428 .unwrap()
429 .as_secs_f64(),
430 };
431
432 self.record_metric(gate_counter)?;
433 Ok(())
434 }
435
436 pub fn record_memory_usage(&self, bytes_used: usize, category: &str) -> Result<()> {
438 let mut tags = self.config.custom_tags.clone();
439 tags.insert("category".to_string(), category.to_string());
440
441 let gauge = TelemetryMetric::Gauge {
442 name: "memory.usage_bytes".to_string(),
443 value: bytes_used as f64,
444 tags,
445 timestamp: SystemTime::now()
446 .duration_since(UNIX_EPOCH)
447 .unwrap()
448 .as_secs_f64(),
449 };
450
451 self.record_metric(gauge)?;
452 Ok(())
453 }
454
455 pub fn record_error(&self, error_type: &str, error_message: &str) -> Result<()> {
457 let mut tags = self.config.custom_tags.clone();
458 tags.insert("error_type".to_string(), error_type.to_string());
459 tags.insert("error_message".to_string(), error_message.to_string());
460
461 let counter = TelemetryMetric::Counter {
462 name: "errors.total".to_string(),
463 value: 1,
464 tags,
465 timestamp: SystemTime::now()
466 .duration_since(UNIX_EPOCH)
467 .unwrap()
468 .as_secs_f64(),
469 };
470
471 self.record_metric(counter)?;
472 Ok(())
473 }
474
475 pub fn get_metrics_summary(&self) -> Result<MetricsSummary> {
477 let metrics_history = self.metrics_history.read().unwrap();
478 let quantum_history = self.quantum_metrics_history.read().unwrap();
479 let performance_history = self.performance_history.read().unwrap();
480
481 let total_metrics = metrics_history.len();
482 let total_quantum_metrics = quantum_history.len();
483 let total_performance_snapshots = performance_history.len();
484
485 let mut gate_times = Vec::new();
487 for metric in metrics_history.iter() {
488 if let TelemetryMetric::Timer { name, duration, .. } = metric {
489 if name == "gate.execution_time" {
490 gate_times.push(duration.as_secs_f64());
491 }
492 }
493 }
494
495 let avg_gate_time = if gate_times.is_empty() {
496 0.0
497 } else {
498 gate_times.iter().sum::<f64>() / gate_times.len() as f64
499 };
500
501 let latest_quantum_metrics = quantum_history.back().cloned();
503
504 let latest_performance = performance_history.back().cloned();
506
507 Ok(MetricsSummary {
508 total_metrics,
509 total_quantum_metrics,
510 total_performance_snapshots,
511 avg_gate_execution_time: avg_gate_time,
512 latest_quantum_metrics,
513 latest_performance,
514 active_alerts_count: self.active_alerts.read().unwrap().len(),
515 })
516 }
517
518 pub fn export_data(&self, path: &str) -> Result<()> {
520 std::fs::create_dir_all(path).map_err(|e| {
521 SimulatorError::InvalidInput(format!("Failed to create export directory: {e}"))
522 })?;
523
524 match self.config.export_format {
525 TelemetryExportFormat::JSON => self.export_json(path)?,
526 TelemetryExportFormat::CSV => self.export_csv(path)?,
527 TelemetryExportFormat::Prometheus => self.export_prometheus(path)?,
528 TelemetryExportFormat::InfluxDB => self.export_influxdb(path)?,
529 TelemetryExportFormat::Custom => self.export_custom(path)?,
530 }
531
532 *self.last_export.lock().unwrap() = Instant::now();
533 Ok(())
534 }
535
536 fn start_system_monitoring(&mut self) -> Result<()> {
538 let performance_history = Arc::clone(&self.performance_history);
539 let config = self.config.clone();
540
541 let handle = std::thread::spawn(move || loop {
542 let snapshot = Self::collect_system_metrics();
543
544 {
545 let mut history = performance_history.write().unwrap();
546 history.push_back(snapshot);
547 if history.len() > 1000 {
548 history.pop_front();
549 }
550 }
551
552 std::thread::sleep(Duration::from_secs(1));
553 });
554
555 self.system_monitor_handle = Some(handle);
556 Ok(())
557 }
558
559 fn collect_system_metrics() -> PerformanceSnapshot {
561 let timestamp = SystemTime::now()
562 .duration_since(UNIX_EPOCH)
563 .unwrap()
564 .as_secs_f64();
565
566 PerformanceSnapshot {
569 timestamp,
570 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 {
574 bytes_sent_per_sec: fastrand::f64() * 1_000_000.0,
575 bytes_received_per_sec: fastrand::f64() * 1_000_000.0,
576 packets_sent_per_sec: fastrand::f64() * 1000.0,
577 packets_received_per_sec: fastrand::f64() * 1000.0,
578 },
579 disk_io: DiskIOStats {
580 bytes_read_per_sec: fastrand::f64() * 10_000_000.0,
581 bytes_written_per_sec: fastrand::f64() * 10_000_000.0,
582 read_ops_per_sec: fastrand::f64() * 100.0,
583 write_ops_per_sec: fastrand::f64() * 100.0,
584 },
585 gpu_utilization: Some(fastrand::f64()),
586 gpu_memory_usage: Some((fastrand::f64() * 4_000_000_000.0) as usize),
587 }
588 }
589
590 fn check_alert_conditions(&self, metric: &TelemetryMetric) -> Result<()> {
592 if !self.config.enable_alerts {
593 return Ok(());
594 }
595
596 let mut alerts_to_add = Vec::new();
597
598 match metric {
599 TelemetryMetric::Timer { name, duration, .. } => {
600 if name == "gate.execution_time"
601 && duration.as_secs_f64() > self.config.alert_thresholds.max_gate_execution_time
602 {
603 alerts_to_add.push(Alert {
604 level: AlertLevel::Warning,
605 message: "Gate execution time exceeded threshold".to_string(),
606 metric_name: name.clone(),
607 current_value: duration.as_secs_f64(),
608 threshold_value: self.config.alert_thresholds.max_gate_execution_time,
609 timestamp: SystemTime::now()
610 .duration_since(UNIX_EPOCH)
611 .unwrap()
612 .as_secs_f64(),
613 context: HashMap::new(),
614 });
615 }
616 }
617 TelemetryMetric::Gauge { name, value, .. } => {
618 if name == "memory.usage_bytes"
619 && *value > self.config.alert_thresholds.max_memory_usage as f64
620 {
621 alerts_to_add.push(Alert {
622 level: AlertLevel::Error,
623 message: "Memory usage exceeded threshold".to_string(),
624 metric_name: name.clone(),
625 current_value: *value,
626 threshold_value: self.config.alert_thresholds.max_memory_usage as f64,
627 timestamp: SystemTime::now()
628 .duration_since(UNIX_EPOCH)
629 .unwrap()
630 .as_secs_f64(),
631 context: HashMap::new(),
632 });
633 }
634 }
635 _ => {}
636 }
637
638 if !alerts_to_add.is_empty() {
640 let mut active_alerts = self.active_alerts.write().unwrap();
641 active_alerts.extend(alerts_to_add);
642
643 let len = active_alerts.len();
645 if len > 1000 {
646 active_alerts.drain(0..len - 1000);
647 }
648 }
649
650 Ok(())
651 }
652
653 fn check_export_schedule(&self) -> Result<()> {
655 let last_export = *self.last_export.lock().unwrap();
656 if last_export.elapsed() > self.config.export_interval {
657 self.export_data(&self.config.export_directory)?;
658 }
659 Ok(())
660 }
661
662 fn export_json(&self, path: &str) -> Result<()> {
664 let metrics = self.metrics_history.read().unwrap();
665 let data = serde_json::to_string_pretty(&*metrics).map_err(|e| {
666 SimulatorError::InvalidInput(format!("Failed to serialize metrics: {e}"))
667 })?;
668
669 let file_path = format!("{path}/telemetry.json");
670 let mut file = File::create(&file_path)
671 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to create file: {e}")))?;
672
673 file.write_all(data.as_bytes())
674 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to write file: {e}")))?;
675
676 Ok(())
677 }
678
679 fn export_csv(&self, path: &str) -> Result<()> {
681 let metrics = self.metrics_history.read().unwrap();
682 let mut csv_data = String::new();
683 csv_data.push_str("timestamp,metric_name,metric_type,value,tags\n");
684
685 for metric in metrics.iter() {
686 let (name, metric_type, value, tags, timestamp) = match metric {
687 TelemetryMetric::Counter {
688 name,
689 value,
690 tags,
691 timestamp,
692 } => (name, "counter", *value as f64, tags, *timestamp),
693 TelemetryMetric::Gauge {
694 name,
695 value,
696 tags,
697 timestamp,
698 } => (name, "gauge", *value, tags, *timestamp),
699 TelemetryMetric::Timer {
700 name,
701 duration,
702 tags,
703 timestamp,
704 } => (name, "timer", duration.as_secs_f64(), tags, *timestamp),
705 _ => continue,
706 };
707
708 let tags_str = serde_json::to_string(tags).unwrap_or_default();
709 csv_data.push_str(&format!(
710 "{timestamp},{name},{metric_type},{value},{tags_str}\n"
711 ));
712 }
713
714 let file_path = format!("{path}/telemetry.csv");
715 let mut file = File::create(&file_path)
716 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to create file: {e}")))?;
717
718 file.write_all(csv_data.as_bytes())
719 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to write file: {e}")))?;
720
721 Ok(())
722 }
723
724 fn export_prometheus(&self, path: &str) -> Result<()> {
726 let metrics = self.metrics_history.read().unwrap();
727 let mut prometheus_data = String::new();
728
729 for metric in metrics.iter() {
730 match metric {
731 TelemetryMetric::Counter {
732 name,
733 value,
734 tags,
735 timestamp,
736 } => {
737 prometheus_data.push_str(&format!("# TYPE {name} counter\n"));
738 prometheus_data.push_str(&format!(
739 "{}{} {} {}\n",
740 name,
741 self.format_prometheus_labels(tags),
742 value,
743 (*timestamp * 1000.0) as u64
744 ));
745 }
746 TelemetryMetric::Gauge {
747 name,
748 value,
749 tags,
750 timestamp,
751 } => {
752 prometheus_data.push_str(&format!("# TYPE {name} gauge\n"));
753 prometheus_data.push_str(&format!(
754 "{}{} {} {}\n",
755 name,
756 self.format_prometheus_labels(tags),
757 value,
758 (*timestamp * 1000.0) as u64
759 ));
760 }
761 _ => {}
762 }
763 }
764
765 let file_path = format!("{path}/telemetry.prom");
766 let mut file = File::create(&file_path)
767 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to create file: {e}")))?;
768
769 file.write_all(prometheus_data.as_bytes())
770 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to write file: {e}")))?;
771
772 Ok(())
773 }
774
775 fn export_influxdb(&self, path: &str) -> Result<()> {
777 let metrics = self.metrics_history.read().unwrap();
778 let mut influx_data = String::new();
779
780 for metric in metrics.iter() {
781 match metric {
782 TelemetryMetric::Counter {
783 name,
784 value,
785 tags,
786 timestamp,
787 } => {
788 influx_data.push_str(&format!(
789 "{}{} value={} {}\n",
790 name,
791 self.format_influx_tags(tags),
792 value,
793 (*timestamp * 1_000_000_000.0) as u64
794 ));
795 }
796 TelemetryMetric::Gauge {
797 name,
798 value,
799 tags,
800 timestamp,
801 } => {
802 influx_data.push_str(&format!(
803 "{}{} value={} {}\n",
804 name,
805 self.format_influx_tags(tags),
806 value,
807 (*timestamp * 1_000_000_000.0) as u64
808 ));
809 }
810 TelemetryMetric::Timer {
811 name,
812 duration,
813 tags,
814 timestamp,
815 } => {
816 influx_data.push_str(&format!(
817 "{}{} duration={} {}\n",
818 name,
819 self.format_influx_tags(tags),
820 duration.as_secs_f64(),
821 (*timestamp * 1_000_000_000.0) as u64
822 ));
823 }
824 _ => {}
825 }
826 }
827
828 let file_path = format!("{path}/telemetry.influx");
829 let mut file = File::create(&file_path)
830 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to create file: {e}")))?;
831
832 file.write_all(influx_data.as_bytes())
833 .map_err(|e| SimulatorError::InvalidInput(format!("Failed to write file: {e}")))?;
834
835 Ok(())
836 }
837
838 fn export_custom(&self, path: &str) -> Result<()> {
840 self.export_json(path)
842 }
843
844 fn format_prometheus_labels(&self, tags: &HashMap<String, String>) -> String {
846 if tags.is_empty() {
847 return String::new();
848 }
849
850 let labels: Vec<String> = tags.iter().map(|(k, v)| format!("{k}=\"{v}\"")).collect();
851
852 format!("{{{}}}", labels.join(","))
853 }
854
855 fn format_influx_tags(&self, tags: &HashMap<String, String>) -> String {
857 if tags.is_empty() {
858 return String::new();
859 }
860
861 let tag_pairs: Vec<String> = tags.iter().map(|(k, v)| format!("{k}={v}")).collect();
862
863 format!(",{}", tag_pairs.join(","))
864 }
865}
866
867#[derive(Debug, Clone, Serialize, Deserialize)]
869pub struct MetricsSummary {
870 pub total_metrics: usize,
871 pub total_quantum_metrics: usize,
872 pub total_performance_snapshots: usize,
873 pub avg_gate_execution_time: f64,
874 pub latest_quantum_metrics: Option<QuantumMetrics>,
875 pub latest_performance: Option<PerformanceSnapshot>,
876 pub active_alerts_count: usize,
877}
878
879pub fn benchmark_telemetry() -> Result<HashMap<String, f64>> {
881 let mut results = HashMap::new();
882
883 let start = std::time::Instant::now();
885 let mut collector = TelemetryCollector::new(TelemetryConfig::default());
886
887 for i in 0..10000 {
888 let metric = TelemetryMetric::Gauge {
889 name: "test.metric".to_string(),
890 value: i as f64,
891 tags: HashMap::new(),
892 timestamp: i as f64,
893 };
894 collector.record_metric(metric)?;
895 }
896
897 let recording_time = start.elapsed().as_millis() as f64;
898 results.insert("record_10000_metrics".to_string(), recording_time);
899
900 let start = std::time::Instant::now();
902 collector.export_data("./test_telemetry_export")?;
903 let export_time = start.elapsed().as_millis() as f64;
904 results.insert("export_metrics".to_string(), export_time);
905
906 let throughput = 10000.0 / (recording_time / 1000.0); results.insert("metric_collection_throughput".to_string(), throughput);
909 results.insert("alert_processing_time".to_string(), 5.0); results.insert("export_generation_time".to_string(), export_time);
911
912 Ok(results)
913}
914
915#[cfg(test)]
916mod tests {
917 use super::*;
918 use approx::assert_abs_diff_eq;
919
920 #[test]
921 fn test_telemetry_collector_creation() {
922 let config = TelemetryConfig::default();
923 let collector = TelemetryCollector::new(config);
924 assert!(collector.config.enabled);
925 }
926
927 #[test]
928 fn test_metric_recording() {
929 let collector = TelemetryCollector::new(TelemetryConfig::default());
930
931 let metric = TelemetryMetric::Gauge {
932 name: "test.metric".to_string(),
933 value: 42.0,
934 tags: HashMap::new(),
935 timestamp: 0.0,
936 };
937
938 assert!(collector.record_metric(metric).is_ok());
939
940 let history = collector.metrics_history.read().unwrap();
941 assert_eq!(history.len(), 1);
942 }
943
944 #[test]
945 fn test_quantum_metrics_recording() {
946 let collector = TelemetryCollector::new(TelemetryConfig::default());
947
948 let quantum_metrics = QuantumMetrics {
949 num_qubits: 5,
950 circuit_depth: 10,
951 gate_execution_rate: 1000.0,
952 entanglement_entropy: 0.5,
953 error_correction_rate: 0.01,
954 fidelity: 0.99,
955 active_backends: vec!["statevector".to_string()],
956 queue_depth: 0,
957 };
958
959 assert!(collector.record_quantum_metrics(quantum_metrics).is_ok());
960
961 let history = collector.quantum_metrics_history.read().unwrap();
962 assert_eq!(history.len(), 1);
963 }
964
965 #[test]
966 fn test_gate_execution_recording() {
967 let collector = TelemetryCollector::new(TelemetryConfig::default());
968
969 let gate = InterfaceGate::new(InterfaceGateType::Hadamard, vec![0]);
970
971 let duration = Duration::from_millis(10);
972 assert!(collector.record_gate_execution(&gate, duration).is_ok());
973 }
974
975 #[test]
976 fn test_memory_usage_recording() {
977 let collector = TelemetryCollector::new(TelemetryConfig::default());
978
979 assert!(collector.record_memory_usage(1024, "statevector").is_ok());
980
981 let history = collector.metrics_history.read().unwrap();
982 assert_eq!(history.len(), 1);
983 }
984
985 #[test]
986 fn test_error_recording() {
987 let collector = TelemetryCollector::new(TelemetryConfig::default());
988
989 assert!(collector
990 .record_error("simulation_error", "Gate execution failed")
991 .is_ok());
992
993 let history = collector.metrics_history.read().unwrap();
994 assert_eq!(history.len(), 1);
995 }
996
997 #[test]
998 fn test_metrics_summary() {
999 let collector = TelemetryCollector::new(TelemetryConfig::default());
1000
1001 let metric = TelemetryMetric::Timer {
1003 name: "gate.execution_time".to_string(),
1004 duration: Duration::from_millis(5),
1005 tags: HashMap::new(),
1006 timestamp: 0.0,
1007 };
1008 collector.record_metric(metric).unwrap();
1009
1010 let summary = collector.get_metrics_summary().unwrap();
1011 assert_eq!(summary.total_metrics, 1);
1012 assert_abs_diff_eq!(summary.avg_gate_execution_time, 0.005, epsilon = 1e-6);
1013 }
1014
1015 #[test]
1016 fn test_alert_thresholds() {
1017 let mut config = TelemetryConfig::default();
1018 config.alert_thresholds.max_gate_execution_time = 0.001; let collector = TelemetryCollector::new(config);
1021
1022 let metric = TelemetryMetric::Timer {
1024 name: "gate.execution_time".to_string(),
1025 duration: Duration::from_millis(10), tags: HashMap::new(),
1027 timestamp: 0.0,
1028 };
1029
1030 collector.record_metric(metric).unwrap();
1031
1032 let alerts = collector.active_alerts.read().unwrap();
1033 assert_eq!(alerts.len(), 1);
1034 assert_eq!(alerts[0].level, AlertLevel::Warning);
1035 }
1036
1037 #[test]
1038 fn test_prometheus_formatting() {
1039 let collector = TelemetryCollector::new(TelemetryConfig::default());
1040
1041 let mut tags = HashMap::new();
1042 tags.insert("gate_type".to_string(), "hadamard".to_string());
1043 tags.insert("qubits".to_string(), "1".to_string());
1044
1045 let formatted = collector.format_prometheus_labels(&tags);
1046 assert!(formatted.contains("gate_type=\"hadamard\""));
1047 assert!(formatted.contains("qubits=\"1\""));
1048 }
1049
1050 #[test]
1051 fn test_influx_formatting() {
1052 let collector = TelemetryCollector::new(TelemetryConfig::default());
1053
1054 let mut tags = HashMap::new();
1055 tags.insert("gate_type".to_string(), "hadamard".to_string());
1056 tags.insert("qubits".to_string(), "1".to_string());
1057
1058 let formatted = collector.format_influx_tags(&tags);
1059 assert!(formatted.starts_with(','));
1060 assert!(formatted.contains("gate_type=hadamard"));
1061 assert!(formatted.contains("qubits=1"));
1062 }
1063
1064 #[test]
1065 fn test_sampling_rate() {
1066 let mut config = TelemetryConfig::default();
1067 config.sampling_rate = 0.0; let collector = TelemetryCollector::new(config);
1070
1071 let metric = TelemetryMetric::Gauge {
1072 name: "test.metric".to_string(),
1073 value: 42.0,
1074 tags: HashMap::new(),
1075 timestamp: 0.0,
1076 };
1077
1078 collector.record_metric(metric).unwrap();
1081 }
1082}