memscope_rs/core/
string_pool_monitor.rs

1//! String pool monitoring and statistics
2//!
3//! This module provides monitoring capabilities for the string pool system,
4//! including performance metrics, memory usage tracking, and optimization
5//! recommendations.
6
7use crate::core::string_pool::{get_string_pool_stats, StringPoolStats};
8use serde::{Deserialize, Serialize};
9use std::sync::{Arc, Mutex};
10use std::time::{Duration, Instant};
11
12/// Detailed string pool monitoring statistics
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct StringPoolMonitorStats {
15    /// Basic string pool statistics
16    pub pool_stats: StringPoolStats,
17    /// Performance metrics
18    pub performance: PerformanceMetrics,
19    /// Memory efficiency metrics
20    pub memory_efficiency: MemoryEfficiencyMetrics,
21    /// Usage patterns
22    pub usage_patterns: UsagePatterns,
23    /// Optimization recommendations
24    pub recommendations: Vec<OptimizationRecommendation>,
25}
26
27/// Performance metrics for string pool operations
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct PerformanceMetrics {
30    /// Average time per intern operation in nanoseconds
31    pub avg_intern_time_ns: f64,
32    /// Peak intern operations per second
33    pub peak_ops_per_second: f64,
34    /// Current intern operations per second
35    pub current_ops_per_second: f64,
36    /// Total time spent in intern operations (nanoseconds)
37    pub total_intern_time_ns: u64,
38    /// Monitor uptime in seconds
39    pub uptime_seconds: f64,
40}
41
42impl Default for PerformanceMetrics {
43    fn default() -> Self {
44        Self {
45            avg_intern_time_ns: 0.0,
46            peak_ops_per_second: 0.0,
47            current_ops_per_second: 0.0,
48            total_intern_time_ns: 0,
49            uptime_seconds: 0.0,
50        }
51    }
52}
53
54/// Memory efficiency metrics
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct MemoryEfficiencyMetrics {
57    /// Memory efficiency ratio (0.0 to 1.0, higher is better)
58    pub efficiency_ratio: f64,
59    /// Total memory that would be used without interning
60    pub memory_without_interning_bytes: u64,
61    /// Actual memory used with interning
62    pub memory_with_interning_bytes: u64,
63    /// Memory overhead of the pool structure itself
64    pub pool_overhead_bytes: u64,
65}
66
67impl Default for MemoryEfficiencyMetrics {
68    fn default() -> Self {
69        Self {
70            efficiency_ratio: 0.0,
71            memory_without_interning_bytes: 0,
72            memory_with_interning_bytes: 0,
73            pool_overhead_bytes: 0,
74        }
75    }
76}
77
78/// Usage patterns analysis
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct UsagePatterns {
81    /// Most frequently interned strings
82    pub top_strings: Vec<StringUsageInfo>,
83    /// Distribution of string lengths
84    pub length_distribution: Vec<LengthBucket>,
85    /// Temporal usage patterns
86    pub temporal_patterns: TemporalPatterns,
87}
88
89/// Information about a frequently used string
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct StringUsageInfo {
92    /// The string content (truncated if too long)
93    pub content: String,
94    /// Number of times this string was interned
95    pub usage_count: u64,
96    /// Estimated memory saved by interning this string
97    pub memory_saved_bytes: u64,
98}
99
100/// String length distribution bucket
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct LengthBucket {
103    /// Minimum length in this bucket (inclusive)
104    pub min_length: usize,
105    /// Maximum length in this bucket (exclusive)
106    pub max_length: usize,
107    /// Number of strings in this bucket
108    pub count: usize,
109    /// Total bytes for strings in this bucket
110    pub total_bytes: usize,
111}
112
113/// Temporal usage patterns
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct TemporalPatterns {
116    /// Intern operations in the last minute
117    pub ops_last_minute: u64,
118    /// Intern operations in the last hour
119    pub ops_last_hour: u64,
120    /// Peak operations per minute observed
121    pub peak_ops_per_minute: u64,
122}
123
124/// Optimization recommendation
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct OptimizationRecommendation {
127    /// Type of recommendation
128    pub recommendation_type: RecommendationType,
129    /// Description of the recommendation
130    pub description: String,
131    /// Estimated impact if implemented
132    pub estimated_impact: String,
133    /// Priority level (1-5, 5 being highest)
134    pub priority: u8,
135}
136
137/// Types of optimization recommendations
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum RecommendationType {
140    /// Increase pool size
141    IncreasePoolSize,
142    /// Decrease pool size
143    DecreasePoolSize,
144    /// Enable/disable specific features
145    FeatureToggle,
146    /// Memory optimization
147    MemoryOptimization,
148    /// Performance optimization
149    PerformanceOptimization,
150    /// Usage pattern optimization
151    UsageOptimization,
152}
153
154/// String pool monitor that tracks usage and performance
155pub struct StringPoolMonitor {
156    /// Performance tracking
157    performance_tracker: Arc<Mutex<PerformanceTracker>>,
158    /// Start time for monitoring
159    start_time: Instant,
160}
161
162struct PerformanceTracker {
163    total_intern_time_ns: u64,
164    intern_count: u64,
165    last_ops_calculation: Instant,
166    ops_in_last_second: u64,
167    peak_ops_per_second: f64,
168    recent_intern_times: Vec<u64>, // Ring buffer of recent intern times
169}
170
171impl StringPoolMonitor {
172    /// Create a new string pool monitor
173    pub fn new() -> Self {
174        Self {
175            performance_tracker: Arc::new(Mutex::new(PerformanceTracker {
176                total_intern_time_ns: 0,
177                intern_count: 0,
178                last_ops_calculation: Instant::now(),
179                ops_in_last_second: 0,
180                peak_ops_per_second: 0.0,
181                recent_intern_times: Vec::with_capacity(1000),
182            })),
183            start_time: Instant::now(),
184        }
185    }
186
187    /// Record an intern operation with its duration
188    pub fn record_intern_operation(&self, duration_ns: u64) {
189        if let Ok(mut tracker) = self.performance_tracker.lock() {
190            tracker.total_intern_time_ns += duration_ns;
191            tracker.intern_count += 1;
192
193            // Add to recent times (ring buffer)
194            if tracker.recent_intern_times.len() >= 1000 {
195                tracker.recent_intern_times.remove(0);
196            }
197            tracker.recent_intern_times.push(duration_ns);
198
199            // Update operations per second calculation
200            let now = Instant::now();
201            if now.duration_since(tracker.last_ops_calculation) >= Duration::from_secs(1) {
202                let ops_per_second = tracker.ops_in_last_second as f64;
203                if ops_per_second > tracker.peak_ops_per_second {
204                    tracker.peak_ops_per_second = ops_per_second;
205                }
206                tracker.ops_in_last_second = 0;
207                tracker.last_ops_calculation = now;
208            } else {
209                tracker.ops_in_last_second += 1;
210            }
211        }
212    }
213
214    /// Get comprehensive monitoring statistics
215    pub fn get_stats(&self) -> StringPoolMonitorStats {
216        let pool_stats = get_string_pool_stats();
217        let performance = self.get_performance_metrics();
218        let memory_efficiency = self.calculate_memory_efficiency(&pool_stats);
219        let usage_patterns = self.analyze_usage_patterns(&pool_stats);
220        let recommendations =
221            self.generate_recommendations(&pool_stats, &performance, &memory_efficiency);
222
223        StringPoolMonitorStats {
224            pool_stats,
225            performance,
226            memory_efficiency,
227            usage_patterns,
228            recommendations,
229        }
230    }
231
232    fn get_performance_metrics(&self) -> PerformanceMetrics {
233        let uptime_seconds = self.start_time.elapsed().as_secs_f64();
234
235        if let Ok(tracker) = self.performance_tracker.lock() {
236            let avg_intern_time_ns = if tracker.intern_count > 0 {
237                tracker.total_intern_time_ns as f64 / tracker.intern_count as f64
238            } else {
239                0.0
240            };
241
242            let current_ops_per_second = tracker.ops_in_last_second as f64;
243
244            PerformanceMetrics {
245                avg_intern_time_ns,
246                peak_ops_per_second: tracker.peak_ops_per_second,
247                current_ops_per_second,
248                total_intern_time_ns: tracker.total_intern_time_ns,
249                uptime_seconds,
250            }
251        } else {
252            PerformanceMetrics {
253                avg_intern_time_ns: 0.0,
254                peak_ops_per_second: 0.0,
255                current_ops_per_second: 0.0,
256                total_intern_time_ns: 0,
257                uptime_seconds,
258            }
259        }
260    }
261
262    fn calculate_memory_efficiency(&self, pool_stats: &StringPoolStats) -> MemoryEfficiencyMetrics {
263        // Estimate memory usage
264        let avg_string_size = pool_stats.average_string_length as u64;
265        let total_unique_strings = pool_stats.unique_strings as u64;
266        let total_intern_ops = pool_stats.intern_operations;
267
268        // Memory with interning: unique strings + Arc overhead
269        let arc_overhead_per_string = std::mem::size_of::<Arc<str>>() as u64;
270        let memory_with_interning =
271            (avg_string_size + arc_overhead_per_string) * total_unique_strings;
272
273        // Memory without interning: all strings stored separately
274        let memory_without_interning = avg_string_size * total_intern_ops;
275
276        // Pool overhead: HashMap structure + DashMap overhead
277        let pool_overhead = total_unique_strings * 64; // Rough estimate
278
279        let efficiency_ratio: f64 = if memory_without_interning > 0 {
280            1.0 - (memory_with_interning as f64 / memory_without_interning as f64)
281        } else {
282            0.0
283        };
284
285        MemoryEfficiencyMetrics {
286            efficiency_ratio: efficiency_ratio.clamp(0.0, 1.0),
287            memory_without_interning_bytes: memory_without_interning,
288            memory_with_interning_bytes: memory_with_interning,
289            pool_overhead_bytes: pool_overhead,
290        }
291    }
292
293    fn analyze_usage_patterns(&self, _pool_stats: &StringPoolStats) -> UsagePatterns {
294        // For now, return empty patterns - in a real implementation,
295        // we would track actual string usage
296        UsagePatterns {
297            top_strings: vec![],
298            length_distribution: vec![
299                LengthBucket {
300                    min_length: 0,
301                    max_length: 10,
302                    count: 0,
303                    total_bytes: 0,
304                },
305                LengthBucket {
306                    min_length: 10,
307                    max_length: 50,
308                    count: 0,
309                    total_bytes: 0,
310                },
311                LengthBucket {
312                    min_length: 50,
313                    max_length: 100,
314                    count: 0,
315                    total_bytes: 0,
316                },
317                LengthBucket {
318                    min_length: 100,
319                    max_length: usize::MAX,
320                    count: 0,
321                    total_bytes: 0,
322                },
323            ],
324            temporal_patterns: TemporalPatterns {
325                ops_last_minute: 0,
326                ops_last_hour: 0,
327                peak_ops_per_minute: 0,
328            },
329        }
330    }
331
332    fn generate_recommendations(
333        &self,
334        pool_stats: &StringPoolStats,
335        performance: &PerformanceMetrics,
336        memory_efficiency: &MemoryEfficiencyMetrics,
337    ) -> Vec<OptimizationRecommendation> {
338        let mut recommendations = Vec::new();
339
340        // Memory efficiency recommendations
341        if memory_efficiency.efficiency_ratio < 0.3 {
342            recommendations.push(OptimizationRecommendation {
343                recommendation_type: RecommendationType::MemoryOptimization,
344                description:
345                    "String pool efficiency is low. Consider reviewing string usage patterns."
346                        .to_string(),
347                estimated_impact: "Could reduce memory usage by 20-40%".to_string(),
348                priority: 4,
349            });
350        }
351
352        // Performance recommendations
353        if performance.avg_intern_time_ns > 1000.0 {
354            recommendations.push(OptimizationRecommendation {
355                recommendation_type: RecommendationType::PerformanceOptimization,
356                description: "String interning operations are slow. Consider optimizing hash function or reducing contention.".to_string(),
357                estimated_impact: "Could improve intern performance by 2-3x".to_string(),
358                priority: 3,
359            });
360        }
361
362        // Pool size recommendations
363        if pool_stats.unique_strings > 100000 {
364            recommendations.push(OptimizationRecommendation {
365                recommendation_type: RecommendationType::IncreasePoolSize,
366                description: "String pool is getting large. Consider implementing LRU eviction or pool size limits.".to_string(),
367                estimated_impact: "Could reduce memory usage by 10-20%".to_string(),
368                priority: 2,
369            });
370        }
371
372        // Cache hit rate recommendations
373        let cache_hit_rate = if pool_stats.intern_operations > 0 {
374            pool_stats.cache_hits as f64 / pool_stats.intern_operations as f64
375        } else {
376            0.0
377        };
378
379        if cache_hit_rate < 0.5 {
380            recommendations.push(OptimizationRecommendation {
381                recommendation_type: RecommendationType::UsageOptimization,
382                description: "Low cache hit rate suggests many unique strings. Review string generation patterns.".to_string(),
383                estimated_impact: "Could improve cache hit rate to 70-80%".to_string(),
384                priority: 3,
385            });
386        }
387
388        recommendations
389    }
390}
391
392impl Default for StringPoolMonitor {
393    fn default() -> Self {
394        Self::new()
395    }
396}
397
398/// Global string pool monitor instance
399static GLOBAL_MONITOR: std::sync::OnceLock<StringPoolMonitor> = std::sync::OnceLock::new();
400
401/// Get the global string pool monitor
402pub fn get_string_pool_monitor() -> &'static StringPoolMonitor {
403    GLOBAL_MONITOR.get_or_init(StringPoolMonitor::new)
404}
405
406/// Record an intern operation for monitoring
407pub fn record_intern_operation(duration_ns: u64) {
408    get_string_pool_monitor().record_intern_operation(duration_ns);
409}
410
411/// Get comprehensive string pool monitoring statistics
412pub fn get_string_pool_monitor_stats() -> StringPoolMonitorStats {
413    get_string_pool_monitor().get_stats()
414}
415
416#[cfg(test)]
417mod tests {
418    use super::*;
419    // use std::time::Duration;
420
421    // Helper function to create test StringPoolStats
422    fn create_test_stats() -> StringPoolStats {
423        StringPoolStats {
424            unique_strings: 100,
425            intern_operations: 1000,
426            cache_hits: 900,
427            memory_saved_bytes: 5000,
428            average_string_length: 20.0,
429        }
430    }
431
432    #[test]
433    fn test_monitor_creation() {
434        let monitor = StringPoolMonitor::new();
435        let stats = monitor.get_stats();
436
437        assert_eq!(stats.performance.avg_intern_time_ns, 0.0);
438        assert_eq!(stats.performance.peak_ops_per_second, 0.0);
439        assert_eq!(stats.performance.total_intern_time_ns, 0);
440    }
441
442    #[test]
443    fn test_default_implementation() {
444        let monitor = StringPoolMonitor::default();
445        let stats = monitor.get_stats();
446
447        // Default should be same as new()
448        assert_eq!(stats.performance.avg_intern_time_ns, 0.0);
449        assert_eq!(stats.performance.peak_ops_per_second, 0.0);
450    }
451
452    #[test]
453    fn test_performance_tracking() {
454        let monitor = StringPoolMonitor::new();
455
456        // Record some operations
457        monitor.record_intern_operation(100);
458        monitor.record_intern_operation(200);
459        monitor.record_intern_operation(150);
460
461        let stats = monitor.get_stats();
462
463        // Check average and total time
464        assert_eq!(stats.performance.avg_intern_time_ns, 150.0);
465        assert_eq!(stats.performance.total_intern_time_ns, 450);
466
467        // For peak_ops_per_second, we can't guarantee it's > 0 in test environment
468        // as it depends on timing, so we'll just check that it's not negative
469        assert!(stats.performance.peak_ops_per_second >= 0.0);
470        assert!(stats.performance.current_ops_per_second >= 0.0);
471    }
472
473    #[test]
474    fn test_record_intern_operation_edge_cases() {
475        let monitor = StringPoolMonitor::new();
476
477        // Test with zero duration
478        monitor.record_intern_operation(0);
479        let stats = monitor.get_stats();
480        assert_eq!(stats.performance.avg_intern_time_ns, 0.0);
481
482        // Test with very large duration
483        monitor.record_intern_operation(u64::MAX);
484        let stats = monitor.get_stats();
485        assert!(stats.performance.avg_intern_time_ns > 0.0);
486    }
487
488    #[test]
489    fn test_memory_efficiency_calculation() {
490        let monitor = StringPoolMonitor::new();
491        let pool_stats = create_test_stats();
492
493        let efficiency = monitor.calculate_memory_efficiency(&pool_stats);
494
495        // Basic validation of efficiency metrics
496        assert!(efficiency.efficiency_ratio > 0.0 && efficiency.efficiency_ratio <= 1.0);
497        assert!(efficiency.memory_without_interning_bytes > 0);
498        assert!(efficiency.memory_with_interning_bytes > 0);
499        assert!(efficiency.memory_without_interning_bytes > efficiency.memory_with_interning_bytes);
500
501        // Test with empty stats
502        let empty_stats = StringPoolStats {
503            unique_strings: 0,
504            intern_operations: 0,
505            cache_hits: 0,
506            memory_saved_bytes: 0,
507            average_string_length: 0.0,
508        };
509        let empty_efficiency = monitor.calculate_memory_efficiency(&empty_stats);
510        assert_eq!(empty_efficiency.efficiency_ratio, 0.0);
511    }
512
513    #[test]
514    fn test_analyze_usage_patterns() {
515        let monitor = StringPoolMonitor::new();
516        let pool_stats = create_test_stats();
517
518        let usage_patterns = monitor.analyze_usage_patterns(&pool_stats);
519
520        // Check temporal patterns
521        let temporal = &usage_patterns.temporal_patterns;
522        assert!(temporal.ops_last_minute <= temporal.ops_last_hour);
523        assert!(temporal.peak_ops_per_minute >= temporal.ops_last_minute);
524
525        // Note: top_strings and length_distribution might be empty in some implementations
526        // so we don't assert on them
527    }
528
529    #[test]
530    fn test_global_functions() {
531        // Reset global state
532        let _ = GLOBAL_MONITOR.set(StringPoolMonitor::new());
533
534        // Test record_intern_operation
535        record_intern_operation(100);
536        record_intern_operation(200);
537
538        // Test get_string_pool_monitor
539        let monitor = get_string_pool_monitor();
540        let stats = monitor.get_stats();
541
542        // The total should be at least our two operations
543        assert!(stats.performance.total_intern_time_ns >= 300);
544
545        // Test get_string_pool_monitor_stats
546        let stats2 = get_string_pool_monitor_stats();
547        // Due to concurrent tests, the stats might have changed slightly
548        // Just verify that we can get stats and they're reasonable
549        assert!(
550            stats2.performance.total_intern_time_ns >= stats.performance.total_intern_time_ns,
551            "Stats should not decrease: original={}, current={}",
552            stats.performance.total_intern_time_ns,
553            stats2.performance.total_intern_time_ns
554        );
555    }
556
557    #[test]
558    fn test_recommendations_generation() {
559        let monitor = StringPoolMonitor::new();
560
561        let pool_stats = StringPoolStats {
562            unique_strings: 1000,
563            intern_operations: 1000, // Low cache hit rate
564            cache_hits: 100,
565            memory_saved_bytes: 1000,
566            average_string_length: 50.0,
567        };
568
569        let performance = PerformanceMetrics {
570            avg_intern_time_ns: 500.0, // Good performance
571            peak_ops_per_second: 1000.0,
572            current_ops_per_second: 100.0,
573            total_intern_time_ns: 500000,
574            uptime_seconds: 60.0,
575        };
576
577        let memory_efficiency = MemoryEfficiencyMetrics {
578            efficiency_ratio: 0.2, // Poor efficiency
579            memory_without_interning_bytes: 50000,
580            memory_with_interning_bytes: 40000,
581            pool_overhead_bytes: 5000,
582        };
583
584        let recommendations =
585            monitor.generate_recommendations(&pool_stats, &performance, &memory_efficiency);
586
587        // Should generate recommendations for poor efficiency and low cache hit rate
588        assert!(!recommendations.is_empty());
589        assert!(recommendations.iter().any(|r| matches!(
590            r.recommendation_type,
591            RecommendationType::MemoryOptimization
592        )));
593        assert!(recommendations
594            .iter()
595            .any(|r| matches!(r.recommendation_type, RecommendationType::UsageOptimization)));
596
597        // Test with empty stats
598        let empty_stats = StringPoolStats {
599            unique_strings: 0,
600            intern_operations: 0,
601            cache_hits: 0,
602            memory_saved_bytes: 0,
603            average_string_length: 0.0,
604        };
605        let empty_recs = monitor.generate_recommendations(
606            &empty_stats,
607            &PerformanceMetrics::default(),
608            &MemoryEfficiencyMetrics::default(),
609        );
610        assert!(!empty_recs.is_empty());
611    }
612
613    #[test]
614    fn test_string_usage_info() {
615        let info = StringUsageInfo {
616            content: "test".to_string(),
617            usage_count: 10,
618            memory_saved_bytes: 100,
619        };
620
621        assert_eq!(info.content, "test");
622        assert_eq!(info.usage_count, 10);
623        assert_eq!(info.memory_saved_bytes, 100);
624    }
625
626    #[test]
627    fn test_length_bucket() {
628        let bucket = LengthBucket {
629            min_length: 0,
630            max_length: 10,
631            count: 5,
632            total_bytes: 50,
633        };
634
635        assert_eq!(bucket.min_length, 0);
636        assert_eq!(bucket.max_length, 10);
637        assert_eq!(bucket.count, 5);
638        assert_eq!(bucket.total_bytes, 50);
639    }
640
641    #[test]
642    fn test_temporal_patterns() {
643        let patterns = TemporalPatterns {
644            ops_last_minute: 10,
645            ops_last_hour: 100,
646            peak_ops_per_minute: 50,
647        };
648
649        assert_eq!(patterns.ops_last_minute, 10);
650        assert_eq!(patterns.ops_last_hour, 100);
651        assert_eq!(patterns.peak_ops_per_minute, 50);
652    }
653}