memscope_rs/memory/
config.rs

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