Skip to main content

voirs_cli/telemetry/
events.rs

1//! Telemetry event definitions
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Telemetry event
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct TelemetryEvent {
9    /// Unique event ID
10    pub id: String,
11
12    /// Event type
13    pub event_type: EventType,
14
15    /// Event timestamp
16    pub timestamp: chrono::DateTime<chrono::Utc>,
17
18    /// Event metadata
19    pub metadata: EventMetadata,
20
21    /// User ID (anonymized if privacy controls enabled)
22    pub user_id: Option<String>,
23
24    /// Session ID
25    pub session_id: String,
26}
27
28impl TelemetryEvent {
29    /// Create a new telemetry event
30    pub fn new(event_type: EventType) -> Self {
31        Self {
32            id: uuid::Uuid::new_v4().to_string(),
33            event_type,
34            timestamp: chrono::Utc::now(),
35            metadata: EventMetadata::default(),
36            user_id: None,
37            session_id: uuid::Uuid::new_v4().to_string(),
38        }
39    }
40
41    /// Create a command execution event
42    pub fn command_executed(command: String, duration_ms: u64) -> Self {
43        let mut event = Self::new(EventType::CommandExecuted);
44        event.metadata.set("command", command);
45        event.metadata.set("duration_ms", duration_ms.to_string());
46        event
47    }
48
49    /// Create a synthesis request event
50    pub fn synthesis_request(
51        voice: String,
52        text_length: usize,
53        duration_ms: u64,
54        success: bool,
55    ) -> Self {
56        let mut event = Self::new(EventType::SynthesisRequest);
57        event.metadata.set("voice", voice);
58        event.metadata.set("text_length", text_length.to_string());
59        event.metadata.set("duration_ms", duration_ms.to_string());
60        event.metadata.set("success", success.to_string());
61        event
62    }
63
64    /// Create an error event
65    pub fn error(error_type: String, message: String, severity: ErrorSeverity) -> Self {
66        let mut event = Self::new(EventType::Error);
67        event.metadata.set("error_type", error_type);
68        event.metadata.set("message", message);
69        event.metadata.set("severity", severity.to_string());
70        event
71    }
72
73    /// Create a performance event
74    pub fn performance(metric_name: String, value: f64, unit: String) -> Self {
75        let mut event = Self::new(EventType::Performance);
76        event.metadata.set("metric_name", metric_name);
77        event.metadata.set("value", value.to_string());
78        event.metadata.set("unit", unit);
79        event
80    }
81
82    /// Set user ID
83    pub fn with_user_id(mut self, user_id: String) -> Self {
84        self.user_id = Some(user_id);
85        self
86    }
87
88    /// Set session ID
89    pub fn with_session_id(mut self, session_id: String) -> Self {
90        self.session_id = session_id;
91        self
92    }
93
94    /// Add metadata
95    pub fn with_metadata(mut self, key: String, value: String) -> Self {
96        self.metadata.set(key, value);
97        self
98    }
99}
100
101/// Event type enumeration
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
103pub enum EventType {
104    /// Command was executed
105    CommandExecuted,
106
107    /// Synthesis request
108    SynthesisRequest,
109
110    /// Error occurred
111    Error,
112
113    /// Performance metric
114    Performance,
115
116    /// Configuration changed
117    ConfigurationChanged,
118
119    /// Model loaded
120    ModelLoaded,
121
122    /// Voice changed
123    VoiceChanged,
124
125    /// Application started
126    ApplicationStarted,
127
128    /// Application stopped
129    ApplicationStopped,
130
131    /// Feature used
132    FeatureUsed,
133
134    /// Custom event
135    Custom,
136}
137
138impl std::fmt::Display for EventType {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        match self {
141            EventType::CommandExecuted => write!(f, "command_executed"),
142            EventType::SynthesisRequest => write!(f, "synthesis_request"),
143            EventType::Error => write!(f, "error"),
144            EventType::Performance => write!(f, "performance"),
145            EventType::ConfigurationChanged => write!(f, "configuration_changed"),
146            EventType::ModelLoaded => write!(f, "model_loaded"),
147            EventType::VoiceChanged => write!(f, "voice_changed"),
148            EventType::ApplicationStarted => write!(f, "application_started"),
149            EventType::ApplicationStopped => write!(f, "application_stopped"),
150            EventType::FeatureUsed => write!(f, "feature_used"),
151            EventType::Custom => write!(f, "custom"),
152        }
153    }
154}
155
156/// Event metadata container
157#[derive(Debug, Clone, Default, Serialize, Deserialize)]
158pub struct EventMetadata {
159    data: HashMap<String, String>,
160}
161
162impl EventMetadata {
163    /// Create new empty metadata
164    pub fn new() -> Self {
165        Self {
166            data: HashMap::new(),
167        }
168    }
169
170    /// Set a metadata value
171    pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
172        self.data.insert(key.into(), value.into());
173    }
174
175    /// Get a metadata value
176    pub fn get(&self, key: &str) -> Option<&String> {
177        self.data.get(key)
178    }
179
180    /// Check if metadata contains a key
181    pub fn contains(&self, key: &str) -> bool {
182        self.data.contains_key(key)
183    }
184
185    /// Get all metadata keys
186    pub fn keys(&self) -> impl Iterator<Item = &String> {
187        self.data.keys()
188    }
189
190    /// Get all metadata as hashmap
191    pub fn as_map(&self) -> &HashMap<String, String> {
192        &self.data
193    }
194
195    /// Remove a metadata value
196    pub fn remove(&mut self, key: &str) -> Option<String> {
197        self.data.remove(key)
198    }
199
200    /// Clear all metadata
201    pub fn clear(&mut self) {
202        self.data.clear();
203    }
204
205    /// Number of metadata entries
206    pub fn len(&self) -> usize {
207        self.data.len()
208    }
209
210    /// Check if metadata is empty
211    pub fn is_empty(&self) -> bool {
212        self.data.is_empty()
213    }
214}
215
216/// Error severity levels
217#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
218pub enum ErrorSeverity {
219    /// Informational message
220    Info,
221
222    /// Warning condition
223    Warning,
224
225    /// Error condition
226    Error,
227
228    /// Critical error
229    Critical,
230
231    /// Fatal error
232    Fatal,
233}
234
235impl std::fmt::Display for ErrorSeverity {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        match self {
238            ErrorSeverity::Info => write!(f, "info"),
239            ErrorSeverity::Warning => write!(f, "warning"),
240            ErrorSeverity::Error => write!(f, "error"),
241            ErrorSeverity::Critical => write!(f, "critical"),
242            ErrorSeverity::Fatal => write!(f, "fatal"),
243        }
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    #[test]
252    fn test_event_creation() {
253        let event = TelemetryEvent::new(EventType::CommandExecuted);
254        assert_eq!(event.event_type, EventType::CommandExecuted);
255        assert!(!event.id.is_empty());
256        assert!(!event.session_id.is_empty());
257    }
258
259    #[test]
260    fn test_command_executed_event() {
261        let event = TelemetryEvent::command_executed("synthesize".to_string(), 1500);
262        assert_eq!(event.event_type, EventType::CommandExecuted);
263        assert_eq!(
264            event
265                .metadata
266                .get("command")
267                .expect("command metadata should exist"),
268            "synthesize"
269        );
270        assert_eq!(
271            event
272                .metadata
273                .get("duration_ms")
274                .expect("duration_ms metadata should exist"),
275            "1500"
276        );
277    }
278
279    #[test]
280    fn test_synthesis_request_event() {
281        let event = TelemetryEvent::synthesis_request("kokoro-en".to_string(), 100, 2000, true);
282        assert_eq!(event.event_type, EventType::SynthesisRequest);
283        assert_eq!(
284            event
285                .metadata
286                .get("voice")
287                .expect("voice metadata should exist"),
288            "kokoro-en"
289        );
290        assert_eq!(
291            event
292                .metadata
293                .get("text_length")
294                .expect("text_length metadata should exist"),
295            "100"
296        );
297        assert_eq!(
298            event
299                .metadata
300                .get("duration_ms")
301                .expect("duration_ms metadata should exist"),
302            "2000"
303        );
304        assert_eq!(
305            event
306                .metadata
307                .get("success")
308                .expect("success metadata should exist"),
309            "true"
310        );
311    }
312
313    #[test]
314    fn test_error_event() {
315        let event = TelemetryEvent::error(
316            "synthesis_error".to_string(),
317            "Failed to load model".to_string(),
318            ErrorSeverity::Error,
319        );
320        assert_eq!(event.event_type, EventType::Error);
321        assert_eq!(
322            event
323                .metadata
324                .get("error_type")
325                .expect("error_type metadata should exist"),
326            "synthesis_error"
327        );
328        assert_eq!(
329            event
330                .metadata
331                .get("message")
332                .expect("message metadata should exist"),
333            "Failed to load model"
334        );
335        assert_eq!(
336            event
337                .metadata
338                .get("severity")
339                .expect("severity metadata should exist"),
340            "error"
341        );
342    }
343
344    #[test]
345    fn test_performance_event() {
346        let event = TelemetryEvent::performance("rtf".to_string(), 0.25, "ratio".to_string());
347        assert_eq!(event.event_type, EventType::Performance);
348        assert_eq!(
349            event
350                .metadata
351                .get("metric_name")
352                .expect("metric_name metadata should exist"),
353            "rtf"
354        );
355        assert_eq!(
356            event
357                .metadata
358                .get("value")
359                .expect("value metadata should exist"),
360            "0.25"
361        );
362        assert_eq!(
363            event
364                .metadata
365                .get("unit")
366                .expect("unit metadata should exist"),
367            "ratio"
368        );
369    }
370
371    #[test]
372    fn test_event_builder() {
373        let event = TelemetryEvent::new(EventType::Custom)
374            .with_user_id("user123".to_string())
375            .with_session_id("session456".to_string())
376            .with_metadata("key".to_string(), "value".to_string());
377
378        assert_eq!(event.user_id.expect("user_id should be set"), "user123");
379        assert_eq!(event.session_id, "session456");
380        assert_eq!(
381            event
382                .metadata
383                .get("key")
384                .expect("key metadata should exist"),
385            "value"
386        );
387    }
388
389    #[test]
390    fn test_event_metadata() {
391        let mut metadata = EventMetadata::new();
392        assert!(metadata.is_empty());
393
394        metadata.set("key1", "value1");
395        metadata.set("key2", "value2");
396        assert_eq!(metadata.len(), 2);
397        assert!(metadata.contains("key1"));
398        assert_eq!(
399            metadata.get("key1").expect("key1 metadata should exist"),
400            "value1"
401        );
402
403        metadata.remove("key1");
404        assert_eq!(metadata.len(), 1);
405        assert!(!metadata.contains("key1"));
406
407        metadata.clear();
408        assert!(metadata.is_empty());
409    }
410
411    #[test]
412    fn test_event_type_display() {
413        assert_eq!(EventType::CommandExecuted.to_string(), "command_executed");
414        assert_eq!(EventType::SynthesisRequest.to_string(), "synthesis_request");
415        assert_eq!(EventType::Error.to_string(), "error");
416    }
417
418    #[test]
419    fn test_error_severity_display() {
420        assert_eq!(ErrorSeverity::Info.to_string(), "info");
421        assert_eq!(ErrorSeverity::Warning.to_string(), "warning");
422        assert_eq!(ErrorSeverity::Error.to_string(), "error");
423        assert_eq!(ErrorSeverity::Critical.to_string(), "critical");
424        assert_eq!(ErrorSeverity::Fatal.to_string(), "fatal");
425    }
426}