Skip to main content

torsh_package/
profiling.rs

1//! Performance profiling and monitoring utilities
2//!
3//! This module provides comprehensive profiling tools for tracking
4//! package operations, measuring performance, and identifying bottlenecks.
5
6use std::collections::HashMap;
7use std::sync::{Arc, Mutex};
8use std::time::Instant;
9
10use serde::{Deserialize, Serialize};
11
12/// Operation profiler that tracks performance metrics
13#[derive(Debug, Clone)]
14pub struct OperationProfiler {
15    entries: Arc<Mutex<Vec<ProfileEntry>>>,
16    active_operations: Arc<Mutex<HashMap<String, Instant>>>,
17}
18
19/// A single profiling entry
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ProfileEntry {
22    /// Operation name
23    pub name: String,
24    /// Duration in milliseconds
25    pub duration_ms: u64,
26    /// Start timestamp
27    pub start_time: std::time::SystemTime,
28    /// Memory delta (bytes allocated)
29    pub memory_delta: i64,
30    /// Additional metadata
31    pub metadata: HashMap<String, String>,
32}
33
34/// Profiling statistics for an operation
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ProfileStats {
37    /// Operation name
38    pub name: String,
39    /// Number of calls
40    pub count: usize,
41    /// Total duration
42    pub total_duration_ms: u64,
43    /// Average duration
44    pub avg_duration_ms: f64,
45    /// Minimum duration
46    pub min_duration_ms: u64,
47    /// Maximum duration
48    pub max_duration_ms: u64,
49    /// Standard deviation
50    pub std_dev_ms: f64,
51    /// 50th percentile (median)
52    pub p50_ms: u64,
53    /// 95th percentile
54    pub p95_ms: u64,
55    /// 99th percentile
56    pub p99_ms: u64,
57}
58
59/// Package operation types for profiling
60#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
61pub enum PackageOperation {
62    /// Package creation
63    Create,
64    /// Package loading
65    Load,
66    /// Package saving
67    Save,
68    /// Resource addition
69    AddResource,
70    /// Resource retrieval
71    GetResource,
72    /// Compression operation
73    Compress,
74    /// Decompression operation
75    Decompress,
76    /// Signing operation
77    Sign,
78    /// Verification operation
79    Verify,
80    /// Encryption operation
81    Encrypt,
82    /// Decryption operation
83    Decrypt,
84    /// Dependency resolution
85    ResolveDependencies,
86    /// Installation operation
87    Install,
88    /// Custom operation
89    Custom(String),
90}
91
92impl PackageOperation {
93    /// Get operation name as string
94    pub fn as_str(&self) -> &str {
95        match self {
96            Self::Create => "create",
97            Self::Load => "load",
98            Self::Save => "save",
99            Self::AddResource => "add_resource",
100            Self::GetResource => "get_resource",
101            Self::Compress => "compress",
102            Self::Decompress => "decompress",
103            Self::Sign => "sign",
104            Self::Verify => "verify",
105            Self::Encrypt => "encrypt",
106            Self::Decrypt => "decrypt",
107            Self::ResolveDependencies => "resolve_dependencies",
108            Self::Install => "install",
109            Self::Custom(name) => name,
110        }
111    }
112}
113
114impl OperationProfiler {
115    /// Create a new operation profiler
116    pub fn new() -> Self {
117        Self {
118            entries: Arc::new(Mutex::new(Vec::new())),
119            active_operations: Arc::new(Mutex::new(HashMap::new())),
120        }
121    }
122
123    /// Start profiling an operation
124    pub fn start(&self, operation: &str) {
125        let mut active = self
126            .active_operations
127            .lock()
128            .expect("lock should not be poisoned");
129        active.insert(operation.to_string(), Instant::now());
130    }
131
132    /// End profiling an operation
133    pub fn end(&self, operation: &str) {
134        self.end_with_metadata(operation, HashMap::new());
135    }
136
137    /// End profiling with additional metadata
138    pub fn end_with_metadata(&self, operation: &str, metadata: HashMap<String, String>) {
139        let mut active = self
140            .active_operations
141            .lock()
142            .expect("lock should not be poisoned");
143        if let Some(start) = active.remove(operation) {
144            let duration = start.elapsed();
145            let entry = ProfileEntry {
146                name: operation.to_string(),
147                duration_ms: duration.as_millis() as u64,
148                start_time: std::time::SystemTime::now() - duration,
149                memory_delta: 0, // Would require memory tracking integration
150                metadata,
151            };
152
153            let mut entries = self.entries.lock().expect("lock should not be poisoned");
154            entries.push(entry);
155        }
156    }
157
158    /// Get all profile entries
159    pub fn entries(&self) -> Vec<ProfileEntry> {
160        self.entries
161            .lock()
162            .expect("lock should not be poisoned")
163            .clone()
164    }
165
166    /// Clear all entries
167    pub fn clear(&self) {
168        self.entries
169            .lock()
170            .expect("lock should not be poisoned")
171            .clear();
172        self.active_operations
173            .lock()
174            .expect("lock should not be poisoned")
175            .clear();
176    }
177
178    /// Get statistics for a specific operation
179    pub fn stats_for(&self, operation: &str) -> Option<ProfileStats> {
180        let entries = self.entries.lock().expect("lock should not be poisoned");
181        let operation_entries: Vec<_> = entries.iter().filter(|e| e.name == operation).collect();
182
183        if operation_entries.is_empty() {
184            return None;
185        }
186
187        let count = operation_entries.len();
188        let durations: Vec<u64> = operation_entries.iter().map(|e| e.duration_ms).collect();
189
190        let total: u64 = durations.iter().sum();
191        let avg = total as f64 / count as f64;
192        let min = *durations.iter().min().expect("reduction should succeed");
193        let max = *durations.iter().max().expect("reduction should succeed");
194
195        // Calculate standard deviation
196        let variance = durations
197            .iter()
198            .map(|d| {
199                let diff = *d as f64 - avg;
200                diff * diff
201            })
202            .sum::<f64>()
203            / count as f64;
204        let std_dev = variance.sqrt();
205
206        // Calculate percentiles
207        let mut sorted_durations = durations.clone();
208        sorted_durations.sort_unstable();
209        let p50 = percentile(&sorted_durations, 50.0);
210        let p95 = percentile(&sorted_durations, 95.0);
211        let p99 = percentile(&sorted_durations, 99.0);
212
213        Some(ProfileStats {
214            name: operation.to_string(),
215            count,
216            total_duration_ms: total,
217            avg_duration_ms: avg,
218            min_duration_ms: min,
219            max_duration_ms: max,
220            std_dev_ms: std_dev,
221            p50_ms: p50,
222            p95_ms: p95,
223            p99_ms: p99,
224        })
225    }
226
227    /// Get statistics for all operations
228    pub fn all_stats(&self) -> Vec<ProfileStats> {
229        let entries = self.entries.lock().expect("lock should not be poisoned");
230        let mut operation_names: Vec<String> = entries.iter().map(|e| e.name.clone()).collect();
231        operation_names.sort();
232        operation_names.dedup();
233
234        drop(entries); // Release lock before calling stats_for
235
236        operation_names
237            .into_iter()
238            .filter_map(|name| self.stats_for(&name))
239            .collect()
240    }
241
242    /// Export profile report as JSON
243    pub fn export_json(&self) -> Result<String, serde_json::Error> {
244        let stats = self.all_stats();
245        serde_json::to_string_pretty(&stats)
246    }
247
248    /// Get total profiling time
249    pub fn total_time_ms(&self) -> u64 {
250        self.entries
251            .lock()
252            .expect("lock should not be poisoned")
253            .iter()
254            .map(|e| e.duration_ms)
255            .sum()
256    }
257
258    /// Get operation count
259    pub fn operation_count(&self) -> usize {
260        self.entries
261            .lock()
262            .expect("lock should not be poisoned")
263            .len()
264    }
265}
266
267impl Default for OperationProfiler {
268    fn default() -> Self {
269        Self::new()
270    }
271}
272
273/// Calculate percentile from sorted data
274fn percentile(sorted_data: &[u64], p: f64) -> u64 {
275    if sorted_data.is_empty() {
276        return 0;
277    }
278    let index = ((p / 100.0) * (sorted_data.len() - 1) as f64).round() as usize;
279    sorted_data[index.min(sorted_data.len() - 1)]
280}
281
282/// RAII guard for automatic profiling
283pub struct ProfileGuard<'a> {
284    profiler: &'a OperationProfiler,
285    operation: String,
286    metadata: HashMap<String, String>,
287}
288
289impl<'a> ProfileGuard<'a> {
290    /// Create a new profile guard
291    pub fn new(profiler: &'a OperationProfiler, operation: impl Into<String>) -> Self {
292        let operation = operation.into();
293        profiler.start(&operation);
294        Self {
295            profiler,
296            operation,
297            metadata: HashMap::new(),
298        }
299    }
300
301    /// Add metadata to the profile entry
302    pub fn add_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
303        self.metadata.insert(key.into(), value.into());
304    }
305}
306
307impl<'a> Drop for ProfileGuard<'a> {
308    fn drop(&mut self) {
309        self.profiler
310            .end_with_metadata(&self.operation, self.metadata.clone());
311    }
312}
313
314/// Global profiler instance
315static GLOBAL_PROFILER: once_cell::sync::Lazy<OperationProfiler> =
316    once_cell::sync::Lazy::new(OperationProfiler::new);
317
318/// Get the global profiler instance
319pub fn global_profiler() -> &'static OperationProfiler {
320    &GLOBAL_PROFILER
321}
322
323/// Profile a function execution
324pub fn profile<F, R>(operation: &str, f: F) -> R
325where
326    F: FnOnce() -> R,
327{
328    let _guard = ProfileGuard::new(&GLOBAL_PROFILER, operation);
329    f()
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335    use std::time::Duration;
336
337    #[test]
338    fn test_profiler_basic() {
339        let profiler = OperationProfiler::new();
340
341        profiler.start("test_op");
342        std::thread::sleep(Duration::from_millis(10));
343        profiler.end("test_op");
344
345        let entries = profiler.entries();
346        assert_eq!(entries.len(), 1);
347        assert_eq!(entries[0].name, "test_op");
348        assert!(entries[0].duration_ms >= 10);
349    }
350
351    #[test]
352    fn test_profiler_multiple_operations() {
353        let profiler = OperationProfiler::new();
354
355        for _ in 0..5 {
356            profiler.start("op");
357            std::thread::sleep(Duration::from_millis(5));
358            profiler.end("op");
359        }
360
361        let stats = profiler.stats_for("op").unwrap();
362        assert_eq!(stats.count, 5);
363        assert!(stats.avg_duration_ms >= 5.0);
364        assert!(stats.total_duration_ms >= 25);
365    }
366
367    #[test]
368    fn test_profiler_stats() {
369        let profiler = OperationProfiler::new();
370
371        // Add entries with known durations
372        profiler.start("test");
373        std::thread::sleep(Duration::from_millis(10));
374        profiler.end("test");
375
376        profiler.start("test");
377        std::thread::sleep(Duration::from_millis(20));
378        profiler.end("test");
379
380        let stats = profiler.stats_for("test").unwrap();
381        assert_eq!(stats.count, 2);
382        assert!(stats.min_duration_ms >= 10);
383        assert!(stats.max_duration_ms >= 20);
384        assert!(stats.avg_duration_ms >= 15.0);
385    }
386
387    #[test]
388    fn test_profile_guard() {
389        let profiler = OperationProfiler::new();
390
391        {
392            let _guard = ProfileGuard::new(&profiler, "guarded_op");
393            std::thread::sleep(Duration::from_millis(10));
394        } // Guard drops here, automatically recording the profile
395
396        let entries = profiler.entries();
397        assert_eq!(entries.len(), 1);
398        assert_eq!(entries[0].name, "guarded_op");
399    }
400
401    #[test]
402    fn test_profile_guard_with_metadata() {
403        let profiler = OperationProfiler::new();
404
405        {
406            let mut guard = ProfileGuard::new(&profiler, "meta_op");
407            guard.add_metadata("key", "value");
408            std::thread::sleep(Duration::from_millis(5));
409        }
410
411        let entries = profiler.entries();
412        assert_eq!(entries.len(), 1);
413        assert_eq!(entries[0].metadata.get("key").unwrap(), "value");
414    }
415
416    #[test]
417    fn test_profiler_clear() {
418        let profiler = OperationProfiler::new();
419
420        profiler.start("op");
421        profiler.end("op");
422        assert_eq!(profiler.entries().len(), 1);
423
424        profiler.clear();
425        assert_eq!(profiler.entries().len(), 0);
426    }
427
428    #[test]
429    fn test_profiler_export_json() {
430        let profiler = OperationProfiler::new();
431
432        profiler.start("test");
433        std::thread::sleep(Duration::from_millis(5));
434        profiler.end("test");
435
436        let json = profiler.export_json().unwrap();
437        assert!(json.contains("test"));
438        assert!(json.contains("count"));
439        // Verify it's valid JSON
440        let _parsed: Vec<ProfileStats> = serde_json::from_str(&json).unwrap();
441    }
442
443    #[test]
444    fn test_package_operation_names() {
445        assert_eq!(PackageOperation::Create.as_str(), "create");
446        assert_eq!(PackageOperation::Load.as_str(), "load");
447        assert_eq!(PackageOperation::Compress.as_str(), "compress");
448        assert_eq!(
449            PackageOperation::Custom("custom_op".to_string()).as_str(),
450            "custom_op"
451        );
452    }
453
454    #[test]
455    fn test_global_profiler() {
456        global_profiler().clear(); // Clear any previous entries
457
458        let result = profile("global_test", || {
459            std::thread::sleep(Duration::from_millis(5));
460            42
461        });
462
463        assert_eq!(result, 42);
464        let entries = global_profiler().entries();
465        assert!(!entries.is_empty());
466    }
467
468    #[test]
469    fn test_percentile_calculation() {
470        let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
471        assert_eq!(percentile(&data, 0.0), 1);
472        // 50th percentile of 10 items: index = 0.5 * 9 = 4.5, rounded = 5, value = 6
473        assert_eq!(percentile(&data, 50.0), 6);
474        assert_eq!(percentile(&data, 100.0), 10);
475
476        // Test with smaller dataset
477        let small = vec![1, 2, 3];
478        assert_eq!(percentile(&small, 0.0), 1);
479        assert_eq!(percentile(&small, 50.0), 2);
480        assert_eq!(percentile(&small, 100.0), 3);
481    }
482}