Skip to main content

oxigdal_observability/
profiling.rs

1//! Performance profiling and monitoring.
2
3use chrono::{DateTime, Utc};
4use parking_lot::RwLock;
5use std::collections::HashMap;
6use std::sync::Arc;
7use std::time::Instant;
8
9/// Performance profiler for tracking operation durations.
10pub struct Profiler {
11    profiles: Arc<RwLock<HashMap<String, Vec<ProfileSample>>>>,
12}
13
14/// Profile sample with timing information.
15#[derive(Debug, Clone)]
16pub struct ProfileSample {
17    /// Name of the profiled operation.
18    pub operation: String,
19    /// Duration of the operation in milliseconds.
20    pub duration_ms: f64,
21    /// Timestamp when the sample was recorded.
22    pub timestamp: DateTime<Utc>,
23    /// Additional metadata associated with the sample.
24    pub metadata: HashMap<String, String>,
25}
26
27impl Profiler {
28    /// Create a new profiler.
29    pub fn new() -> Self {
30        Self {
31            profiles: Arc::new(RwLock::new(HashMap::new())),
32        }
33    }
34
35    /// Start profiling an operation.
36    pub fn start(&self, operation: impl Into<String>) -> ProfileGuard {
37        ProfileGuard {
38            operation: operation.into(),
39            start: Instant::now(),
40            profiler: self.profiles.clone(),
41            metadata: HashMap::new(),
42        }
43    }
44
45    /// Get profile samples for an operation.
46    pub fn get_samples(&self, operation: &str) -> Vec<ProfileSample> {
47        self.profiles
48            .read()
49            .get(operation)
50            .cloned()
51            .unwrap_or_default()
52    }
53
54    /// Get statistics for an operation.
55    pub fn get_stats(&self, operation: &str) -> Option<ProfileStats> {
56        let samples = self.get_samples(operation);
57        if samples.is_empty() {
58            return None;
59        }
60
61        let durations: Vec<f64> = samples.iter().map(|s| s.duration_ms).collect();
62        let count = durations.len();
63        let sum: f64 = durations.iter().sum();
64        let mean = sum / count as f64;
65
66        let variance: f64 =
67            durations.iter().map(|d| (d - mean).powi(2)).sum::<f64>() / count as f64;
68
69        let std_dev = variance.sqrt();
70
71        let mut sorted = durations.clone();
72        sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
73
74        let min = sorted[0];
75        let max = sorted[count - 1];
76        let p50 = sorted[count / 2];
77        let p95 = sorted[(count * 95) / 100];
78        let p99 = sorted[(count * 99) / 100];
79
80        Some(ProfileStats {
81            operation: operation.to_string(),
82            count,
83            mean,
84            std_dev,
85            min,
86            max,
87            p50,
88            p95,
89            p99,
90        })
91    }
92
93    /// Clear all profile data.
94    pub fn clear(&self) {
95        self.profiles.write().clear();
96    }
97}
98
99impl Default for Profiler {
100    fn default() -> Self {
101        Self::new()
102    }
103}
104
105/// Guard for automatic profiling.
106pub struct ProfileGuard {
107    operation: String,
108    start: Instant,
109    profiler: Arc<RwLock<HashMap<String, Vec<ProfileSample>>>>,
110    metadata: HashMap<String, String>,
111}
112
113impl ProfileGuard {
114    /// Add metadata to the profile sample.
115    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
116        self.metadata.insert(key.into(), value.into());
117        self
118    }
119}
120
121impl Drop for ProfileGuard {
122    fn drop(&mut self) {
123        let duration = self.start.elapsed();
124        let sample = ProfileSample {
125            operation: self.operation.clone(),
126            duration_ms: duration.as_secs_f64() * 1000.0,
127            timestamp: Utc::now(),
128            metadata: self.metadata.clone(),
129        };
130
131        let mut profiles = self.profiler.write();
132        profiles
133            .entry(self.operation.clone())
134            .or_default()
135            .push(sample);
136    }
137}
138
139/// Profile statistics.
140#[derive(Debug, Clone)]
141pub struct ProfileStats {
142    /// Name of the profiled operation.
143    pub operation: String,
144    /// Number of samples collected.
145    pub count: usize,
146    /// Mean duration in milliseconds.
147    pub mean: f64,
148    /// Standard deviation of durations.
149    pub std_dev: f64,
150    /// Minimum duration in milliseconds.
151    pub min: f64,
152    /// Maximum duration in milliseconds.
153    pub max: f64,
154    /// 50th percentile (median) duration.
155    pub p50: f64,
156    /// 95th percentile duration.
157    pub p95: f64,
158    /// 99th percentile duration.
159    pub p99: f64,
160}
161
162impl std::fmt::Display for ProfileStats {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        write!(
165            f,
166            "Operation: {}\n\
167             Count: {}\n\
168             Mean: {:.2}ms\n\
169             StdDev: {:.2}ms\n\
170             Min: {:.2}ms\n\
171             Max: {:.2}ms\n\
172             P50: {:.2}ms\n\
173             P95: {:.2}ms\n\
174             P99: {:.2}ms",
175            self.operation,
176            self.count,
177            self.mean,
178            self.std_dev,
179            self.min,
180            self.max,
181            self.p50,
182            self.p95,
183            self.p99
184        )
185    }
186}
187
188/// Memory profiler for tracking allocations.
189pub struct MemoryProfiler {
190    snapshots: Arc<RwLock<Vec<MemorySnapshot>>>,
191}
192
193/// Memory snapshot.
194#[derive(Debug, Clone)]
195pub struct MemorySnapshot {
196    /// Timestamp when the snapshot was taken.
197    pub timestamp: DateTime<Utc>,
198    /// Total bytes allocated at this point.
199    pub allocated_bytes: u64,
200    /// Total bytes deallocated at this point.
201    pub deallocated_bytes: u64,
202    /// Number of active allocations.
203    pub active_allocations: u64,
204    /// Operation associated with this snapshot.
205    pub operation: String,
206}
207
208impl MemoryProfiler {
209    /// Create a new memory profiler.
210    pub fn new() -> Self {
211        Self {
212            snapshots: Arc::new(RwLock::new(Vec::new())),
213        }
214    }
215
216    /// Take a memory snapshot.
217    pub fn snapshot(&self, operation: impl Into<String>) {
218        // In production, this would use actual memory allocation tracking
219        let snapshot = MemorySnapshot {
220            timestamp: Utc::now(),
221            allocated_bytes: 0,
222            deallocated_bytes: 0,
223            active_allocations: 0,
224            operation: operation.into(),
225        };
226
227        self.snapshots.write().push(snapshot);
228    }
229
230    /// Get all snapshots.
231    pub fn get_snapshots(&self) -> Vec<MemorySnapshot> {
232        self.snapshots.read().clone()
233    }
234
235    /// Clear all snapshots.
236    pub fn clear(&self) {
237        self.snapshots.write().clear();
238    }
239}
240
241impl Default for MemoryProfiler {
242    fn default() -> Self {
243        Self::new()
244    }
245}
246
247/// CPU profiler for tracking CPU usage.
248pub struct CpuProfiler {
249    samples: Arc<RwLock<Vec<CpuSample>>>,
250}
251
252/// CPU usage sample.
253#[derive(Debug, Clone)]
254pub struct CpuSample {
255    /// Timestamp when the sample was taken.
256    pub timestamp: DateTime<Utc>,
257    /// CPU usage percentage (0.0 to 100.0).
258    pub cpu_percent: f64,
259    /// Operation associated with this sample.
260    pub operation: String,
261}
262
263impl CpuProfiler {
264    /// Create a new CPU profiler.
265    pub fn new() -> Self {
266        Self {
267            samples: Arc::new(RwLock::new(Vec::new())),
268        }
269    }
270
271    /// Record CPU usage.
272    pub fn record(&self, operation: impl Into<String>, cpu_percent: f64) {
273        let sample = CpuSample {
274            timestamp: Utc::now(),
275            cpu_percent,
276            operation: operation.into(),
277        };
278
279        self.samples.write().push(sample);
280    }
281
282    /// Get all samples.
283    pub fn get_samples(&self) -> Vec<CpuSample> {
284        self.samples.read().clone()
285    }
286
287    /// Get average CPU usage for an operation.
288    pub fn get_average(&self, operation: &str) -> Option<f64> {
289        let samples: Vec<CpuSample> = self
290            .samples
291            .read()
292            .iter()
293            .filter(|s| s.operation == operation)
294            .cloned()
295            .collect();
296
297        if samples.is_empty() {
298            return None;
299        }
300
301        let sum: f64 = samples.iter().map(|s| s.cpu_percent).sum();
302        Some(sum / samples.len() as f64)
303    }
304}
305
306impl Default for CpuProfiler {
307    fn default() -> Self {
308        Self::new()
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315    use std::thread;
316    use std::time::Duration;
317
318    #[test]
319    fn test_profiler() {
320        let profiler = Profiler::new();
321
322        {
323            let _guard = profiler.start("test_operation");
324            thread::sleep(Duration::from_millis(10));
325        }
326
327        let samples = profiler.get_samples("test_operation");
328        assert_eq!(samples.len(), 1);
329        assert!(samples[0].duration_ms >= 10.0);
330    }
331
332    #[test]
333    fn test_profiler_stats() {
334        let profiler = Profiler::new();
335
336        for _ in 0..10 {
337            let _guard = profiler.start("test_op");
338            thread::sleep(Duration::from_millis(1));
339        }
340
341        let stats = profiler.get_stats("test_op");
342        assert!(stats.is_some());
343
344        let stats = stats.expect("No stats");
345        assert_eq!(stats.count, 10);
346        assert!(stats.mean > 0.0);
347    }
348
349    #[test]
350    fn test_memory_profiler() {
351        let profiler = MemoryProfiler::new();
352        profiler.snapshot("test_operation");
353
354        assert_eq!(profiler.get_snapshots().len(), 1);
355    }
356
357    #[test]
358    fn test_cpu_profiler() {
359        let profiler = CpuProfiler::new();
360        profiler.record("test_op", 50.0);
361        profiler.record("test_op", 60.0);
362
363        let avg = profiler.get_average("test_op");
364        assert_eq!(avg, Some(55.0));
365    }
366}