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;
11use tokio::sync::Mutex;
12
13use parking_lot::RwLock;
14
15use crate::cache::estimate_json_size;
16use vtcode_config::FileReadCacheConfig;
17
18pub static FILE_CACHE: Lazy<FileCache> = Lazy::new(|| FileCache::new(1000));
20
21static FILE_READ_CACHE_CONFIG: Lazy<RwLock<FileReadCacheConfig>> =
22 Lazy::new(|| RwLock::new(FileReadCacheConfig::default()));
23
24pub struct FileCache {
30 file_cache: Arc<Cache<String, EnhancedCacheEntry<Arc<Value>>>>,
31 directory_cache: Arc<Cache<String, EnhancedCacheEntry<Arc<Value>>>>,
32 stats: Arc<Mutex<EnhancedCacheStats>>,
33 max_size_bytes: AtomicUsize,
34 ttl_millis: AtomicU64,
35}
36
37impl FileCache {
38 pub fn new(capacity: usize) -> Self {
39 Self {
40 file_cache: Arc::new(Cache::new(capacity)),
41 directory_cache: Arc::new(Cache::new(capacity / 2)),
42 stats: Arc::new(Mutex::new(EnhancedCacheStats::default())),
43 max_size_bytes: AtomicUsize::new(50 * 1024 * 1024), ttl_millis: AtomicU64::new(300_000), }
46 }
47
48 #[inline]
49 fn ttl(&self) -> Duration {
50 Duration::from_millis(self.ttl_millis.load(Ordering::Relaxed))
51 }
52
53 #[inline]
54 fn max_size_bytes(&self) -> usize {
55 self.max_size_bytes.load(Ordering::Relaxed)
56 }
57
58 pub async fn get_file(&self, key: &str) -> Option<Value> {
60 self.get_file_arc(key).await.map(|arc| (*arc).clone())
61 }
62
63 pub async fn get_file_arc(&self, key: &str) -> Option<Arc<Value>> {
65 let mut stats = self.stats.lock().await;
66
67 if let Some(entry) = self.file_cache.get(key) {
68 if entry.timestamp.elapsed() < self.ttl() {
70 stats.hits += 1;
72 return Some(Arc::clone(&entry.data));
73 } else {
74 self.file_cache.remove(key);
76 stats.expired_evictions += 1;
77 }
78 }
79
80 stats.misses += 1;
81 None
82 }
83
84 #[inline]
88 fn estimate_value_size(value: &Value) -> usize {
89 estimate_json_size(value) as usize
90 }
91
92 pub fn put_file(&self, key: String, value: Value) -> impl Future<Output = ()> + '_ {
94 self.put_file_arc(key, Arc::new(value))
95 }
96
97 pub async fn put_file_arc(&self, key: String, value: Arc<Value>) {
99 let size_bytes = Self::estimate_value_size(&value);
100 let entry = EnhancedCacheEntry::new(value, size_bytes);
101
102 let mut stats = self.stats.lock().await;
103
104 if stats.total_size_bytes + size_bytes > self.max_size_bytes() {
106 stats.memory_evictions += 1;
107 }
108
109 self.file_cache.insert(key, entry);
110 stats.entries = self.file_cache.len();
111 stats.total_size_bytes += size_bytes;
112 }
113
114 pub async fn get_directory(&self, key: &str) -> Option<Value> {
116 self.get_directory_arc(key).await.map(|arc| (*arc).clone())
117 }
118
119 pub async fn get_directory_arc(&self, key: &str) -> Option<Arc<Value>> {
121 let mut stats = self.stats.lock().await;
122
123 if let Some(entry) = self.directory_cache.get(key) {
124 if entry.timestamp.elapsed() < self.ttl() {
125 stats.hits += 1;
126 return Some(Arc::clone(&entry.data));
127 } else {
128 self.directory_cache.remove(key);
129 stats.expired_evictions += 1;
130 }
131 }
132
133 stats.misses += 1;
134 None
135 }
136
137 pub fn put_directory(&self, key: String, value: Value) -> impl Future<Output = ()> + '_ {
139 self.put_directory_arc(key, Arc::new(value))
140 }
141
142 pub async fn put_directory_arc(&self, key: String, value: Arc<Value>) {
144 let size_bytes = Self::estimate_value_size(&value);
145 let entry = EnhancedCacheEntry::new(value, size_bytes);
146
147 let mut stats = self.stats.lock().await;
148
149 self.directory_cache.insert(key, entry);
150 stats.entries += self.directory_cache.len();
151 stats.total_size_bytes += size_bytes;
152 }
153
154 pub async fn stats(&self) -> EnhancedCacheStats {
156 self.stats.lock().await.clone()
157 }
158
159 pub async fn clear(&self) {
161 self.file_cache.clear();
162 self.directory_cache.clear();
163 *self.stats.lock().await = EnhancedCacheStats::default();
164 }
165
166 pub fn capacity(&self) -> (usize, usize) {
168 (
169 self.file_cache.capacity().try_into().unwrap_or(0),
170 self.directory_cache.capacity().try_into().unwrap_or(0),
171 )
172 }
173
174 pub fn len(&self) -> (usize, usize) {
176 (self.file_cache.len(), self.directory_cache.len())
177 }
178
179 pub async fn check_pressure_and_evict(&self) {
181 let mut stats = self.stats.lock().await;
182
183 let current_size = stats.total_size_bytes;
184 let max_size = self.max_size_bytes();
185
186 if current_size > max_size {
187 self.directory_cache.clear();
189
190 if current_size as f64 > max_size as f64 * 1.5 {
198 self.file_cache.clear();
199 stats.total_size_bytes = 0;
200 stats.entries = 0;
201 stats.memory_evictions += 1;
202 return;
203 }
204
205 self.file_cache.clear(); stats.total_size_bytes = 0;
231 stats.entries = 0;
232 stats.memory_evictions += 1;
233 } else if current_size as f64 > max_size as f64 * 0.9 {
234 }
237 }
238
239 pub fn set_capacity_limit(&mut self, max_bytes: usize) {
241 self.max_size_bytes.store(max_bytes, Ordering::Relaxed);
242 }
243
244 pub fn apply_read_cache_config(&self, config: &FileReadCacheConfig) {
246 self.max_size_bytes
247 .store(config.max_size_bytes, Ordering::Relaxed);
248 self.ttl_millis
249 .store(config.ttl_secs.saturating_mul(1000), Ordering::Relaxed);
250 }
251
252 pub fn adjust_capacity(&self, target_memory_ratio: f64) {
255 const ASSUMED_SYSTEM_MEMORY: usize = 16 * 1024 * 1024 * 1024;
257
258 let target_bytes = (ASSUMED_SYSTEM_MEMORY as f64 * target_memory_ratio) as usize;
259 self.max_size_bytes.store(target_bytes, Ordering::Relaxed);
260 }
261}
262
263pub fn configure_file_cache(config: &FileReadCacheConfig) {
265 *FILE_READ_CACHE_CONFIG.write() = config.clone();
266 FILE_CACHE.apply_read_cache_config(config);
267}
268
269pub fn file_read_cache_config() -> FileReadCacheConfig {
270 FILE_READ_CACHE_CONFIG.read().clone()
271}