vtcode_core/tools/
cache.rs1use super::types::{EnhancedCacheEntry, EnhancedCacheStats};
4use once_cell::sync::Lazy;
5use quick_cache::sync::Cache;
6use serde_json::Value;
7use std::future::Future;
8use std::sync::Arc;
9use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
10use std::time::Duration;
11
12use parking_lot::RwLock;
13
14use crate::cache::estimate_json_size;
15use vtcode_config::FileReadCacheConfig;
16
17pub static FILE_CACHE: Lazy<FileCache> = Lazy::new(|| FileCache::new(1000));
19
20static FILE_READ_CACHE_CONFIG: Lazy<RwLock<FileReadCacheConfig>> =
21 Lazy::new(|| RwLock::new(FileReadCacheConfig::default()));
22
23pub struct FileCache {
31 file_cache: Arc<Cache<String, EnhancedCacheEntry<Arc<Value>>>>,
32 directory_cache: Arc<Cache<String, EnhancedCacheEntry<Arc<Value>>>>,
33 stats: Arc<parking_lot::Mutex<EnhancedCacheStats>>,
34 max_size_bytes: AtomicUsize,
35 ttl_millis: AtomicU64,
36}
37
38impl FileCache {
39 pub fn new(capacity: usize) -> Self {
40 Self {
41 file_cache: Arc::new(Cache::new(capacity)),
42 directory_cache: Arc::new(Cache::new(capacity / 2)),
43 stats: Arc::new(parking_lot::Mutex::new(EnhancedCacheStats::default())),
44 max_size_bytes: AtomicUsize::new(50 * 1024 * 1024), ttl_millis: AtomicU64::new(300_000), }
47 }
48
49 #[inline]
50 fn ttl(&self) -> Duration {
51 Duration::from_millis(self.ttl_millis.load(Ordering::Relaxed))
52 }
53
54 #[inline]
55 fn max_size_bytes(&self) -> usize {
56 self.max_size_bytes.load(Ordering::Relaxed)
57 }
58
59 pub async fn get_file(&self, key: &str) -> Option<Value> {
61 self.get_file_arc(key).await.map(|arc| (*arc).clone())
62 }
63
64 pub async fn get_file_arc(&self, key: &str) -> Option<Arc<Value>> {
66 let mut stats = self.stats.lock();
67
68 if let Some(entry) = self.file_cache.get(key) {
69 if entry.timestamp.elapsed() < self.ttl() {
71 stats.hits += 1;
73 return Some(Arc::clone(&entry.data));
74 } else {
75 let size = entry.size_bytes;
77 self.file_cache.remove(key);
78 stats.expired_evictions += 1;
79 stats.total_size_bytes = stats.total_size_bytes.saturating_sub(size);
80 stats.file_size_bytes = stats.file_size_bytes.saturating_sub(size);
81 }
82 }
83
84 stats.misses += 1;
85 None
86 }
87
88 #[inline]
92 fn estimate_value_size(value: &Value) -> usize {
93 estimate_json_size(value) as usize
94 }
95
96 pub fn put_file(&self, key: String, value: Value) -> impl Future<Output = ()> + '_ {
98 self.put_file_arc(key, Arc::new(value))
99 }
100
101 pub async fn put_file_arc(&self, key: String, value: Arc<Value>) {
103 let size_bytes = Self::estimate_value_size(&value);
104 let entry = EnhancedCacheEntry::new(value, size_bytes);
105
106 self.file_cache.insert(key, entry);
107
108 let mut stats = self.stats.lock();
109 stats.file_entries = self.file_cache.len();
110 stats.entries = stats.file_entries + stats.directory_entries;
111 stats.file_size_bytes += size_bytes;
112 stats.total_size_bytes = stats.file_size_bytes + stats.directory_size_bytes;
113 }
114
115 pub async fn get_directory(&self, key: &str) -> Option<Value> {
117 self.get_directory_arc(key).await.map(|arc| (*arc).clone())
118 }
119
120 pub async fn get_directory_arc(&self, key: &str) -> Option<Arc<Value>> {
122 let mut stats = self.stats.lock();
123
124 if let Some(entry) = self.directory_cache.get(key) {
125 if entry.timestamp.elapsed() < self.ttl() {
126 stats.hits += 1;
127 return Some(Arc::clone(&entry.data));
128 } else {
129 let size = entry.size_bytes;
130 self.directory_cache.remove(key);
131 stats.expired_evictions += 1;
132 stats.total_size_bytes = stats.total_size_bytes.saturating_sub(size);
133 stats.directory_size_bytes = stats.directory_size_bytes.saturating_sub(size);
134 }
135 }
136
137 stats.misses += 1;
138 None
139 }
140
141 pub fn put_directory(&self, key: String, value: Value) -> impl Future<Output = ()> + '_ {
143 self.put_directory_arc(key, Arc::new(value))
144 }
145
146 pub async fn put_directory_arc(&self, key: String, value: Arc<Value>) {
148 let size_bytes = Self::estimate_value_size(&value);
149 let entry = EnhancedCacheEntry::new(value, size_bytes);
150
151 self.directory_cache.insert(key, entry);
152
153 let mut stats = self.stats.lock();
154 stats.directory_entries = self.directory_cache.len();
155 stats.entries = stats.file_entries + stats.directory_entries;
156 stats.directory_size_bytes += size_bytes;
157 stats.total_size_bytes = stats.file_size_bytes + stats.directory_size_bytes;
158 }
159
160 pub async fn stats(&self) -> EnhancedCacheStats {
162 self.stats.lock().clone()
163 }
164
165 pub async fn clear(&self) {
167 self.file_cache.clear();
168 self.directory_cache.clear();
169 *self.stats.lock() = EnhancedCacheStats::default();
170 }
171
172 pub fn capacity(&self) -> (usize, usize) {
174 (
175 self.file_cache.capacity().try_into().unwrap_or(0),
176 self.directory_cache.capacity().try_into().unwrap_or(0),
177 )
178 }
179
180 pub fn len(&self) -> (usize, usize) {
182 (self.file_cache.len(), self.directory_cache.len())
183 }
184
185 pub async fn check_pressure_and_evict(&self) {
187 let mut stats = self.stats.lock();
188
189 let current_size = stats.total_size_bytes;
190 let max_size = self.max_size_bytes();
191
192 if current_size > max_size {
193 self.directory_cache.clear();
195 stats.directory_entries = 0;
196 stats.directory_size_bytes = 0;
197
198 stats.total_size_bytes = stats.file_size_bytes;
200 stats.entries = stats.file_entries;
201
202 if stats.total_size_bytes as f64 > max_size as f64 * 1.5 {
204 self.file_cache.clear();
205 stats.file_entries = 0;
206 stats.file_size_bytes = 0;
207 stats.total_size_bytes = 0;
208 stats.entries = 0;
209 stats.memory_evictions += 1;
210 } else if stats.total_size_bytes > max_size {
211 self.file_cache.clear();
213 stats.file_entries = 0;
214 stats.file_size_bytes = 0;
215 stats.total_size_bytes = 0;
216 stats.entries = 0;
217 stats.memory_evictions += 1;
218 }
219 } else if current_size as f64 > max_size as f64 * 0.9 {
220 self.directory_cache.clear();
222 stats.directory_entries = 0;
223 stats.directory_size_bytes = 0;
224 stats.total_size_bytes = stats.file_size_bytes;
225 stats.entries = stats.file_entries;
226 }
227 }
228
229 pub fn set_capacity_limit(&self, max_bytes: usize) {
231 self.max_size_bytes.store(max_bytes, Ordering::Relaxed);
232 }
233
234 pub fn apply_read_cache_config(&self, config: &FileReadCacheConfig) {
236 self.max_size_bytes.store(config.max_size_bytes, Ordering::Relaxed);
237 self.ttl_millis.store(config.ttl_secs.saturating_mul(1000), Ordering::Relaxed);
238 }
239}
240
241pub fn configure_file_cache(config: &FileReadCacheConfig) {
243 *FILE_READ_CACHE_CONFIG.write() = config.clone();
244 FILE_CACHE.apply_read_cache_config(config);
245}
246
247pub fn file_read_cache_config() -> FileReadCacheConfig {
249 FILE_READ_CACHE_CONFIG.read().clone()
250}