Skip to main content

codesearch/cache/
mod.rs

1mod file_meta;
2
3pub use file_meta::{normalize_path, normalize_path_str, FileMetaStore};
4
5use moka::sync::Cache;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::sync::Arc;
8
9/// High-performance embedding cache with memory awareness
10pub struct EmbeddingCache {
11    cache: Cache<String, Arc<Vec<f32>>>,
12    hits: AtomicU64,
13    misses: AtomicU64,
14    max_memory_mb: usize,
15}
16
17impl EmbeddingCache {
18    #[allow(dead_code)] // Reserved for future caching layer
19    pub fn new(max_memory_mb: usize) -> Self {
20        // Calculate max entries based on memory budget
21        let avg_embedding_size = 384 * std::mem::size_of::<f32>(); // 384-dim f32 vector
22        let max_entries = (max_memory_mb * 1024 * 1024) / avg_embedding_size;
23
24        let cache = Cache::builder()
25            .max_capacity(max_entries as u64)
26            .weigher(|_key: &String, value: &Arc<Vec<f32>>| {
27                (value.len() * std::mem::size_of::<f32>()) as u32
28            })
29            .build();
30
31        Self {
32            cache,
33            hits: AtomicU64::new(0),
34            misses: AtomicU64::new(0),
35            max_memory_mb,
36        }
37    }
38
39    /// Get or compute an embedding
40    #[allow(dead_code)] // Reserved for future caching layer
41    pub fn get_or_compute<F>(&self, key: &str, compute: F) -> Arc<Vec<f32>>
42    where
43        F: FnOnce() -> Vec<f32>,
44    {
45        if let Some(value) = self.cache.get(key) {
46            self.hits.fetch_add(1, Ordering::Relaxed);
47            value
48        } else {
49            self.misses.fetch_add(1, Ordering::Relaxed);
50            let value = Arc::new(compute());
51            self.cache.insert(key.to_string(), value.clone());
52            value
53        }
54    }
55
56    /// Get cache hit rate
57    #[allow(dead_code)] // Reserved for future caching layer
58    pub fn hit_rate(&self) -> f64 {
59        let hits = self.hits.load(Ordering::Relaxed);
60        let misses = self.misses.load(Ordering::Relaxed);
61        let total = hits + misses;
62
63        if total == 0 {
64            0.0
65        } else {
66            hits as f64 / total as f64
67        }
68    }
69
70    /// Get cache statistics
71    #[allow(dead_code)] // Reserved for future caching layer
72    pub fn stats(&self) -> CacheStats {
73        CacheStats {
74            hits: self.hits.load(Ordering::Relaxed),
75            misses: self.misses.load(Ordering::Relaxed),
76            size: self.cache.entry_count(),
77            max_memory_mb: self.max_memory_mb,
78        }
79    }
80}
81
82#[derive(Debug, Clone)]
83#[allow(dead_code)] // Fields used via stats() method
84pub struct CacheStats {
85    pub hits: u64,
86    pub misses: u64,
87    pub size: u64,
88    pub max_memory_mb: usize,
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_cache() {
97        let cache = EmbeddingCache::new(100);
98
99        let result = cache.get_or_compute("test", || vec![1.0, 2.0, 3.0]);
100        assert_eq!(*result, vec![1.0, 2.0, 3.0]);
101
102        let stats = cache.stats();
103        assert_eq!(stats.misses, 1);
104        assert_eq!(stats.hits, 0);
105    }
106}