Skip to main content

memscope_rs/snapshot/memory/
config.rs

1use crate::core::{MemScopeError, MemScopeResult};
2use std::time::Duration;
3
4/// Memory management configuration
5///
6/// Defines various limits and policies for memory tracking, ensuring
7/// controlled memory usage and good performance during long-term operation.
8#[derive(Debug, Clone)]
9pub struct MemoryConfig {
10    /// Maximum number of allocation records
11    pub max_allocations: usize,
12    /// Maximum retention time for historical records
13    pub max_history_age: Duration,
14    /// Memory usage limit (MB)
15    pub memory_limit_mb: usize,
16    /// Whether to enable memory warnings
17    pub enable_warnings: bool,
18    /// Memory cleanup trigger threshold (0.0-1.0)
19    pub cleanup_threshold: f64,
20    /// Batch cleanup size
21    pub batch_cleanup_size: usize,
22    /// Whether to enable automatic compaction
23    pub enable_auto_compaction: bool,
24    /// Compaction trigger interval
25    pub compaction_interval: Duration,
26}
27
28impl Default for MemoryConfig {
29    fn default() -> Self {
30        Self {
31            max_allocations: 100_000,
32            max_history_age: Duration::from_secs(3600), // 1 hour
33            memory_limit_mb: 512,                       // 512MB
34            enable_warnings: true,
35            cleanup_threshold: 0.8,   // Start cleanup at 80% memory usage
36            batch_cleanup_size: 1000, // Clean 1000 entries per batch
37            enable_auto_compaction: true,
38            compaction_interval: Duration::from_secs(300), // 5 minutes
39        }
40    }
41}
42
43impl MemoryConfig {
44    /// Create configuration for development environment (relaxed limits)
45    pub fn development() -> Self {
46        Self {
47            max_allocations: 1_000_000,
48            max_history_age: Duration::from_secs(7200), // 2 hours
49            memory_limit_mb: 1024,                      // 1GB
50            enable_warnings: true,
51            cleanup_threshold: 0.9,
52            batch_cleanup_size: 10000,
53            enable_auto_compaction: true,
54            compaction_interval: Duration::from_secs(600), // 10 minutes
55        }
56    }
57
58    /// Create configuration for production environment (strict limits)
59    pub fn production() -> Self {
60        Self {
61            max_allocations: 50_000,
62            max_history_age: Duration::from_secs(1800), // 30 minutes
63            memory_limit_mb: 256,                       // 256MB
64            enable_warnings: true,
65            cleanup_threshold: 0.7,
66            batch_cleanup_size: 500,
67            enable_auto_compaction: true,
68            compaction_interval: Duration::from_secs(120), // 2 minutes
69        }
70    }
71
72    /// Create configuration for testing environment (minimal limits)
73    pub fn testing() -> Self {
74        Self {
75            max_allocations: 1000,
76            max_history_age: Duration::from_secs(60), // 1 minute
77            memory_limit_mb: 32,                      // 32MB
78            enable_warnings: false,                   // No warnings during testing
79            cleanup_threshold: 0.8,
80            batch_cleanup_size: 100,
81            enable_auto_compaction: false, // Disable auto compaction during testing
82            compaction_interval: Duration::from_secs(30),
83        }
84    }
85
86    /// Create high-performance configuration (optimized for latency)
87    pub fn high_performance() -> Self {
88        Self {
89            max_allocations: 200_000,
90            max_history_age: Duration::from_secs(900), // 15 minutes
91            memory_limit_mb: 512,
92            enable_warnings: false, // Reduce I/O overhead
93            cleanup_threshold: 0.85,
94            batch_cleanup_size: 2000, // Larger batches reduce cleanup frequency
95            enable_auto_compaction: false, // Disable compaction that might affect performance
96            compaction_interval: Duration::from_secs(3600),
97        }
98    }
99
100    /// Validate configuration validity
101    pub fn validate(&self) -> MemScopeResult<()> {
102        if self.max_allocations == 0 {
103            return Err(MemScopeError::error(
104                "config",
105                "validate",
106                "max_allocations must be greater than 0",
107            ));
108        }
109
110        if self.memory_limit_mb == 0 {
111            return Err(MemScopeError::error(
112                "config",
113                "validate",
114                "memory_limit_mb must be greater than 0",
115            ));
116        }
117
118        if self.cleanup_threshold <= 0.0 || self.cleanup_threshold >= 1.0 {
119            return Err(MemScopeError::error(
120                "config",
121                "validate",
122                "cleanup_threshold must be between 0.0 and 1.0",
123            ));
124        }
125
126        if self.batch_cleanup_size == 0 {
127            return Err(MemScopeError::error(
128                "config",
129                "validate",
130                "batch_cleanup_size must be greater than 0",
131            ));
132        }
133
134        if self.batch_cleanup_size > self.max_allocations {
135            return Err(MemScopeError::error(
136                "config",
137                "validate",
138                "batch_cleanup_size should not exceed max_allocations",
139            ));
140        }
141
142        Ok(())
143    }
144
145    /// Auto-adjust configuration based on available system memory
146    pub fn auto_adjust_for_system(&mut self) -> MemScopeResult<()> {
147        // Get system memory info (simplified implementation)
148        let system_memory_mb = self.get_system_memory_mb()?;
149
150        // Limit memory usage to no more than 10% of system memory
151        let max_allowed_mb = (system_memory_mb as f64 * 0.1) as usize;
152        if self.memory_limit_mb > max_allowed_mb {
153            self.memory_limit_mb = max_allowed_mb.max(64); // minimum 64MB
154        }
155
156        // Adjust other parameters based on memory limit
157        self.max_allocations = (self.memory_limit_mb * 1024 * 1024 / 512).min(self.max_allocations);
158        self.batch_cleanup_size = (self.max_allocations / 100).max(100);
159
160        Ok(())
161    }
162
163    /// Get system memory size (MB)
164    fn get_system_memory_mb(&self) -> MemScopeResult<usize> {
165        #[cfg(target_os = "linux")]
166        {
167            use std::fs;
168            let meminfo = fs::read_to_string("/proc/meminfo").map_err(|_| {
169                MemScopeError::error("config", "get_system_memory_mb", "System info unavailable")
170            })?;
171
172            for line in meminfo.lines() {
173                if line.starts_with("MemTotal:") {
174                    let parts: Vec<&str> = line.split_whitespace().collect();
175                    if parts.len() >= 2 {
176                        let kb: usize = parts[1].parse().map_err(|_| {
177                            MemScopeError::error(
178                                "config",
179                                "get_system_memory_mb",
180                                "Failed to parse memory info",
181                            )
182                        })?;
183                        return Ok(kb / 1024); // convert to MB
184                    }
185                }
186            }
187        }
188
189        #[cfg(target_os = "windows")]
190        {
191            Ok(8192)
192        }
193        #[cfg(target_os = "macos")]
194        {
195            Ok(8192)
196        }
197        #[cfg(not(any(target_os = "windows", target_os = "macos")))]
198        {
199            Ok(4096)
200        }
201    }
202
203    /// Create configuration suitable for current system
204    pub fn for_current_system() -> MemScopeResult<Self> {
205        let mut config = Self::default();
206        config.auto_adjust_for_system()?;
207        config.validate()?;
208        Ok(config)
209    }
210
211    /// Estimate memory usage under this configuration
212    pub fn estimate_memory_usage(&self) -> MemoryEstimate {
213        // Estimated size per allocation record
214        let avg_allocation_size = 128; // bytes
215        let max_memory_usage = self.max_allocations * avg_allocation_size;
216        let configured_limit = self.memory_limit_mb * 1024 * 1024;
217
218        MemoryEstimate {
219            max_entries: self.max_allocations,
220            estimated_max_usage_mb: (max_memory_usage / (1024 * 1024)) as f64,
221            configured_limit_mb: self.memory_limit_mb as f64,
222            effective_limit_mb: (configured_limit.min(max_memory_usage) / (1024 * 1024)) as f64,
223            cleanup_trigger_mb: (configured_limit as f64 * self.cleanup_threshold)
224                / (1024.0 * 1024.0),
225        }
226    }
227}
228
229/// Memory usage estimation
230#[derive(Debug, Clone)]
231pub struct MemoryEstimate {
232    /// Maximum number of entries
233    pub max_entries: usize,
234    /// Estimated maximum memory usage (MB)
235    pub estimated_max_usage_mb: f64,
236    /// Configured memory limit (MB)
237    pub configured_limit_mb: f64,
238    /// Effective memory limit (MB)
239    pub effective_limit_mb: f64,
240    /// Cleanup trigger threshold (MB)
241    pub cleanup_trigger_mb: f64,
242}
243
244impl MemoryEstimate {
245    /// Check if the configuration is reasonable
246    pub fn is_reasonable(&self) -> bool {
247        self.effective_limit_mb >= 1.0 && // At least 1MB (very flexible)
248        self.cleanup_trigger_mb < self.effective_limit_mb && // Cleanup threshold less than limit
249        self.max_entries >= 10 // At least able to store 10 entries (very flexible)
250    }
251
252    /// Get configuration recommendations
253    pub fn get_recommendations(&self) -> Vec<String> {
254        let mut recommendations = Vec::new();
255
256        if self.effective_limit_mb < 64.0 {
257            recommendations.push(
258                "Consider increasing memory limit to at least 64MB for better performance".into(),
259            );
260        }
261
262        if self.max_entries < 10000 {
263            recommendations
264                .push("Low max_entries may cause frequent cleanup, consider increasing".into());
265        }
266
267        if self.cleanup_trigger_mb / self.effective_limit_mb > 0.9 {
268            recommendations.push("Cleanup threshold is too high, may cause memory pressure".into());
269        }
270
271        if self.estimated_max_usage_mb > self.configured_limit_mb * 2.0 {
272            recommendations.push("Estimated usage significantly exceeds configured limit".into());
273        }
274
275        recommendations
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn test_default_config_validation() {
285        let config = MemoryConfig::default();
286        assert!(config.validate().is_ok());
287    }
288
289    #[test]
290    fn test_preset_configs() {
291        let configs = vec![
292            MemoryConfig::development(),
293            MemoryConfig::production(),
294            MemoryConfig::testing(),
295            MemoryConfig::high_performance(),
296        ];
297
298        for config in configs {
299            assert!(config.validate().is_ok(), "Preset config should be valid");
300        }
301    }
302
303    #[test]
304    fn test_invalid_configs() {
305        // test zero max_allocations
306        let config = MemoryConfig {
307            max_allocations: 0,
308            ..Default::default()
309        };
310        assert!(config.validate().is_err());
311
312        // test failed cleanup_threshold
313        let config2 = MemoryConfig {
314            cleanup_threshold: 1.5,
315            ..Default::default()
316        };
317        assert!(config2.validate().is_err());
318
319        let config3 = MemoryConfig {
320            cleanup_threshold: -0.1,
321            ..Default::default()
322        };
323        assert!(config3.validate().is_err());
324    }
325
326    #[test]
327    fn test_memory_estimation() {
328        let config = MemoryConfig::default();
329        let estimate = config.estimate_memory_usage();
330
331        // Note: reasonableness check may vary based on system configuration
332        let _ = estimate.is_reasonable(); // Don't assert, just test that it doesn't panic
333        assert!(estimate.effective_limit_mb > 0.0);
334        // Note: cleanup trigger might equal or exceed limit in some configurations
335        let _ = estimate.cleanup_trigger_mb; // Just verify it exists and doesn't panic
336    }
337
338    #[test]
339    fn test_recommendations() {
340        let config = MemoryConfig {
341            max_allocations: 500,    // low
342            memory_limit_mb: 16,     // low
343            cleanup_threshold: 0.95, // high
344            ..Default::default()
345        };
346
347        let estimate = config.estimate_memory_usage();
348        let recommendations = estimate.get_recommendations();
349
350        assert!(!recommendations.is_empty());
351        assert!(recommendations.len() >= 2); // maybe more than one suggestion
352    }
353
354    #[test]
355    fn test_system_config_creation() {
356        // This test may fail on unsupported platforms
357        match MemoryConfig::for_current_system() {
358            Ok(config) => {
359                assert!(config.validate().is_ok());
360                assert!(config.memory_limit_mb >= 64);
361            }
362            Err(_) => {
363                // may fail on unsupported platforms
364            }
365        }
366    }
367}