Skip to main content

mabi_core/profiling/
mod.rs

1//! Memory and performance profiling module.
2//!
3//! This module provides comprehensive profiling capabilities for the TRAP simulator,
4//! including memory usage tracking, allocation analysis, and performance monitoring.
5//!
6//! # Features
7//!
8//! - **Memory Profiling**: Track heap allocations, memory regions, and usage patterns
9//! - **Leak Detection**: Identify potential memory leaks through allocation tracking
10//! - **Performance Profiling**: Measure operation latencies and throughput
11//! - **Report Generation**: Generate detailed profiling reports
12//!
13//! # Example
14//!
15//! ```rust,ignore
16//! use mabi_core::profiling::{MemoryProfiler, ProfilerConfig};
17//!
18//! let config = ProfilerConfig::default();
19//! let profiler = MemoryProfiler::new(config);
20//!
21//! // Start profiling
22//! profiler.start();
23//!
24//! // ... run your workload ...
25//!
26//! // Get a snapshot
27//! let snapshot = profiler.snapshot();
28//! println!("Current memory: {} bytes", snapshot.current_bytes);
29//!
30//! // Generate report
31//! let report = profiler.generate_report();
32//! ```
33
34pub mod memory;
35pub mod leak_detector;
36pub mod report;
37
38pub use memory::*;
39pub use leak_detector::*;
40pub use report::*;
41
42use std::sync::Arc;
43use std::time::Duration;
44
45use parking_lot::RwLock;
46use serde::{Deserialize, Serialize};
47
48/// Profiler configuration.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ProfilerConfig {
51    /// Enable memory profiling.
52    #[serde(default = "default_true")]
53    pub memory_enabled: bool,
54
55    /// Enable leak detection.
56    #[serde(default = "default_true")]
57    pub leak_detection_enabled: bool,
58
59    /// Sampling interval for periodic snapshots.
60    #[serde(default = "default_sampling_interval")]
61    pub sampling_interval: Duration,
62
63    /// Maximum number of snapshots to retain.
64    #[serde(default = "default_max_snapshots")]
65    pub max_snapshots: usize,
66
67    /// Allocation size threshold for tracking (bytes).
68    /// Allocations smaller than this are not individually tracked.
69    #[serde(default = "default_allocation_threshold")]
70    pub allocation_threshold: usize,
71
72    /// Enable stack trace capture for allocations (expensive).
73    #[serde(default)]
74    pub capture_stack_traces: bool,
75
76    /// Maximum stack trace depth.
77    #[serde(default = "default_stack_depth")]
78    pub max_stack_depth: usize,
79}
80
81fn default_true() -> bool {
82    true
83}
84
85fn default_sampling_interval() -> Duration {
86    Duration::from_secs(10)
87}
88
89fn default_max_snapshots() -> usize {
90    1000
91}
92
93fn default_allocation_threshold() -> usize {
94    1024 // 1KB
95}
96
97fn default_stack_depth() -> usize {
98    16
99}
100
101impl Default for ProfilerConfig {
102    fn default() -> Self {
103        Self {
104            memory_enabled: true,
105            leak_detection_enabled: true,
106            sampling_interval: default_sampling_interval(),
107            max_snapshots: default_max_snapshots(),
108            allocation_threshold: default_allocation_threshold(),
109            capture_stack_traces: false,
110            max_stack_depth: default_stack_depth(),
111        }
112    }
113}
114
115/// Combined profiler that manages all profiling subsystems.
116pub struct Profiler {
117    config: ProfilerConfig,
118    memory_profiler: Arc<RwLock<MemoryProfiler>>,
119    leak_detector: Arc<RwLock<LeakDetector>>,
120    started: Arc<std::sync::atomic::AtomicBool>,
121}
122
123impl Profiler {
124    /// Create a new profiler with the given configuration.
125    pub fn new(config: ProfilerConfig) -> Self {
126        let memory_config = MemoryProfilerConfig {
127            sampling_interval: config.sampling_interval,
128            max_snapshots: config.max_snapshots,
129            track_allocations: config.memory_enabled,
130            allocation_threshold: config.allocation_threshold,
131        };
132
133        let leak_config = LeakDetectorConfig {
134            enabled: config.leak_detection_enabled,
135            check_interval: config.sampling_interval * 6, // Check every minute by default
136            growth_threshold_percent: 10.0,
137            min_samples_for_detection: 6,
138        };
139
140        Self {
141            config,
142            memory_profiler: Arc::new(RwLock::new(MemoryProfiler::new(memory_config))),
143            leak_detector: Arc::new(RwLock::new(LeakDetector::new(leak_config))),
144            started: Arc::new(std::sync::atomic::AtomicBool::new(false)),
145        }
146    }
147
148    /// Create a profiler with default configuration.
149    pub fn with_defaults() -> Self {
150        Self::new(ProfilerConfig::default())
151    }
152
153    /// Get the profiler configuration.
154    pub fn config(&self) -> &ProfilerConfig {
155        &self.config
156    }
157
158    /// Start profiling.
159    pub fn start(&self) {
160        self.started
161            .store(true, std::sync::atomic::Ordering::SeqCst);
162        self.memory_profiler.write().start();
163        self.leak_detector.write().start();
164    }
165
166    /// Stop profiling.
167    pub fn stop(&self) {
168        self.started
169            .store(false, std::sync::atomic::Ordering::SeqCst);
170        self.memory_profiler.write().stop();
171        self.leak_detector.write().stop();
172    }
173
174    /// Check if profiling is active.
175    pub fn is_running(&self) -> bool {
176        self.started.load(std::sync::atomic::Ordering::SeqCst)
177    }
178
179    /// Record a memory allocation.
180    pub fn record_allocation(&self, label: &str, size: usize) {
181        if self.is_running() {
182            self.memory_profiler.write().record_allocation(label, size);
183            self.leak_detector.write().record_allocation(label, size);
184        }
185    }
186
187    /// Record a memory deallocation.
188    pub fn record_deallocation(&self, label: &str, size: usize) {
189        if self.is_running() {
190            self.memory_profiler
191                .write()
192                .record_deallocation(label, size);
193            self.leak_detector.write().record_deallocation(label, size);
194        }
195    }
196
197    /// Take a memory snapshot.
198    pub fn snapshot(&self) -> MemorySnapshot {
199        self.memory_profiler.read().snapshot()
200    }
201
202    /// Check for potential memory leaks.
203    pub fn check_leaks(&self) -> Vec<LeakWarning> {
204        self.leak_detector.read().check()
205    }
206
207    /// Generate a comprehensive profiling report.
208    pub fn generate_report(&self) -> ProfileReport {
209        let memory_report = self.memory_profiler.read().generate_report();
210        let leak_warnings = self.check_leaks();
211
212        ProfileReport {
213            generated_at: chrono::Utc::now(),
214            config: self.config.clone(),
215            memory: memory_report,
216            leak_warnings,
217            is_running: self.is_running(),
218        }
219    }
220
221    /// Get the memory profiler.
222    pub fn memory_profiler(&self) -> Arc<RwLock<MemoryProfiler>> {
223        Arc::clone(&self.memory_profiler)
224    }
225
226    /// Get the leak detector.
227    pub fn leak_detector(&self) -> Arc<RwLock<LeakDetector>> {
228        Arc::clone(&self.leak_detector)
229    }
230
231    /// Reset all profiling data.
232    pub fn reset(&self) {
233        self.memory_profiler.write().reset();
234        self.leak_detector.write().reset();
235    }
236}
237
238impl std::fmt::Debug for Profiler {
239    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240        f.debug_struct("Profiler")
241            .field("config", &self.config)
242            .field("is_running", &self.is_running())
243            .finish()
244    }
245}
246
247/// Comprehensive profiling report.
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct ProfileReport {
250    /// When the report was generated.
251    pub generated_at: chrono::DateTime<chrono::Utc>,
252
253    /// Profiler configuration used.
254    pub config: ProfilerConfig,
255
256    /// Memory profiling report.
257    pub memory: MemoryReport,
258
259    /// Detected leak warnings.
260    pub leak_warnings: Vec<LeakWarning>,
261
262    /// Whether profiling is currently running.
263    pub is_running: bool,
264}
265
266impl ProfileReport {
267    /// Check if there are any leak warnings.
268    pub fn has_leak_warnings(&self) -> bool {
269        !self.leak_warnings.is_empty()
270    }
271
272    /// Get the current memory usage in bytes.
273    pub fn current_memory_bytes(&self) -> u64 {
274        self.memory.current_bytes
275    }
276
277    /// Get the peak memory usage in bytes.
278    pub fn peak_memory_bytes(&self) -> u64 {
279        self.memory.peak_bytes
280    }
281
282    /// Export report as JSON.
283    pub fn to_json(&self) -> Result<String, serde_json::Error> {
284        serde_json::to_string_pretty(self)
285    }
286
287    /// Export report as a human-readable string.
288    pub fn to_summary(&self) -> String {
289        let mut summary = String::new();
290        summary.push_str("=== TRAP Simulator Profiling Report ===\n\n");
291
292        summary.push_str(&format!(
293            "Generated: {}\n",
294            self.generated_at.format("%Y-%m-%d %H:%M:%S UTC")
295        ));
296        summary.push_str(&format!("Status: {}\n", if self.is_running { "Running" } else { "Stopped" }));
297
298        summary.push_str("\n--- Memory Usage ---\n");
299        summary.push_str(&format!(
300            "Current: {} MB\n",
301            self.memory.current_bytes / 1024 / 1024
302        ));
303        summary.push_str(&format!(
304            "Peak: {} MB\n",
305            self.memory.peak_bytes / 1024 / 1024
306        ));
307        summary.push_str(&format!(
308            "Total Allocated: {} MB\n",
309            self.memory.total_allocated / 1024 / 1024
310        ));
311        summary.push_str(&format!(
312            "Total Deallocated: {} MB\n",
313            self.memory.total_deallocated / 1024 / 1024
314        ));
315        summary.push_str(&format!(
316            "Allocation Count: {}\n",
317            self.memory.allocation_count
318        ));
319
320        if !self.memory.regions.is_empty() {
321            summary.push_str("\n--- Memory Regions ---\n");
322            for (name, region) in &self.memory.regions {
323                summary.push_str(&format!(
324                    "  {}: {} KB (peak: {} KB, allocs: {})\n",
325                    name,
326                    region.current_bytes / 1024,
327                    region.peak_bytes / 1024,
328                    region.allocation_count
329                ));
330            }
331        }
332
333        if !self.leak_warnings.is_empty() {
334            summary.push_str("\n--- Leak Warnings ---\n");
335            for warning in &self.leak_warnings {
336                summary.push_str(&format!(
337                    "  [{}] {}: {} (growth: {:.1}%/min)\n",
338                    warning.severity,
339                    warning.region,
340                    warning.message,
341                    warning.growth_rate_per_minute
342                ));
343            }
344        } else {
345            summary.push_str("\n--- No leak warnings detected ---\n");
346        }
347
348        summary
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    #[test]
357    fn test_profiler_config_default() {
358        let config = ProfilerConfig::default();
359        assert!(config.memory_enabled);
360        assert!(config.leak_detection_enabled);
361        assert_eq!(config.sampling_interval, Duration::from_secs(10));
362    }
363
364    #[test]
365    fn test_profiler_lifecycle() {
366        let profiler = Profiler::with_defaults();
367        assert!(!profiler.is_running());
368
369        profiler.start();
370        assert!(profiler.is_running());
371
372        profiler.stop();
373        assert!(!profiler.is_running());
374    }
375
376    #[test]
377    fn test_profiler_record_allocation() {
378        let profiler = Profiler::with_defaults();
379        profiler.start();
380
381        profiler.record_allocation("test_region", 1024);
382        profiler.record_allocation("test_region", 2048);
383
384        let snapshot = profiler.snapshot();
385        assert!(snapshot.current_bytes >= 3072);
386
387        profiler.record_deallocation("test_region", 1024);
388
389        let snapshot = profiler.snapshot();
390        assert!(snapshot.current_bytes >= 2048);
391    }
392
393    #[test]
394    fn test_profiler_report() {
395        let profiler = Profiler::with_defaults();
396        profiler.start();
397
398        profiler.record_allocation("devices", 1024 * 1024);
399        profiler.record_allocation("registers", 512 * 1024);
400
401        let report = profiler.generate_report();
402        assert!(report.is_running);
403        assert!(!report.memory.regions.is_empty());
404
405        let summary = report.to_summary();
406        assert!(summary.contains("Memory Usage"));
407    }
408
409    #[test]
410    fn test_profiler_reset() {
411        let profiler = Profiler::with_defaults();
412        profiler.start();
413
414        profiler.record_allocation("test", 1024);
415        profiler.reset();
416
417        let snapshot = profiler.snapshot();
418        assert_eq!(snapshot.current_bytes, 0);
419    }
420}