1pub mod collector;
7pub mod config;
8pub mod events;
9pub mod export;
10pub mod privacy;
11pub mod storage;
12
13use serde::{Deserialize, Serialize};
14use std::sync::Arc;
15use tokio::sync::RwLock;
16
17pub use collector::TelemetryCollector;
18pub use config::{TelemetryConfig, TelemetryLevel};
19pub use events::{EventMetadata, EventType, TelemetryEvent};
20pub use export::{ExportFormat, TelemetryExporter};
21pub use privacy::{AnonymizationLevel, PrivacyControl};
22pub use storage::TelemetryStorage;
23
24pub struct TelemetrySystem {
26 config: Arc<RwLock<TelemetryConfig>>,
27 collector: Arc<TelemetryCollector>,
28 storage: Arc<RwLock<TelemetryStorage>>,
29 exporter: Arc<TelemetryExporter>,
30}
31
32impl TelemetrySystem {
33 pub async fn new(config: TelemetryConfig) -> Result<Self, TelemetryError> {
35 let config = Arc::new(RwLock::new(config));
36 let storage = Arc::new(RwLock::new(TelemetryStorage::new(
37 &config.read().await.storage_path,
38 )?));
39 let collector = Arc::new(TelemetryCollector::new(Arc::clone(&config)).await);
40 let exporter = Arc::new(TelemetryExporter::new(Arc::clone(&config)));
41
42 Ok(Self {
43 config,
44 collector,
45 storage,
46 exporter,
47 })
48 }
49
50 pub async fn record_event(&self, event: TelemetryEvent) -> Result<(), TelemetryError> {
52 if !self.config.read().await.enabled {
53 return Ok(());
54 }
55
56 let event = self.collector.apply_privacy(event).await?;
58
59 self.storage.write().await.store_event(&event).await?;
61
62 Ok(())
63 }
64
65 pub async fn get_statistics(&self) -> Result<TelemetryStatistics, TelemetryError> {
67 self.storage.read().await.get_statistics().await
68 }
69
70 pub async fn export(
72 &self,
73 format: ExportFormat,
74 output: &std::path::Path,
75 ) -> Result<(), TelemetryError> {
76 let events = self.storage.read().await.get_all_events().await?;
77 self.exporter.export(&events, format, output).await
78 }
79
80 pub async fn clear_data(&self) -> Result<(), TelemetryError> {
82 self.storage.write().await.clear().await
83 }
84
85 pub async fn update_config(&self, new_config: TelemetryConfig) -> Result<(), TelemetryError> {
87 *self.config.write().await = new_config;
88 Ok(())
89 }
90
91 pub async fn is_enabled(&self) -> bool {
93 self.config.read().await.enabled
94 }
95}
96
97#[derive(Debug, thiserror::Error)]
99pub enum TelemetryError {
100 #[error("I/O error: {0}")]
101 Io(#[from] std::io::Error),
102
103 #[error("Serialization error: {0}")]
104 Serialization(#[from] serde_json::Error),
105
106 #[error("Privacy violation: {0}")]
107 PrivacyViolation(String),
108
109 #[error("Configuration error: {0}")]
110 Configuration(String),
111
112 #[error("Storage error: {0}")]
113 Storage(String),
114
115 #[error("Export error: {0}")]
116 Export(String),
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct TelemetryStatistics {
122 pub total_events: u64,
124
125 pub events_by_type: std::collections::HashMap<String, u64>,
127
128 pub synthesis_requests: u64,
130
131 pub avg_synthesis_duration: f64,
133
134 pub total_errors: u64,
136
137 pub most_used_commands: Vec<(String, u64)>,
139
140 pub most_used_voices: Vec<(String, u64)>,
142
143 pub start_time: Option<chrono::DateTime<chrono::Utc>>,
145 pub end_time: Option<chrono::DateTime<chrono::Utc>>,
146
147 pub storage_size_bytes: u64,
149}
150
151impl Default for TelemetryStatistics {
152 fn default() -> Self {
153 Self {
154 total_events: 0,
155 events_by_type: std::collections::HashMap::new(),
156 synthesis_requests: 0,
157 avg_synthesis_duration: 0.0,
158 total_errors: 0,
159 most_used_commands: Vec::new(),
160 most_used_voices: Vec::new(),
161 start_time: None,
162 end_time: None,
163 storage_size_bytes: 0,
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use std::path::PathBuf;
172
173 #[tokio::test]
174 async fn test_telemetry_system_creation() {
175 let temp_dir = std::env::temp_dir().join("voirs_telemetry_test");
176 let config = TelemetryConfig {
177 enabled: true,
178 level: TelemetryLevel::Standard,
179 storage_path: temp_dir.clone(),
180 anonymization: AnonymizationLevel::Medium,
181 remote_endpoint: None,
182 batch_size: 100,
183 flush_interval_secs: 60,
184 };
185
186 let system = TelemetrySystem::new(config).await;
187 assert!(system.is_ok());
188
189 let _ = std::fs::remove_dir_all(temp_dir);
191 }
192
193 #[tokio::test]
194 async fn test_telemetry_disabled() {
195 let temp_dir = std::env::temp_dir().join("voirs_telemetry_test_disabled");
196 let config = TelemetryConfig {
197 enabled: false,
198 level: TelemetryLevel::Minimal,
199 storage_path: temp_dir.clone(),
200 anonymization: AnonymizationLevel::High,
201 remote_endpoint: None,
202 batch_size: 100,
203 flush_interval_secs: 60,
204 };
205
206 let system = TelemetrySystem::new(config).await.unwrap();
207 assert!(!system.is_enabled().await);
208
209 let _ = std::fs::remove_dir_all(temp_dir);
211 }
212
213 #[tokio::test]
214 async fn test_record_event() {
215 let temp_dir = std::env::temp_dir().join("voirs_telemetry_test_record");
216 let config = TelemetryConfig {
217 enabled: true,
218 level: TelemetryLevel::Standard,
219 storage_path: temp_dir.clone(),
220 anonymization: AnonymizationLevel::Low,
221 remote_endpoint: None,
222 batch_size: 100,
223 flush_interval_secs: 60,
224 };
225
226 let system = TelemetrySystem::new(config).await.unwrap();
227
228 let event = TelemetryEvent {
229 id: uuid::Uuid::new_v4().to_string(),
230 event_type: EventType::CommandExecuted,
231 timestamp: chrono::Utc::now(),
232 metadata: EventMetadata::default(),
233 user_id: Some("test_user".to_string()),
234 session_id: uuid::Uuid::new_v4().to_string(),
235 };
236
237 let result = system.record_event(event).await;
238 assert!(result.is_ok());
239
240 let _ = std::fs::remove_dir_all(temp_dir);
242 }
243
244 #[tokio::test]
245 async fn test_get_statistics() {
246 let temp_dir = std::env::temp_dir().join("voirs_telemetry_test_stats");
247 let config = TelemetryConfig {
248 enabled: true,
249 level: TelemetryLevel::Standard,
250 storage_path: temp_dir.clone(),
251 anonymization: AnonymizationLevel::Low,
252 remote_endpoint: None,
253 batch_size: 100,
254 flush_interval_secs: 60,
255 };
256
257 let system = TelemetrySystem::new(config).await.unwrap();
258 let stats = system.get_statistics().await;
259 assert!(stats.is_ok());
260
261 let _ = std::fs::remove_dir_all(temp_dir);
263 }
264
265 #[tokio::test]
266 async fn test_clear_data() {
267 let temp_dir = std::env::temp_dir().join("voirs_telemetry_test_clear");
268 let config = TelemetryConfig {
269 enabled: true,
270 level: TelemetryLevel::Standard,
271 storage_path: temp_dir.clone(),
272 anonymization: AnonymizationLevel::Low,
273 remote_endpoint: None,
274 batch_size: 100,
275 flush_interval_secs: 60,
276 };
277
278 let system = TelemetrySystem::new(config).await.unwrap();
279 let result = system.clear_data().await;
280 assert!(result.is_ok());
281
282 let _ = std::fs::remove_dir_all(temp_dir);
284 }
285}