1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct StringPoolMonitorStats {
15 pub pool_stats: StringPoolStats,
17 pub performance: PerformanceMetrics,
19 pub memory_efficiency: MemoryEfficiencyMetrics,
21 pub usage_patterns: UsagePatterns,
23 pub recommendations: Vec<OptimizationRecommendation>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct PerformanceMetrics {
30 pub avg_intern_time_ns: f64,
32 pub peak_ops_per_second: f64,
34 pub current_ops_per_second: f64,
36 pub total_intern_time_ns: u64,
38 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#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct MemoryEfficiencyMetrics {
57 pub efficiency_ratio: f64,
59 pub memory_without_interning_bytes: u64,
61 pub memory_with_interning_bytes: u64,
63 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#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct UsagePatterns {
81 pub top_strings: Vec<StringUsageInfo>,
83 pub length_distribution: Vec<LengthBucket>,
85 pub temporal_patterns: TemporalPatterns,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct StringUsageInfo {
92 pub content: String,
94 pub usage_count: u64,
96 pub memory_saved_bytes: u64,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct LengthBucket {
103 pub min_length: usize,
105 pub max_length: usize,
107 pub count: usize,
109 pub total_bytes: usize,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct TemporalPatterns {
116 pub ops_last_minute: u64,
118 pub ops_last_hour: u64,
120 pub peak_ops_per_minute: u64,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct OptimizationRecommendation {
127 pub recommendation_type: RecommendationType,
129 pub description: String,
131 pub estimated_impact: String,
133 pub priority: u8,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum RecommendationType {
140 IncreasePoolSize,
142 DecreasePoolSize,
144 FeatureToggle,
146 MemoryOptimization,
148 PerformanceOptimization,
150 UsageOptimization,
152}
153
154pub struct StringPoolMonitor {
156 performance_tracker: Arc<Mutex<PerformanceTracker>>,
158 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>, }
170
171impl StringPoolMonitor {
172 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 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 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 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 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 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 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 let memory_without_interning = avg_string_size * total_intern_ops;
275
276 let pool_overhead = total_unique_strings * 64; 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 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 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 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 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 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
398static GLOBAL_MONITOR: std::sync::OnceLock<StringPoolMonitor> = std::sync::OnceLock::new();
400
401pub fn get_string_pool_monitor() -> &'static StringPoolMonitor {
403 GLOBAL_MONITOR.get_or_init(StringPoolMonitor::new)
404}
405
406pub fn record_intern_operation(duration_ns: u64) {
408 get_string_pool_monitor().record_intern_operation(duration_ns);
409}
410
411pub 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 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 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 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 assert_eq!(stats.performance.avg_intern_time_ns, 150.0);
465 assert_eq!(stats.performance.total_intern_time_ns, 450);
466
467 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 monitor.record_intern_operation(0);
479 let stats = monitor.get_stats();
480 assert_eq!(stats.performance.avg_intern_time_ns, 0.0);
481
482 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 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 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 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 }
528
529 #[test]
530 fn test_global_functions() {
531 let _ = GLOBAL_MONITOR.set(StringPoolMonitor::new());
533
534 record_intern_operation(100);
536 record_intern_operation(200);
537
538 let monitor = get_string_pool_monitor();
540 let stats = monitor.get_stats();
541
542 assert!(stats.performance.total_intern_time_ns >= 300);
544
545 let stats2 = get_string_pool_monitor_stats();
547 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, 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, 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, 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 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 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}