oxify_connect_vision/
profiling.rs

1//! Performance Profiling for OCR Operations
2//!
3//! This module provides performance profiling capabilities including CPU profiling,
4//! memory profiling, and bottleneck detection for OCR operations.
5//!
6//! # Features
7//!
8//! - CPU time profiling with hierarchical call tracking
9//! - Memory usage profiling (heap allocations)
10//! - Flamegraph generation data
11//! - Bottleneck detection and analysis
12//! - Operation timing with statistics
13//! - Resource usage tracking
14//!
15//! # Example
16//!
17//! ```rust,ignore
18//! use oxify_connect_vision::profiling::{Profiler, ProfilerConfig};
19//!
20//! let config = ProfilerConfig::default();
21//! let profiler = Profiler::new(config);
22//!
23//! // Profile an operation
24//! profiler.start_profile("process_image");
25//! let result = process_image(&bytes).await?;
26//! profiler.end_profile("process_image");
27//!
28//! // Get profiling results
29//! let report = profiler.generate_report();
30//! println!("{}", report.summary());
31//! ```
32
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::sync::{Arc, Mutex};
36use std::time::{Duration, Instant};
37use thiserror::Error;
38
39/// Profiling errors
40#[derive(Debug, Error)]
41pub enum ProfilingError {
42    #[error("Profile not found: {0}")]
43    ProfileNotFound(String),
44
45    #[error("Profile already started: {0}")]
46    ProfileAlreadyStarted(String),
47
48    #[error("Invalid configuration: {0}")]
49    ConfigError(String),
50
51    #[error("Failed to generate report: {0}")]
52    ReportError(String),
53}
54
55pub type Result<T> = std::result::Result<T, ProfilingError>;
56
57/// Profiler configuration
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ProfilerConfig {
60    /// Enable CPU profiling
61    pub enable_cpu_profiling: bool,
62
63    /// Enable memory profiling
64    pub enable_memory_profiling: bool,
65
66    /// Enable call hierarchy tracking
67    pub enable_call_hierarchy: bool,
68
69    /// Maximum call stack depth
70    pub max_call_depth: usize,
71
72    /// Sample interval for periodic sampling (milliseconds)
73    pub sample_interval_ms: u64,
74
75    /// Track individual allocations
76    pub track_allocations: bool,
77}
78
79impl Default for ProfilerConfig {
80    fn default() -> Self {
81        Self {
82            enable_cpu_profiling: true,
83            enable_memory_profiling: true,
84            enable_call_hierarchy: true,
85            max_call_depth: 32,
86            sample_interval_ms: 10,
87            track_allocations: false,
88        }
89    }
90}
91
92impl ProfilerConfig {
93    /// Create a new configuration
94    pub fn new() -> Self {
95        Self::default()
96    }
97
98    /// Enable CPU profiling
99    pub fn with_cpu_profiling(mut self, enabled: bool) -> Self {
100        self.enable_cpu_profiling = enabled;
101        self
102    }
103
104    /// Enable memory profiling
105    pub fn with_memory_profiling(mut self, enabled: bool) -> Self {
106        self.enable_memory_profiling = enabled;
107        self
108    }
109
110    /// Enable call hierarchy tracking
111    pub fn with_call_hierarchy(mut self, enabled: bool) -> Self {
112        self.enable_call_hierarchy = enabled;
113        self
114    }
115
116    /// Set maximum call depth
117    pub fn with_max_call_depth(mut self, depth: usize) -> Self {
118        self.max_call_depth = depth;
119        self
120    }
121}
122
123/// Profile entry representing a single profiled operation
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ProfileEntry {
126    /// Operation name
127    pub name: String,
128
129    /// Start timestamp
130    pub start_time: std::time::SystemTime,
131
132    /// Duration
133    pub duration: Duration,
134
135    /// CPU time (may differ from duration due to parallelism)
136    pub cpu_time: Duration,
137
138    /// Memory allocated (bytes)
139    pub memory_allocated: u64,
140
141    /// Memory deallocated (bytes)
142    pub memory_deallocated: u64,
143
144    /// Peak memory usage (bytes)
145    pub peak_memory: u64,
146
147    /// Number of invocations
148    pub invocations: u64,
149
150    /// Parent operation (for call hierarchy)
151    pub parent: Option<String>,
152
153    /// Child operations
154    pub children: Vec<String>,
155}
156
157impl ProfileEntry {
158    /// Create a new profile entry
159    fn new(name: impl Into<String>) -> Self {
160        Self {
161            name: name.into(),
162            start_time: std::time::SystemTime::now(),
163            duration: Duration::ZERO,
164            cpu_time: Duration::ZERO,
165            memory_allocated: 0,
166            memory_deallocated: 0,
167            peak_memory: 0,
168            invocations: 0,
169            parent: None,
170            children: Vec::new(),
171        }
172    }
173
174    /// Get net memory usage
175    pub fn net_memory(&self) -> i64 {
176        self.memory_allocated as i64 - self.memory_deallocated as i64
177    }
178
179    /// Get average duration per invocation
180    pub fn avg_duration(&self) -> Duration {
181        if self.invocations == 0 {
182            Duration::ZERO
183        } else {
184            self.duration / self.invocations as u32
185        }
186    }
187}
188
189/// Active profile tracking
190#[derive(Debug)]
191struct ActiveProfile {
192    /// Profile name
193    #[allow(dead_code)]
194    name: String,
195
196    /// Start time
197    start_time: Instant,
198
199    /// Start memory (if tracked)
200    start_memory: u64,
201
202    /// Parent profile
203    parent: Option<String>,
204}
205
206/// Call stack for hierarchy tracking
207#[derive(Debug, Default)]
208struct CallStack {
209    /// Stack of active operations
210    stack: Vec<String>,
211}
212
213impl CallStack {
214    /// Push an operation onto the stack
215    fn push(&mut self, name: String) -> Option<String> {
216        let parent = self.stack.last().cloned();
217        self.stack.push(name);
218        parent
219    }
220
221    /// Pop an operation from the stack
222    fn pop(&mut self) -> Option<String> {
223        self.stack.pop()
224    }
225
226    /// Get current depth
227    fn depth(&self) -> usize {
228        self.stack.len()
229    }
230
231    /// Get current operation
232    #[allow(dead_code)]
233    fn current(&self) -> Option<&String> {
234        self.stack.last()
235    }
236}
237
238/// Memory snapshot
239#[derive(Debug, Clone, Default, Serialize, Deserialize)]
240pub struct MemorySnapshot {
241    /// Total allocated bytes
242    pub total_allocated: u64,
243
244    /// Total deallocated bytes
245    pub total_deallocated: u64,
246
247    /// Current usage (allocated - deallocated)
248    pub current_usage: u64,
249
250    /// Peak usage
251    pub peak_usage: u64,
252
253    /// Number of active allocations
254    pub active_allocations: u64,
255}
256
257impl MemorySnapshot {
258    /// Get current system memory info (simplified)
259    pub fn current() -> Self {
260        // In a real implementation, this would query system memory
261        // For now, return a placeholder
262        Self::default()
263    }
264}
265
266/// Performance profiler
267#[derive(Debug)]
268pub struct Profiler {
269    /// Configuration
270    config: ProfilerConfig,
271
272    /// Completed profiles
273    profiles: Arc<Mutex<HashMap<String, ProfileEntry>>>,
274
275    /// Active profiles
276    active: Arc<Mutex<HashMap<String, ActiveProfile>>>,
277
278    /// Call stack for hierarchy
279    call_stack: Arc<Mutex<CallStack>>,
280
281    /// Memory snapshots
282    memory_snapshots: Arc<Mutex<Vec<MemorySnapshot>>>,
283
284    /// Start time
285    start_time: Instant,
286}
287
288impl Profiler {
289    /// Create a new profiler
290    pub fn new(config: ProfilerConfig) -> Self {
291        Self {
292            config,
293            profiles: Arc::new(Mutex::new(HashMap::new())),
294            active: Arc::new(Mutex::new(HashMap::new())),
295            call_stack: Arc::new(Mutex::new(CallStack::default())),
296            memory_snapshots: Arc::new(Mutex::new(Vec::new())),
297            start_time: Instant::now(),
298        }
299    }
300
301    /// Start profiling an operation
302    pub fn start_profile(&self, name: impl Into<String>) -> Result<()> {
303        let name = name.into();
304
305        if !self.config.enable_cpu_profiling {
306            return Ok(());
307        }
308
309        let mut active = self.active.lock().unwrap();
310
311        if active.contains_key(&name) {
312            return Err(ProfilingError::ProfileAlreadyStarted(name));
313        }
314
315        // Get parent from call stack
316        let parent = if self.config.enable_call_hierarchy {
317            let mut stack = self.call_stack.lock().unwrap();
318            if stack.depth() >= self.config.max_call_depth {
319                None
320            } else {
321                stack.push(name.clone())
322            }
323        } else {
324            None
325        };
326
327        // Take memory snapshot if enabled
328        let start_memory = if self.config.enable_memory_profiling {
329            let snapshot = MemorySnapshot::current();
330            snapshot.current_usage
331        } else {
332            0
333        };
334
335        active.insert(
336            name.clone(),
337            ActiveProfile {
338                name,
339                start_time: Instant::now(),
340                start_memory,
341                parent,
342            },
343        );
344
345        Ok(())
346    }
347
348    /// End profiling an operation
349    pub fn end_profile(&self, name: impl Into<String>) -> Result<()> {
350        let name = name.into();
351
352        if !self.config.enable_cpu_profiling {
353            return Ok(());
354        }
355
356        let mut active = self.active.lock().unwrap();
357
358        let active_profile = active
359            .remove(&name)
360            .ok_or_else(|| ProfilingError::ProfileNotFound(name.clone()))?;
361
362        drop(active);
363
364        // Pop from call stack
365        if self.config.enable_call_hierarchy {
366            let mut stack = self.call_stack.lock().unwrap();
367            stack.pop();
368        }
369
370        let duration = active_profile.start_time.elapsed();
371
372        // Calculate memory usage
373        let (memory_allocated, memory_deallocated, peak_memory) =
374            if self.config.enable_memory_profiling {
375                let end_snapshot = MemorySnapshot::current();
376                let allocated = end_snapshot
377                    .current_usage
378                    .saturating_sub(active_profile.start_memory);
379                let deallocated = 0; // Simplified
380                let peak = end_snapshot.peak_usage;
381                (allocated, deallocated, peak)
382            } else {
383                (0, 0, 0)
384            };
385
386        // Update or create profile entry
387        let mut profiles = self.profiles.lock().unwrap();
388
389        let entry = profiles.entry(name.clone()).or_insert_with(|| {
390            let mut entry = ProfileEntry::new(name.clone());
391            entry.parent = active_profile.parent.clone();
392            entry
393        });
394
395        entry.duration += duration;
396        entry.cpu_time += duration; // Simplified, would need OS thread time
397        entry.memory_allocated += memory_allocated;
398        entry.memory_deallocated += memory_deallocated;
399        entry.peak_memory = entry.peak_memory.max(peak_memory);
400        entry.invocations += 1;
401
402        // Update parent's children list
403        if let Some(parent_name) = &active_profile.parent {
404            // Ensure parent entry exists (create if needed)
405            let parent_entry = profiles
406                .entry(parent_name.clone())
407                .or_insert_with(|| ProfileEntry::new(parent_name.clone()));
408
409            if !parent_entry.children.contains(&name) {
410                parent_entry.children.push(name.clone());
411            }
412        }
413
414        Ok(())
415    }
416
417    /// Get a profile entry
418    pub fn get_profile(&self, name: &str) -> Option<ProfileEntry> {
419        let profiles = self.profiles.lock().unwrap();
420        profiles.get(name).cloned()
421    }
422
423    /// Get all profiles
424    pub fn get_all_profiles(&self) -> Vec<ProfileEntry> {
425        let profiles = self.profiles.lock().unwrap();
426        profiles.values().cloned().collect()
427    }
428
429    /// Generate a profiling report
430    pub fn generate_report(&self) -> ProfilingReport {
431        let profiles = self.profiles.lock().unwrap();
432        let entries: Vec<ProfileEntry> = profiles.values().cloned().collect();
433
434        ProfilingReport::new(entries, self.start_time.elapsed())
435    }
436
437    /// Reset all profiles
438    pub fn reset(&self) {
439        let mut profiles = self.profiles.lock().unwrap();
440        profiles.clear();
441
442        let mut active = self.active.lock().unwrap();
443        active.clear();
444
445        let mut stack = self.call_stack.lock().unwrap();
446        *stack = CallStack::default();
447
448        let mut snapshots = self.memory_snapshots.lock().unwrap();
449        snapshots.clear();
450    }
451
452    /// Take a memory snapshot
453    pub fn take_memory_snapshot(&self) {
454        if self.config.enable_memory_profiling {
455            let snapshot = MemorySnapshot::current();
456            let mut snapshots = self.memory_snapshots.lock().unwrap();
457            snapshots.push(snapshot);
458        }
459    }
460
461    /// Get memory snapshots
462    pub fn get_memory_snapshots(&self) -> Vec<MemorySnapshot> {
463        let snapshots = self.memory_snapshots.lock().unwrap();
464        snapshots.clone()
465    }
466
467    /// Get configuration
468    pub fn config(&self) -> &ProfilerConfig {
469        &self.config
470    }
471}
472
473/// Profiling report
474#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct ProfilingReport {
476    /// All profile entries
477    pub entries: Vec<ProfileEntry>,
478
479    /// Total profiling duration
480    pub total_duration: Duration,
481
482    /// Statistics
483    pub stats: ReportStats,
484
485    /// Bottlenecks (operations taking > 10% of total time)
486    pub bottlenecks: Vec<BottleneckInfo>,
487
488    /// Call hierarchy (tree structure)
489    pub call_tree: Vec<CallTreeNode>,
490}
491
492impl ProfilingReport {
493    /// Create a new report
494    fn new(entries: Vec<ProfileEntry>, total_duration: Duration) -> Self {
495        let stats = ReportStats::from_entries(&entries);
496        let bottlenecks = Self::identify_bottlenecks(&entries, total_duration);
497        let call_tree = Self::build_call_tree(&entries);
498
499        Self {
500            entries,
501            total_duration,
502            stats,
503            bottlenecks,
504            call_tree,
505        }
506    }
507
508    /// Identify bottlenecks
509    fn identify_bottlenecks(
510        entries: &[ProfileEntry],
511        total_duration: Duration,
512    ) -> Vec<BottleneckInfo> {
513        let threshold = total_duration.as_secs_f64() * 0.1; // 10% threshold
514
515        let mut bottlenecks: Vec<BottleneckInfo> = entries
516            .iter()
517            .filter(|e| e.duration.as_secs_f64() >= threshold)
518            .map(|e| BottleneckInfo {
519                name: e.name.clone(),
520                duration: e.duration,
521                percentage: (e.duration.as_secs_f64() / total_duration.as_secs_f64()) * 100.0,
522                invocations: e.invocations,
523                avg_duration: e.avg_duration(),
524                memory_usage: e.net_memory(),
525            })
526            .collect();
527
528        bottlenecks.sort_by(|a, b| b.duration.cmp(&a.duration));
529        bottlenecks
530    }
531
532    /// Build call tree
533    fn build_call_tree(entries: &[ProfileEntry]) -> Vec<CallTreeNode> {
534        // Find root nodes (no parent)
535        let roots: Vec<_> = entries
536            .iter()
537            .filter(|e| e.parent.is_none())
538            .map(|e| Self::build_tree_node(e, entries))
539            .collect();
540
541        roots
542    }
543
544    /// Build a single tree node
545    fn build_tree_node(entry: &ProfileEntry, all_entries: &[ProfileEntry]) -> CallTreeNode {
546        let children: Vec<CallTreeNode> = entry
547            .children
548            .iter()
549            .filter_map(|child_name| {
550                all_entries
551                    .iter()
552                    .find(|e| e.name == *child_name)
553                    .map(|e| Self::build_tree_node(e, all_entries))
554            })
555            .collect();
556
557        CallTreeNode {
558            name: entry.name.clone(),
559            duration: entry.duration,
560            invocations: entry.invocations,
561            memory: entry.net_memory(),
562            children,
563        }
564    }
565
566    /// Generate a summary string
567    pub fn summary(&self) -> String {
568        let mut s = String::new();
569        s.push_str("=== Performance Profiling Report ===\n\n");
570        s.push_str(&format!(
571            "Total Duration: {:.2}s\n",
572            self.total_duration.as_secs_f64()
573        ));
574        s.push_str(&format!("Total Operations: {}\n", self.entries.len()));
575        s.push_str(&format!(
576            "Total Invocations: {}\n\n",
577            self.stats.total_invocations
578        ));
579
580        s.push_str("=== Statistics ===\n");
581        s.push_str(&format!(
582            "Mean Duration: {:.2}ms\n",
583            self.stats.mean_duration.as_secs_f64() * 1000.0
584        ));
585        s.push_str(&format!(
586            "Median Duration: {:.2}ms\n",
587            self.stats.median_duration.as_secs_f64() * 1000.0
588        ));
589        s.push_str(&format!(
590            "Total Memory: {} bytes\n\n",
591            self.stats.total_memory
592        ));
593
594        if !self.bottlenecks.is_empty() {
595            s.push_str("=== Bottlenecks (>10% of total time) ===\n");
596            for bottleneck in &self.bottlenecks {
597                s.push_str(&format!(
598                    "  {} - {:.2}s ({:.1}%) - {} calls\n",
599                    bottleneck.name,
600                    bottleneck.duration.as_secs_f64(),
601                    bottleneck.percentage,
602                    bottleneck.invocations
603                ));
604            }
605            s.push('\n');
606        }
607
608        s
609    }
610
611    /// Export to JSON
612    pub fn to_json(&self) -> serde_json::Result<String> {
613        serde_json::to_string_pretty(self)
614    }
615
616    /// Generate flamegraph data (name;duration format)
617    pub fn to_flamegraph_data(&self) -> String {
618        let mut lines = Vec::new();
619
620        for node in &self.call_tree {
621            Self::flamegraph_node(&mut lines, node, String::new());
622        }
623
624        lines.join("\n")
625    }
626
627    /// Generate flamegraph data for a node
628    fn flamegraph_node(lines: &mut Vec<String>, node: &CallTreeNode, stack: String) {
629        let current_stack = if stack.is_empty() {
630            node.name.clone()
631        } else {
632            format!("{};{}", stack, node.name)
633        };
634
635        let duration_us = node.duration.as_micros();
636        lines.push(format!("{} {}", current_stack, duration_us));
637
638        for child in &node.children {
639            Self::flamegraph_node(lines, child, current_stack.clone());
640        }
641    }
642}
643
644/// Report statistics
645#[derive(Debug, Clone, Serialize, Deserialize)]
646pub struct ReportStats {
647    /// Total invocations across all operations
648    pub total_invocations: u64,
649
650    /// Mean duration
651    pub mean_duration: Duration,
652
653    /// Median duration
654    pub median_duration: Duration,
655
656    /// Total memory allocated
657    pub total_memory: i64,
658
659    /// Slowest operation
660    pub slowest_operation: Option<String>,
661
662    /// Fastest operation
663    pub fastest_operation: Option<String>,
664}
665
666impl ReportStats {
667    /// Compute statistics from entries
668    fn from_entries(entries: &[ProfileEntry]) -> Self {
669        if entries.is_empty() {
670            return Self::default();
671        }
672
673        let total_invocations: u64 = entries.iter().map(|e| e.invocations).sum();
674        let total_duration: Duration = entries.iter().map(|e| e.duration).sum();
675        let total_memory: i64 = entries.iter().map(|e| e.net_memory()).sum();
676
677        let mean_duration = if !entries.is_empty() {
678            total_duration / entries.len() as u32
679        } else {
680            Duration::ZERO
681        };
682
683        // Calculate median
684        let mut durations: Vec<Duration> = entries.iter().map(|e| e.duration).collect();
685        durations.sort();
686        let median_duration = durations
687            .get(durations.len() / 2)
688            .copied()
689            .unwrap_or(Duration::ZERO);
690
691        // Find slowest and fastest
692        let slowest = entries
693            .iter()
694            .max_by_key(|e| e.duration)
695            .map(|e| e.name.clone());
696        let fastest = entries
697            .iter()
698            .min_by_key(|e| e.duration)
699            .map(|e| e.name.clone());
700
701        Self {
702            total_invocations,
703            mean_duration,
704            median_duration,
705            total_memory,
706            slowest_operation: slowest,
707            fastest_operation: fastest,
708        }
709    }
710}
711
712impl Default for ReportStats {
713    fn default() -> Self {
714        Self {
715            total_invocations: 0,
716            mean_duration: Duration::ZERO,
717            median_duration: Duration::ZERO,
718            total_memory: 0,
719            slowest_operation: None,
720            fastest_operation: None,
721        }
722    }
723}
724
725/// Bottleneck information
726#[derive(Debug, Clone, Serialize, Deserialize)]
727pub struct BottleneckInfo {
728    /// Operation name
729    pub name: String,
730
731    /// Total duration
732    pub duration: Duration,
733
734    /// Percentage of total time
735    pub percentage: f64,
736
737    /// Number of invocations
738    pub invocations: u64,
739
740    /// Average duration per invocation
741    pub avg_duration: Duration,
742
743    /// Memory usage
744    pub memory_usage: i64,
745}
746
747/// Call tree node
748#[derive(Debug, Clone, Serialize, Deserialize)]
749pub struct CallTreeNode {
750    /// Operation name
751    pub name: String,
752
753    /// Duration
754    pub duration: Duration,
755
756    /// Invocations
757    pub invocations: u64,
758
759    /// Memory usage
760    pub memory: i64,
761
762    /// Child nodes
763    pub children: Vec<CallTreeNode>,
764}
765
766/// Helper macro for profiling a block
767#[macro_export]
768macro_rules! profile {
769    ($profiler:expr, $name:expr, $block:block) => {{
770        $profiler.start_profile($name).ok();
771        let result = $block;
772        $profiler.end_profile($name).ok();
773        result
774    }};
775}
776
777#[cfg(test)]
778mod tests {
779    use super::*;
780
781    #[test]
782    fn test_profiler_config_default() {
783        let config = ProfilerConfig::default();
784        assert!(config.enable_cpu_profiling);
785        assert!(config.enable_memory_profiling);
786        assert!(config.enable_call_hierarchy);
787        assert_eq!(config.max_call_depth, 32);
788    }
789
790    #[test]
791    fn test_profiler_config_builder() {
792        let config = ProfilerConfig::new()
793            .with_cpu_profiling(false)
794            .with_memory_profiling(false)
795            .with_call_hierarchy(false)
796            .with_max_call_depth(16);
797
798        assert!(!config.enable_cpu_profiling);
799        assert!(!config.enable_memory_profiling);
800        assert!(!config.enable_call_hierarchy);
801        assert_eq!(config.max_call_depth, 16);
802    }
803
804    #[test]
805    fn test_start_end_profile() {
806        let config = ProfilerConfig::default();
807        let profiler = Profiler::new(config);
808
809        profiler.start_profile("test_op").unwrap();
810        std::thread::sleep(Duration::from_millis(10));
811        profiler.end_profile("test_op").unwrap();
812
813        let profile = profiler.get_profile("test_op").unwrap();
814        assert_eq!(profile.name, "test_op");
815        assert_eq!(profile.invocations, 1);
816        assert!(profile.duration.as_millis() >= 10);
817    }
818
819    #[test]
820    fn test_multiple_invocations() {
821        let config = ProfilerConfig::default();
822        let profiler = Profiler::new(config);
823
824        for _ in 0..5 {
825            profiler.start_profile("test_op").unwrap();
826            std::thread::sleep(Duration::from_millis(5));
827            profiler.end_profile("test_op").unwrap();
828        }
829
830        let profile = profiler.get_profile("test_op").unwrap();
831        assert_eq!(profile.invocations, 5);
832        assert!(profile.duration.as_millis() >= 25);
833    }
834
835    #[test]
836    fn test_call_hierarchy() {
837        let config = ProfilerConfig::default();
838        let profiler = Profiler::new(config);
839
840        profiler.start_profile("parent").unwrap();
841        profiler.start_profile("child1").unwrap();
842        profiler.end_profile("child1").unwrap();
843        profiler.start_profile("child2").unwrap();
844        profiler.end_profile("child2").unwrap();
845        profiler.end_profile("parent").unwrap();
846
847        let parent = profiler.get_profile("parent").unwrap();
848        assert!(parent.parent.is_none());
849        assert_eq!(parent.children.len(), 2);
850
851        let child1 = profiler.get_profile("child1").unwrap();
852        assert_eq!(child1.parent, Some("parent".to_string()));
853    }
854
855    #[test]
856    fn test_profile_not_found() {
857        let config = ProfilerConfig::default();
858        let profiler = Profiler::new(config);
859
860        let result = profiler.end_profile("nonexistent");
861        assert!(result.is_err());
862    }
863
864    #[test]
865    fn test_profile_already_started() {
866        let config = ProfilerConfig::default();
867        let profiler = Profiler::new(config);
868
869        profiler.start_profile("test").unwrap();
870        let result = profiler.start_profile("test");
871        assert!(result.is_err());
872    }
873
874    #[test]
875    fn test_reset() {
876        let config = ProfilerConfig::default();
877        let profiler = Profiler::new(config);
878
879        profiler.start_profile("test").unwrap();
880        profiler.end_profile("test").unwrap();
881
882        assert!(profiler.get_profile("test").is_some());
883
884        profiler.reset();
885
886        assert!(profiler.get_profile("test").is_none());
887    }
888
889    #[test]
890    fn test_get_all_profiles() {
891        let config = ProfilerConfig::default();
892        let profiler = Profiler::new(config);
893
894        profiler.start_profile("op1").unwrap();
895        profiler.end_profile("op1").unwrap();
896
897        profiler.start_profile("op2").unwrap();
898        profiler.end_profile("op2").unwrap();
899
900        let profiles = profiler.get_all_profiles();
901        assert_eq!(profiles.len(), 2);
902    }
903
904    #[test]
905    fn test_generate_report() {
906        let config = ProfilerConfig::default();
907        let profiler = Profiler::new(config);
908
909        profiler.start_profile("op1").unwrap();
910        std::thread::sleep(Duration::from_millis(50));
911        profiler.end_profile("op1").unwrap();
912
913        profiler.start_profile("op2").unwrap();
914        std::thread::sleep(Duration::from_millis(10));
915        profiler.end_profile("op2").unwrap();
916
917        let report = profiler.generate_report();
918        assert_eq!(report.entries.len(), 2);
919        assert_eq!(report.stats.total_invocations, 2);
920        assert!(report.stats.slowest_operation.is_some());
921        assert!(report.stats.fastest_operation.is_some());
922    }
923
924    #[test]
925    fn test_report_summary() {
926        let config = ProfilerConfig::default();
927        let profiler = Profiler::new(config);
928
929        profiler.start_profile("test").unwrap();
930        std::thread::sleep(Duration::from_millis(10));
931        profiler.end_profile("test").unwrap();
932
933        let report = profiler.generate_report();
934        let summary = report.summary();
935
936        assert!(summary.contains("Performance Profiling Report"));
937        assert!(summary.contains("Total Operations"));
938    }
939
940    #[test]
941    fn test_bottleneck_detection() {
942        let config = ProfilerConfig::default();
943        let profiler = Profiler::new(config);
944
945        // Create a long-running operation
946        profiler.start_profile("slow_op").unwrap();
947        std::thread::sleep(Duration::from_millis(100));
948        profiler.end_profile("slow_op").unwrap();
949
950        // Create a fast operation
951        profiler.start_profile("fast_op").unwrap();
952        std::thread::sleep(Duration::from_millis(5));
953        profiler.end_profile("fast_op").unwrap();
954
955        let report = profiler.generate_report();
956
957        // The slow operation should be identified as a bottleneck
958        if !report.bottlenecks.is_empty() {
959            assert_eq!(report.bottlenecks[0].name, "slow_op");
960        }
961    }
962
963    #[test]
964    fn test_call_tree() {
965        let config = ProfilerConfig::default();
966        let profiler = Profiler::new(config);
967
968        profiler.start_profile("root").unwrap();
969        profiler.start_profile("child1").unwrap();
970        profiler.end_profile("child1").unwrap();
971        profiler.end_profile("root").unwrap();
972
973        let report = profiler.generate_report();
974
975        assert_eq!(report.call_tree.len(), 1);
976        assert_eq!(report.call_tree[0].name, "root");
977        assert_eq!(report.call_tree[0].children.len(), 1);
978        assert_eq!(report.call_tree[0].children[0].name, "child1");
979    }
980
981    #[test]
982    fn test_flamegraph_data() {
983        let config = ProfilerConfig::default();
984        let profiler = Profiler::new(config);
985
986        profiler.start_profile("root").unwrap();
987        profiler.start_profile("child").unwrap();
988        profiler.end_profile("child").unwrap();
989        profiler.end_profile("root").unwrap();
990
991        let report = profiler.generate_report();
992        let flamegraph = report.to_flamegraph_data();
993
994        assert!(flamegraph.contains("root"));
995        assert!(flamegraph.contains("child"));
996    }
997
998    #[test]
999    fn test_profile_entry_metrics() {
1000        let mut entry = ProfileEntry::new("test");
1001        entry.memory_allocated = 1000;
1002        entry.memory_deallocated = 300;
1003        entry.invocations = 5;
1004        entry.duration = Duration::from_millis(500);
1005
1006        assert_eq!(entry.net_memory(), 700);
1007        assert_eq!(entry.avg_duration(), Duration::from_millis(100));
1008    }
1009
1010    #[test]
1011    fn test_memory_snapshot() {
1012        let snapshot = MemorySnapshot::current();
1013        // Just ensure it doesn't panic
1014        assert_eq!(snapshot.total_allocated, 0);
1015    }
1016
1017    #[test]
1018    fn test_take_memory_snapshot() {
1019        let config = ProfilerConfig::default();
1020        let profiler = Profiler::new(config);
1021
1022        profiler.take_memory_snapshot();
1023        profiler.take_memory_snapshot();
1024
1025        let snapshots = profiler.get_memory_snapshots();
1026        assert_eq!(snapshots.len(), 2);
1027    }
1028
1029    #[test]
1030    fn test_profiler_disabled() {
1031        let config = ProfilerConfig::default().with_cpu_profiling(false);
1032        let profiler = Profiler::new(config);
1033
1034        // Should not error when profiling is disabled
1035        profiler.start_profile("test").unwrap();
1036        profiler.end_profile("test").unwrap();
1037
1038        // Should not create a profile
1039        assert!(profiler.get_profile("test").is_none());
1040    }
1041
1042    #[test]
1043    fn test_report_to_json() {
1044        let config = ProfilerConfig::default();
1045        let profiler = Profiler::new(config);
1046
1047        profiler.start_profile("test").unwrap();
1048        profiler.end_profile("test").unwrap();
1049
1050        let report = profiler.generate_report();
1051        let json = report.to_json().unwrap();
1052
1053        assert!(json.contains("\"entries\""));
1054        assert!(json.contains("\"stats\""));
1055    }
1056}