organizational_intelligence_plugin/
perf.rs1use std::collections::HashMap;
7use std::hash::Hash;
8use std::sync::Arc;
9use std::time::{Duration, Instant};
10
11pub struct BatchProcessor<T> {
13 batch_size: usize,
14 buffer: Vec<T>,
15}
16
17impl<T> BatchProcessor<T> {
18 pub fn new(batch_size: usize) -> Self {
20 Self {
21 batch_size,
22 buffer: Vec::with_capacity(batch_size),
23 }
24 }
25
26 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 pub fn flush(&mut self) -> Vec<T> {
38 std::mem::take(&mut self.buffer)
39 }
40
41 pub fn is_empty(&self) -> bool {
43 self.buffer.is_empty()
44 }
45
46 pub fn len(&self) -> usize {
48 self.buffer.len()
49 }
50}
51
52pub 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 pub fn new(capacity: usize) -> Self {
62 Self {
63 capacity,
64 map: HashMap::with_capacity(capacity),
65 ttl: None,
66 }
67 }
68
69 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 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 pub fn insert(&mut self, key: K, value: V) {
92 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 pub fn contains(&self, key: &K) -> bool {
103 self.get(key).is_some()
104 }
105
106 pub fn clear(&mut self) {
108 self.map.clear();
109 }
110
111 pub fn len(&self) -> usize {
113 self.map.len()
114 }
115
116 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
129pub 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 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 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 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 pub fn len(&self) -> usize {
172 self.size
173 }
174
175 pub fn is_empty(&self) -> bool {
177 self.size == 0
178 }
179
180 pub fn is_full(&self) -> bool {
182 self.size == self.buffer.len()
183 }
184
185 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#[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 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 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 pub fn avg_us(&self) -> f64 {
237 self.avg_ns() as f64 / 1000.0
238 }
239
240 pub fn avg_ms(&self) -> f64 {
242 self.avg_ns() as f64 / 1_000_000.0
243 }
244
245 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
255pub 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
277pub 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 chunks.iter().flat_map(|chunk| f(chunk)).collect()
289}
290
291pub fn estimate_memory_bytes(feature_count: usize, dimensions: usize) -> usize {
293 let feature_size = dimensions * std::mem::size_of::<f32>() + 64; feature_count * feature_size
296}
297
298pub 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 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 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 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 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); assert_eq!(cache.get(&"a"), Some(2));
472 assert_eq!(cache.len(), 1); }
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 buffer.push(1);
501 buffer.push(2);
502 buffer.push(3);
503 assert!(buffer.is_full());
504
505 buffer.push(4); buffer.push(5); 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); 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 for _ in 0..10 {
545 stats.record(100_000_000); }
547
548 let throughput = stats.throughput();
549 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}