organizational_intelligence_plugin/
perf.rs

1//! Performance Tuning Utilities
2//!
3//! PROD-005: Optimizations for production workloads
4//! Provides batching, caching, and parallel processing utilities
5
6use std::collections::HashMap;
7use std::hash::Hash;
8use std::sync::Arc;
9use std::time::{Duration, Instant};
10
11/// Batch processor for efficient bulk operations
12pub struct BatchProcessor<T> {
13    batch_size: usize,
14    buffer: Vec<T>,
15}
16
17impl<T> BatchProcessor<T> {
18    /// Create new batch processor
19    pub fn new(batch_size: usize) -> Self {
20        Self {
21            batch_size,
22            buffer: Vec::with_capacity(batch_size),
23        }
24    }
25
26    /// Add item to batch, returns batch if full
27    pub fn add(&mut self, item: T) -> Option<Vec<T>> {
28        self.buffer.push(item);
29        if self.buffer.len() >= self.batch_size {
30            Some(self.flush())
31        } else {
32            None
33        }
34    }
35
36    /// Flush remaining items
37    pub fn flush(&mut self) -> Vec<T> {
38        std::mem::take(&mut self.buffer)
39    }
40
41    /// Check if buffer is empty
42    pub fn is_empty(&self) -> bool {
43        self.buffer.is_empty()
44    }
45
46    /// Get current buffer size
47    pub fn len(&self) -> usize {
48        self.buffer.len()
49    }
50}
51
52/// Simple LRU cache for expensive computations
53pub struct LruCache<K, V> {
54    capacity: usize,
55    map: HashMap<K, (V, Instant)>,
56    ttl: Option<Duration>,
57}
58
59impl<K: Eq + Hash + Clone, V: Clone> LruCache<K, V> {
60    /// Create cache with capacity
61    pub fn new(capacity: usize) -> Self {
62        Self {
63            capacity,
64            map: HashMap::with_capacity(capacity),
65            ttl: None,
66        }
67    }
68
69    /// Create cache with TTL
70    pub fn with_ttl(capacity: usize, ttl: Duration) -> Self {
71        Self {
72            capacity,
73            map: HashMap::with_capacity(capacity),
74            ttl: Some(ttl),
75        }
76    }
77
78    /// Get value from cache
79    pub fn get(&self, key: &K) -> Option<V> {
80        self.map.get(key).and_then(|(v, inserted)| {
81            if let Some(ttl) = self.ttl {
82                if inserted.elapsed() > ttl {
83                    return None;
84                }
85            }
86            Some(v.clone())
87        })
88    }
89
90    /// Insert value into cache
91    pub fn insert(&mut self, key: K, value: V) {
92        // Evict oldest if at capacity
93        if self.map.len() >= self.capacity && !self.map.contains_key(&key) {
94            if let Some(oldest_key) = self.find_oldest() {
95                self.map.remove(&oldest_key);
96            }
97        }
98        self.map.insert(key, (value, Instant::now()));
99    }
100
101    /// Check if key exists
102    pub fn contains(&self, key: &K) -> bool {
103        self.get(key).is_some()
104    }
105
106    /// Clear cache
107    pub fn clear(&mut self) {
108        self.map.clear();
109    }
110
111    /// Get cache size
112    pub fn len(&self) -> usize {
113        self.map.len()
114    }
115
116    /// Check if empty
117    pub fn is_empty(&self) -> bool {
118        self.map.is_empty()
119    }
120
121    fn find_oldest(&self) -> Option<K> {
122        self.map
123            .iter()
124            .min_by_key(|(_, (_, instant))| *instant)
125            .map(|(k, _)| k.clone())
126    }
127}
128
129/// Memory-efficient ring buffer for streaming data
130pub struct RingBuffer<T> {
131    buffer: Vec<Option<T>>,
132    head: usize,
133    tail: usize,
134    size: usize,
135}
136
137impl<T: Clone> RingBuffer<T> {
138    /// Create ring buffer with capacity
139    pub fn new(capacity: usize) -> Self {
140        Self {
141            buffer: vec![None; capacity],
142            head: 0,
143            tail: 0,
144            size: 0,
145        }
146    }
147
148    /// Push item (overwrites oldest if full)
149    pub fn push(&mut self, item: T) {
150        self.buffer[self.tail] = Some(item);
151        self.tail = (self.tail + 1) % self.buffer.len();
152        if self.size < self.buffer.len() {
153            self.size += 1;
154        } else {
155            self.head = (self.head + 1) % self.buffer.len();
156        }
157    }
158
159    /// Pop oldest item
160    pub fn pop(&mut self) -> Option<T> {
161        if self.size == 0 {
162            return None;
163        }
164        let item = self.buffer[self.head].take();
165        self.head = (self.head + 1) % self.buffer.len();
166        self.size -= 1;
167        item
168    }
169
170    /// Get current size
171    pub fn len(&self) -> usize {
172        self.size
173    }
174
175    /// Check if empty
176    pub fn is_empty(&self) -> bool {
177        self.size == 0
178    }
179
180    /// Check if full
181    pub fn is_full(&self) -> bool {
182        self.size == self.buffer.len()
183    }
184
185    /// Get all items as vector
186    pub fn to_vec(&self) -> Vec<T> {
187        let mut result = Vec::with_capacity(self.size);
188        let mut idx = self.head;
189        for _ in 0..self.size {
190            if let Some(ref item) = self.buffer[idx] {
191                result.push(item.clone());
192            }
193            idx = (idx + 1) % self.buffer.len();
194        }
195        result
196    }
197}
198
199/// Performance statistics collector
200#[derive(Debug, Clone, Default)]
201pub struct PerfStats {
202    pub operation_count: u64,
203    pub total_duration_ns: u64,
204    pub min_duration_ns: u64,
205    pub max_duration_ns: u64,
206}
207
208impl PerfStats {
209    pub fn new() -> Self {
210        Self {
211            operation_count: 0,
212            total_duration_ns: 0,
213            min_duration_ns: u64::MAX,
214            max_duration_ns: 0,
215        }
216    }
217
218    /// Record an operation duration
219    pub fn record(&mut self, duration_ns: u64) {
220        self.operation_count += 1;
221        self.total_duration_ns += duration_ns;
222        self.min_duration_ns = self.min_duration_ns.min(duration_ns);
223        self.max_duration_ns = self.max_duration_ns.max(duration_ns);
224    }
225
226    /// Get average duration in nanoseconds
227    pub fn avg_ns(&self) -> u64 {
228        if self.operation_count == 0 {
229            0
230        } else {
231            self.total_duration_ns / self.operation_count
232        }
233    }
234
235    /// Get average duration in microseconds
236    pub fn avg_us(&self) -> f64 {
237        self.avg_ns() as f64 / 1000.0
238    }
239
240    /// Get average duration in milliseconds
241    pub fn avg_ms(&self) -> f64 {
242        self.avg_ns() as f64 / 1_000_000.0
243    }
244
245    /// Get throughput (operations per second)
246    pub fn throughput(&self) -> f64 {
247        if self.total_duration_ns == 0 {
248            0.0
249        } else {
250            self.operation_count as f64 / (self.total_duration_ns as f64 / 1_000_000_000.0)
251        }
252    }
253}
254
255/// Scoped timer for measuring code blocks
256pub struct ScopedTimer<'a> {
257    stats: &'a mut PerfStats,
258    start: Instant,
259}
260
261impl<'a> ScopedTimer<'a> {
262    pub fn new(stats: &'a mut PerfStats) -> Self {
263        Self {
264            stats,
265            start: Instant::now(),
266        }
267    }
268}
269
270impl<'a> Drop for ScopedTimer<'a> {
271    fn drop(&mut self) {
272        let duration = self.start.elapsed().as_nanos() as u64;
273        self.stats.record(duration);
274    }
275}
276
277/// Parallel chunk processor
278pub fn process_chunks<T, R, F>(items: Vec<T>, chunk_size: usize, f: F) -> Vec<R>
279where
280    T: Send + Sync,
281    R: Send,
282    F: Fn(&[T]) -> Vec<R> + Send + Sync,
283{
284    let chunks: Vec<_> = items.chunks(chunk_size).collect();
285    let f = Arc::new(f);
286
287    // Process sequentially (parallel version would use rayon)
288    chunks.iter().flat_map(|chunk| f(chunk)).collect()
289}
290
291/// Estimate memory usage for feature vectors
292pub fn estimate_memory_bytes(feature_count: usize, dimensions: usize) -> usize {
293    // Each feature: dimensions * sizeof(f32) + struct overhead
294    let feature_size = dimensions * std::mem::size_of::<f32>() + 64; // 64 bytes struct overhead
295    feature_count * feature_size
296}
297
298/// Format bytes as human-readable string
299pub fn format_bytes(bytes: usize) -> String {
300    const KB: usize = 1024;
301    const MB: usize = KB * 1024;
302    const GB: usize = MB * 1024;
303
304    if bytes >= GB {
305        format!("{:.2} GB", bytes as f64 / GB as f64)
306    } else if bytes >= MB {
307        format!("{:.2} MB", bytes as f64 / MB as f64)
308    } else if bytes >= KB {
309        format!("{:.2} KB", bytes as f64 / KB as f64)
310    } else {
311        format!("{} bytes", bytes)
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    #[test]
320    fn test_batch_processor() {
321        let mut processor = BatchProcessor::new(3);
322
323        assert!(processor.add(1).is_none());
324        assert!(processor.add(2).is_none());
325
326        let batch = processor.add(3);
327        assert!(batch.is_some());
328        assert_eq!(batch.unwrap(), vec![1, 2, 3]);
329
330        processor.add(4);
331        let remaining = processor.flush();
332        assert_eq!(remaining, vec![4]);
333    }
334
335    #[test]
336    fn test_lru_cache() {
337        let mut cache = LruCache::new(2);
338
339        cache.insert("a", 1);
340        cache.insert("b", 2);
341
342        assert_eq!(cache.get(&"a"), Some(1));
343        assert_eq!(cache.get(&"b"), Some(2));
344
345        // This should evict "a" (oldest)
346        cache.insert("c", 3);
347        assert_eq!(cache.get(&"a"), None);
348        assert_eq!(cache.get(&"c"), Some(3));
349    }
350
351    #[test]
352    fn test_ring_buffer() {
353        let mut buffer = RingBuffer::new(3);
354
355        buffer.push(1);
356        buffer.push(2);
357        buffer.push(3);
358        assert!(buffer.is_full());
359
360        // Overwrites oldest (1)
361        buffer.push(4);
362        assert_eq!(buffer.to_vec(), vec![2, 3, 4]);
363
364        assert_eq!(buffer.pop(), Some(2));
365        assert_eq!(buffer.len(), 2);
366    }
367
368    #[test]
369    fn test_perf_stats() {
370        let mut stats = PerfStats::new();
371
372        stats.record(1000);
373        stats.record(2000);
374        stats.record(3000);
375
376        assert_eq!(stats.operation_count, 3);
377        assert_eq!(stats.avg_ns(), 2000);
378        assert_eq!(stats.min_duration_ns, 1000);
379        assert_eq!(stats.max_duration_ns, 3000);
380    }
381
382    #[test]
383    fn test_scoped_timer() {
384        let mut stats = PerfStats::new();
385
386        {
387            let _timer = ScopedTimer::new(&mut stats);
388            std::thread::sleep(std::time::Duration::from_millis(1));
389        }
390
391        assert_eq!(stats.operation_count, 1);
392        assert!(stats.avg_ns() > 0);
393    }
394
395    #[test]
396    fn test_estimate_memory() {
397        let bytes = estimate_memory_bytes(1000, 8);
398        assert!(bytes > 0);
399        // 1000 features * (8 * 4 bytes + 64 overhead) = 96,000 bytes
400        assert!(bytes >= 96_000);
401    }
402
403    #[test]
404    fn test_format_bytes() {
405        assert_eq!(format_bytes(500), "500 bytes");
406        assert_eq!(format_bytes(1024), "1.00 KB");
407        assert_eq!(format_bytes(1024 * 1024), "1.00 MB");
408        assert_eq!(format_bytes(1024 * 1024 * 1024), "1.00 GB");
409    }
410
411    #[test]
412    fn test_process_chunks() {
413        let items = vec![1, 2, 3, 4, 5];
414        let results = process_chunks(items, 2, |chunk| chunk.iter().map(|x| x * 2).collect());
415        assert_eq!(results, vec![2, 4, 6, 8, 10]);
416    }
417
418    #[test]
419    fn test_batch_processor_empty() {
420        let processor: BatchProcessor<i32> = BatchProcessor::new(5);
421        assert!(processor.is_empty());
422        assert_eq!(processor.len(), 0);
423    }
424
425    #[test]
426    fn test_batch_processor_flush_empty() {
427        let mut processor: BatchProcessor<i32> = BatchProcessor::new(5);
428        let result = processor.flush();
429        assert!(result.is_empty());
430    }
431
432    #[test]
433    fn test_lru_cache_with_ttl() {
434        let mut cache = LruCache::with_ttl(10, Duration::from_millis(50));
435
436        cache.insert("key", 42);
437        assert_eq!(cache.get(&"key"), Some(42));
438
439        // Wait for TTL to expire
440        std::thread::sleep(Duration::from_millis(60));
441        assert_eq!(cache.get(&"key"), None);
442    }
443
444    #[test]
445    fn test_lru_cache_contains() {
446        let mut cache = LruCache::new(2);
447        cache.insert("a", 1);
448
449        assert!(cache.contains(&"a"));
450        assert!(!cache.contains(&"b"));
451    }
452
453    #[test]
454    fn test_lru_cache_clear() {
455        let mut cache = LruCache::new(2);
456        cache.insert("a", 1);
457        cache.insert("b", 2);
458        assert_eq!(cache.len(), 2);
459
460        cache.clear();
461        assert_eq!(cache.len(), 0);
462        assert!(cache.is_empty());
463    }
464
465    #[test]
466    fn test_lru_cache_update_existing() {
467        let mut cache = LruCache::new(2);
468        cache.insert("a", 1);
469        cache.insert("a", 2); // Update
470
471        assert_eq!(cache.get(&"a"), Some(2));
472        assert_eq!(cache.len(), 1); // Should still be 1
473    }
474
475    #[test]
476    fn test_ring_buffer_empty() {
477        let buffer: RingBuffer<i32> = RingBuffer::new(5);
478        assert!(buffer.is_empty());
479        assert_eq!(buffer.len(), 0);
480        assert!(!buffer.is_full());
481    }
482
483    #[test]
484    fn test_ring_buffer_pop_empty() {
485        let mut buffer: RingBuffer<i32> = RingBuffer::new(5);
486        assert_eq!(buffer.pop(), None);
487    }
488
489    #[test]
490    fn test_ring_buffer_to_vec_empty() {
491        let buffer: RingBuffer<i32> = RingBuffer::new(5);
492        assert!(buffer.to_vec().is_empty());
493    }
494
495    #[test]
496    fn test_ring_buffer_wrap_around() {
497        let mut buffer = RingBuffer::new(3);
498
499        // Fill buffer
500        buffer.push(1);
501        buffer.push(2);
502        buffer.push(3);
503        assert!(buffer.is_full());
504
505        // Overwrite (wraps around)
506        buffer.push(4); // Overwrites 1
507        buffer.push(5); // Overwrites 2
508
509        let vec = buffer.to_vec();
510        assert_eq!(vec, vec![3, 4, 5]);
511    }
512
513    #[test]
514    fn test_perf_stats_default() {
515        let stats = PerfStats::default();
516        assert_eq!(stats.operation_count, 0);
517        assert_eq!(stats.avg_ns(), 0);
518    }
519
520    #[test]
521    fn test_perf_stats_zero_operations() {
522        let stats = PerfStats::new();
523        assert_eq!(stats.avg_ns(), 0);
524        assert_eq!(stats.avg_us(), 0.0);
525        assert_eq!(stats.avg_ms(), 0.0);
526        assert_eq!(stats.throughput(), 0.0);
527    }
528
529    #[test]
530    fn test_perf_stats_conversions() {
531        let mut stats = PerfStats::new();
532        stats.record(1_000_000); // 1ms in nanoseconds
533
534        assert_eq!(stats.avg_ns(), 1_000_000);
535        assert_eq!(stats.avg_us(), 1000.0);
536        assert_eq!(stats.avg_ms(), 1.0);
537    }
538
539    #[test]
540    fn test_perf_stats_throughput() {
541        let mut stats = PerfStats::new();
542
543        // 10 operations, each taking 100ms = 1 second total
544        for _ in 0..10 {
545            stats.record(100_000_000); // 100ms in nanoseconds
546        }
547
548        let throughput = stats.throughput();
549        // 10 operations / 1 second = 10 ops/sec
550        assert!((throughput - 10.0).abs() < 0.1);
551    }
552
553    #[test]
554    fn test_perf_stats_min_max() {
555        let mut stats = PerfStats::new();
556        stats.record(1000);
557        stats.record(5000);
558        stats.record(3000);
559
560        assert_eq!(stats.min_duration_ns, 1000);
561        assert_eq!(stats.max_duration_ns, 5000);
562    }
563
564    #[test]
565    fn test_format_bytes_boundaries() {
566        assert_eq!(format_bytes(0), "0 bytes");
567        assert_eq!(format_bytes(1023), "1023 bytes");
568        assert_eq!(format_bytes(2048), "2.00 KB");
569        assert_eq!(format_bytes(1536 * 1024), "1.50 MB");
570        assert_eq!(format_bytes(2 * 1024 * 1024 * 1024), "2.00 GB");
571    }
572
573    #[test]
574    fn test_estimate_memory_zero() {
575        let bytes = estimate_memory_bytes(0, 8);
576        assert_eq!(bytes, 0);
577    }
578
579    #[test]
580    fn test_process_chunks_empty() {
581        let items: Vec<i32> = vec![];
582        let results = process_chunks(items, 2, |chunk| chunk.iter().map(|x| x * 2).collect());
583        assert!(results.is_empty());
584    }
585
586    #[test]
587    fn test_process_chunks_single_chunk() {
588        let items = vec![1, 2];
589        let results = process_chunks(items, 10, |chunk| chunk.iter().map(|x| x * 2).collect());
590        assert_eq!(results, vec![2, 4]);
591    }
592}