tailwind_rs_core/
performance.rs

1//! Performance optimization system for tailwind-rs
2//!
3//! This module provides caching and performance optimization functionality
4//! for class generation and CSS optimization.
5
6use crate::error::Result;
7use lru::LruCache;
8use parking_lot::RwLock;
9use std::collections::HashMap;
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::Arc;
12use std::time::{Duration, Instant};
13
14/// Performance metrics for tracking optimization effectiveness
15#[derive(Debug, Clone)]
16pub struct PerformanceMetric {
17    pub operation: String,
18    pub duration: Duration,
19    pub timestamp: Instant,
20    pub success: bool,
21}
22
23/// Error metrics for tracking validation and processing errors
24#[derive(Debug, Clone)]
25pub struct ErrorMetric {
26    pub error_type: String,
27    pub error_message: String,
28    pub timestamp: Instant,
29    pub count: u64,
30}
31
32/// Usage metrics for tracking class generation patterns
33#[derive(Debug, Clone)]
34pub struct UsageMetric {
35    pub class_pattern: String,
36    pub usage_count: u64,
37    pub last_used: Instant,
38    pub average_generation_time: Duration,
39}
40
41/// Caches generated classes for performance
42#[derive(Debug)]
43pub struct ClassCache {
44    cache: Arc<RwLock<LruCache<String, String>>>,
45    hit_rate: AtomicU64,
46    miss_rate: AtomicU64,
47    total_requests: AtomicU64,
48}
49
50impl ClassCache {
51    /// Create a new cache with specified capacity
52    pub fn new(capacity: usize) -> Self {
53        Self {
54            cache: Arc::new(RwLock::new(LruCache::new(
55                std::num::NonZeroUsize::new(capacity)
56                    .unwrap_or(std::num::NonZeroUsize::new(100).unwrap()),
57            ))),
58            hit_rate: AtomicU64::new(0),
59            miss_rate: AtomicU64::new(0),
60            total_requests: AtomicU64::new(0),
61        }
62    }
63
64    /// Retrieve a cached class string
65    pub fn get(&self, key: &str) -> Option<String> {
66        self.total_requests.fetch_add(1, Ordering::Relaxed);
67
68        let mut cache = self.cache.write();
69        if let Some(value) = cache.get(key) {
70            self.hit_rate.fetch_add(1, Ordering::Relaxed);
71            Some(value.clone())
72        } else {
73            self.miss_rate.fetch_add(1, Ordering::Relaxed);
74            None
75        }
76    }
77
78    /// Store a class string in the cache
79    pub fn put(&self, key: String, value: String) {
80        let mut cache = self.cache.write();
81        cache.put(key, value);
82    }
83
84    /// Get cache hit rate (0.0 to 1.0)
85    pub fn hit_rate(&self) -> f64 {
86        let hits = self.hit_rate.load(Ordering::Relaxed) as f64;
87        let total = self.total_requests.load(Ordering::Relaxed) as f64;
88
89        if total == 0.0 {
90            0.0
91        } else {
92            hits / total
93        }
94    }
95
96    /// Get cache miss rate (0.0 to 1.0)
97    pub fn miss_rate(&self) -> f64 {
98        let misses = self.miss_rate.load(Ordering::Relaxed) as f64;
99        let total = self.total_requests.load(Ordering::Relaxed) as f64;
100
101        if total == 0.0 {
102            0.0
103        } else {
104            misses / total
105        }
106    }
107
108    /// Get total number of requests
109    pub fn total_requests(&self) -> u64 {
110        self.total_requests.load(Ordering::Relaxed)
111    }
112
113    /// Clear the cache
114    pub fn clear(&self) {
115        let mut cache = self.cache.write();
116        cache.clear();
117    }
118
119    /// Get cache size
120    pub fn len(&self) -> usize {
121        let cache = self.cache.read();
122        cache.len()
123    }
124
125    /// Check if cache is empty
126    pub fn is_empty(&self) -> bool {
127        let cache = self.cache.read();
128        cache.is_empty()
129    }
130}
131
132/// Performance optimization levels
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum OptimizationLevel {
135    None,
136    Low,
137    Medium,
138    High,
139    Maximum,
140}
141
142impl OptimizationLevel {
143    /// Get the cache capacity for this optimization level
144    pub fn cache_capacity(&self) -> usize {
145        match self {
146            OptimizationLevel::None => 0,
147            OptimizationLevel::Low => 100,
148            OptimizationLevel::Medium => 500,
149            OptimizationLevel::High => 1000,
150            OptimizationLevel::Maximum => 5000,
151        }
152    }
153
154    /// Get the optimization factor (1.0 = no optimization, higher = more optimization)
155    pub fn optimization_factor(&self) -> f64 {
156        match self {
157            OptimizationLevel::None => 1.0,
158            OptimizationLevel::Low => 1.2,
159            OptimizationLevel::Medium => 1.5,
160            OptimizationLevel::High => 2.0,
161            OptimizationLevel::Maximum => 3.0,
162        }
163    }
164}
165
166/// Optimizes class generation performance
167#[derive(Debug)]
168pub struct PerformanceOptimizer {
169    class_cache: ClassCache,
170    css_cache: ClassCache,
171    optimization_level: OptimizationLevel,
172    performance_metrics: Arc<RwLock<Vec<PerformanceMetric>>>,
173    error_metrics: Arc<RwLock<Vec<ErrorMetric>>>,
174    usage_metrics: Arc<RwLock<HashMap<String, UsageMetric>>>,
175}
176
177impl PerformanceOptimizer {
178    /// Create a new optimizer instance
179    pub fn new() -> Self {
180        Self::with_optimization_level(OptimizationLevel::Medium)
181    }
182
183    /// Create a new optimizer with specific optimization level
184    pub fn with_optimization_level(level: OptimizationLevel) -> Self {
185        let capacity = level.cache_capacity();
186        Self {
187            class_cache: ClassCache::new(capacity),
188            css_cache: ClassCache::new(capacity),
189            optimization_level: level,
190            performance_metrics: Arc::new(RwLock::new(Vec::new())),
191            error_metrics: Arc::new(RwLock::new(Vec::new())),
192            usage_metrics: Arc::new(RwLock::new(HashMap::new())),
193        }
194    }
195
196    /// Optimize class generation with caching
197    pub fn optimize_class_generation(&mut self, classes: &[String]) -> Result<String> {
198        let start = Instant::now();
199        let cache_key = self.generate_cache_key(classes);
200
201        // Try to get from cache first
202        if let Some(cached_result) = self.class_cache.get(&cache_key) {
203            self.record_performance("class_generation_cached", start.elapsed(), true);
204            return Ok(cached_result);
205        }
206
207        // Generate classes
208        let result = self.generate_classes(classes)?;
209
210        // Cache the result
211        self.class_cache.put(cache_key, result.clone());
212
213        self.record_performance("class_generation", start.elapsed(), true);
214        Ok(result)
215    }
216
217    /// Optimize CSS generation with caching
218    pub fn optimize_css_generation(&mut self, css: &str) -> Result<String> {
219        let start = Instant::now();
220        let cache_key = format!("css:{}", css);
221
222        // Try to get from cache first
223        if let Some(cached_result) = self.css_cache.get(&cache_key) {
224            self.record_performance("css_generation_cached", start.elapsed(), true);
225            return Ok(cached_result);
226        }
227
228        // Generate optimized CSS
229        let result = self.optimize_css(css)?;
230
231        // Cache the result
232        self.css_cache.put(cache_key, result.clone());
233
234        self.record_performance("css_generation", start.elapsed(), true);
235        Ok(result)
236    }
237
238    /// Generate cache key for classes
239    fn generate_cache_key(&self, classes: &[String]) -> String {
240        let mut key = String::new();
241        for class in classes {
242            key.push_str(class);
243            key.push('|');
244        }
245        key
246    }
247
248    /// Generate classes with optimization
249    fn generate_classes(&self, classes: &[String]) -> Result<String> {
250        // Apply optimization based on level
251        let optimized_classes = match self.optimization_level {
252            OptimizationLevel::None => classes.to_vec(),
253            OptimizationLevel::Low => self.optimize_classes_low(classes),
254            OptimizationLevel::Medium => self.optimize_classes_medium(classes),
255            OptimizationLevel::High => self.optimize_classes_high(classes),
256            OptimizationLevel::Maximum => self.optimize_classes_maximum(classes),
257        };
258
259        Ok(optimized_classes.join(" "))
260    }
261
262    /// Low-level optimization
263    fn optimize_classes_low(&self, classes: &[String]) -> Vec<String> {
264        // Remove duplicates
265        let mut unique_classes: Vec<String> = classes.to_vec();
266        unique_classes.sort();
267        unique_classes.dedup();
268        unique_classes
269    }
270
271    /// Medium-level optimization
272    fn optimize_classes_medium(&self, classes: &[String]) -> Vec<String> {
273        let mut optimized = self.optimize_classes_low(classes);
274
275        // Remove conflicting classes
276        optimized = self.remove_conflicting_classes(optimized);
277
278        optimized
279    }
280
281    /// High-level optimization
282    fn optimize_classes_high(&self, classes: &[String]) -> Vec<String> {
283        let mut optimized = self.optimize_classes_medium(classes);
284
285        // Merge similar classes
286        optimized = self.merge_similar_classes(optimized);
287
288        optimized
289    }
290
291    /// Maximum-level optimization
292    fn optimize_classes_maximum(&self, classes: &[String]) -> Vec<String> {
293        let mut optimized = self.optimize_classes_high(classes);
294
295        // Apply advanced optimizations
296        optimized = self.apply_advanced_optimizations(optimized);
297
298        optimized
299    }
300
301    /// Remove conflicting classes
302    fn remove_conflicting_classes(&self, classes: Vec<String>) -> Vec<String> {
303        let mut result = Vec::new();
304        let mut seen_groups: HashMap<String, String> = HashMap::new();
305
306        for class in classes {
307            let group = self.get_class_group(&class);
308            if let Some(existing) = seen_groups.get(&group) {
309                // Keep the more specific class
310                if self.is_more_specific(&class, existing) {
311                    if let Some(pos) = result.iter().position(|c| c == existing) {
312                        result.remove(pos);
313                    }
314                    result.push(class.clone());
315                    seen_groups.insert(group, class);
316                }
317            } else {
318                result.push(class.clone());
319                seen_groups.insert(group, class);
320            }
321        }
322
323        result
324    }
325
326    /// Merge similar classes
327    fn merge_similar_classes(&self, classes: Vec<String>) -> Vec<String> {
328        // This is a simplified implementation
329        // In a real implementation, you would merge classes like:
330        // "px-2", "px-4" -> "px-4" (keep the larger value)
331        classes
332    }
333
334    /// Apply advanced optimizations
335    fn apply_advanced_optimizations(&self, classes: Vec<String>) -> Vec<String> {
336        // This is a placeholder for advanced optimizations
337        // In a real implementation, you might:
338        // - Combine responsive classes
339        // - Optimize color classes
340        // - Merge spacing classes
341        classes
342    }
343
344    /// Get class group for conflict detection
345    fn get_class_group(&self, class: &str) -> String {
346        if class.starts_with("bg-") {
347            "background".to_string()
348        } else if class.starts_with("text-") {
349            "text".to_string()
350        } else if class.starts_with("border-") {
351            "border".to_string()
352        } else if class.starts_with("p-") || class.starts_with("px-") || class.starts_with("py-") {
353            "padding".to_string()
354        } else if class.starts_with("m-") || class.starts_with("mx-") || class.starts_with("my-") {
355            "margin".to_string()
356        } else {
357            "other".to_string()
358        }
359    }
360
361    /// Check if one class is more specific than another
362    fn is_more_specific(&self, class1: &str, class2: &str) -> bool {
363        // Simple heuristic: longer class names are more specific
364        class1.len() > class2.len()
365    }
366
367    /// Optimize CSS
368    fn optimize_css(&self, css: &str) -> Result<String> {
369        let mut optimized = css.to_string();
370
371        // Remove unnecessary whitespace
372        optimized = optimized.replace("  ", " ");
373        optimized = optimized.replace("\n", "");
374        optimized = optimized.replace("\t", "");
375
376        // Remove trailing semicolons
377        optimized = optimized.replace(";}", "}");
378
379        Ok(optimized)
380    }
381
382    /// Record performance metrics
383    pub fn record_performance(&self, operation: &str, duration: Duration, success: bool) {
384        let metric = PerformanceMetric {
385            operation: operation.to_string(),
386            duration,
387            timestamp: Instant::now(),
388            success,
389        };
390
391        let mut metrics = self.performance_metrics.write();
392        metrics.push(metric);
393
394        // Keep only the last 1000 metrics
395        let len = metrics.len();
396        if len > 1000 {
397            metrics.drain(0..len - 1000);
398        }
399    }
400
401    /// Record error metrics
402    pub fn record_error(&self, error_type: &str, error: &dyn std::error::Error) {
403        let metric = ErrorMetric {
404            error_type: error_type.to_string(),
405            error_message: error.to_string(),
406            timestamp: Instant::now(),
407            count: 1,
408        };
409
410        let mut metrics = self.error_metrics.write();
411        metrics.push(metric);
412
413        // Keep only the last 1000 error metrics
414        let len = metrics.len();
415        if len > 1000 {
416            metrics.drain(0..len - 1000);
417        }
418    }
419
420    /// Record usage metrics
421    pub fn record_usage(&self, class_pattern: &str, generation_time: Duration) {
422        let mut metrics = self.usage_metrics.write();
423
424        if let Some(usage) = metrics.get_mut(class_pattern) {
425            usage.usage_count += 1;
426            usage.last_used = Instant::now();
427            usage.average_generation_time = Duration::from_nanos(
428                ((usage.average_generation_time.as_nanos() + generation_time.as_nanos()) / 2)
429                    as u64,
430            );
431        } else {
432            metrics.insert(
433                class_pattern.to_string(),
434                UsageMetric {
435                    class_pattern: class_pattern.to_string(),
436                    usage_count: 1,
437                    last_used: Instant::now(),
438                    average_generation_time: generation_time,
439                },
440            );
441        }
442    }
443
444    /// Get performance metrics
445    pub fn get_performance_metrics(&self) -> Vec<PerformanceMetric> {
446        let metrics = self.performance_metrics.read();
447        metrics.clone()
448    }
449
450    /// Get error metrics
451    pub fn get_error_metrics(&self) -> Vec<ErrorMetric> {
452        let metrics = self.error_metrics.read();
453        metrics.clone()
454    }
455
456    /// Get usage metrics
457    pub fn get_usage_metrics(&self) -> HashMap<String, UsageMetric> {
458        let metrics = self.usage_metrics.read();
459        metrics.clone()
460    }
461
462    /// Get cache statistics
463    pub fn get_cache_stats(&self) -> CacheStats {
464        CacheStats {
465            class_cache_hit_rate: self.class_cache.hit_rate(),
466            class_cache_miss_rate: self.class_cache.miss_rate(),
467            class_cache_total_requests: self.class_cache.total_requests(),
468            class_cache_size: self.class_cache.len(),
469            css_cache_hit_rate: self.css_cache.hit_rate(),
470            css_cache_miss_rate: self.css_cache.miss_rate(),
471            css_cache_total_requests: self.css_cache.total_requests(),
472            css_cache_size: self.css_cache.len(),
473        }
474    }
475
476    /// Set optimization level
477    pub fn set_optimization_level(&mut self, level: OptimizationLevel) {
478        self.optimization_level = level;
479    }
480
481    /// Get optimization level
482    pub fn optimization_level(&self) -> OptimizationLevel {
483        self.optimization_level
484    }
485}
486
487impl Default for PerformanceOptimizer {
488    fn default() -> Self {
489        Self::new()
490    }
491}
492
493/// Cache statistics
494#[derive(Debug, Clone)]
495pub struct CacheStats {
496    pub class_cache_hit_rate: f64,
497    pub class_cache_miss_rate: f64,
498    pub class_cache_total_requests: u64,
499    pub class_cache_size: usize,
500    pub css_cache_hit_rate: f64,
501    pub css_cache_miss_rate: f64,
502    pub css_cache_total_requests: u64,
503    pub css_cache_size: usize,
504}
505
506#[cfg(test)]
507mod tests {
508    use super::*;
509
510    #[test]
511    fn test_optimization_level() {
512        assert_eq!(OptimizationLevel::None.cache_capacity(), 0);
513        assert_eq!(OptimizationLevel::Low.cache_capacity(), 100);
514        assert_eq!(OptimizationLevel::Medium.cache_capacity(), 500);
515        assert_eq!(OptimizationLevel::High.cache_capacity(), 1000);
516        assert_eq!(OptimizationLevel::Maximum.cache_capacity(), 5000);
517
518        assert_eq!(OptimizationLevel::None.optimization_factor(), 1.0);
519        assert_eq!(OptimizationLevel::Low.optimization_factor(), 1.2);
520        assert_eq!(OptimizationLevel::Medium.optimization_factor(), 1.5);
521        assert_eq!(OptimizationLevel::High.optimization_factor(), 2.0);
522        assert_eq!(OptimizationLevel::Maximum.optimization_factor(), 3.0);
523    }
524
525    #[test]
526    fn test_performance_optimizer_creation() {
527        let optimizer = PerformanceOptimizer::new();
528        assert_eq!(optimizer.optimization_level(), OptimizationLevel::Medium);
529    }
530
531    #[test]
532    fn test_performance_optimizer_with_level() {
533        let optimizer = PerformanceOptimizer::with_optimization_level(OptimizationLevel::High);
534        assert_eq!(optimizer.optimization_level(), OptimizationLevel::High);
535    }
536}