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