1use crate::traits::BlockStore;
12use dashmap::DashMap;
13use ipfrs_core::Cid;
14use serde::{Deserialize, Serialize};
15use std::collections::VecDeque;
16use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
17use std::sync::Arc;
18use std::time::{Duration, SystemTime};
19use tokio::sync::Semaphore;
20use tracing::{debug, trace};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24pub enum AccessPattern {
25 Sequential,
27 Random,
29 Clustered,
31 Temporal,
33}
34
35#[derive(Debug, Clone)]
37struct AccessRecord {
38 #[allow(dead_code)]
40 timestamp: SystemTime,
41 #[allow(dead_code)]
43 previous_cid: Option<Cid>,
44 next_cid: Option<Cid>,
46}
47
48#[derive(Debug, Clone)]
50struct CoLocationPattern {
51 count: u64,
53 last_seen: SystemTime,
55 confidence: f64,
57}
58
59#[derive(Debug, Clone)]
61pub struct PrefetchPrediction {
62 pub cid: Cid,
64 pub confidence: f64,
66 pub predicted_access: SystemTime,
68 pub pattern: AccessPattern,
70}
71
72#[derive(Debug, Clone)]
74pub struct PrefetchConfig {
75 pub max_prefetch_depth: usize,
77 pub min_confidence: f64,
79 pub max_concurrent_prefetch: usize,
81 pub pattern_window: Duration,
83 pub enable_sequential: bool,
85 pub enable_colocation: bool,
87 pub enable_temporal: bool,
89}
90
91impl Default for PrefetchConfig {
92 fn default() -> Self {
93 Self {
94 max_prefetch_depth: 5,
95 min_confidence: 0.6,
96 max_concurrent_prefetch: 3,
97 pattern_window: Duration::from_secs(300), enable_sequential: true,
99 enable_colocation: true,
100 enable_temporal: true,
101 }
102 }
103}
104
105#[derive(Debug, Default)]
107pub struct PrefetchStats {
108 pub prefetch_attempts: AtomicU64,
110 pub prefetch_hits: AtomicU64,
112 pub prefetch_misses: AtomicU64,
114 pub bytes_prefetched: AtomicU64,
116 pub avg_confidence: parking_lot::Mutex<f64>,
118}
119
120impl PrefetchStats {
121 fn record_attempt(&self) {
122 self.prefetch_attempts.fetch_add(1, Ordering::Relaxed);
123 }
124
125 fn record_hit(&self, bytes: u64) {
126 self.prefetch_hits.fetch_add(1, Ordering::Relaxed);
127 self.bytes_prefetched.fetch_add(bytes, Ordering::Relaxed);
128 }
129
130 fn record_miss(&self) {
131 self.prefetch_misses.fetch_add(1, Ordering::Relaxed);
132 }
133
134 pub fn hit_rate(&self) -> f64 {
136 let hits = self.prefetch_hits.load(Ordering::Relaxed) as f64;
137 let total = self.prefetch_attempts.load(Ordering::Relaxed) as f64;
138 if total > 0.0 {
139 hits / total
140 } else {
141 0.0
142 }
143 }
144}
145
146pub struct PredictivePrefetcher<S: BlockStore> {
148 store: Arc<S>,
149 config: parking_lot::RwLock<PrefetchConfig>,
150 access_history: DashMap<Cid, VecDeque<AccessRecord>>,
152 colocation_patterns: DashMap<Cid, DashMap<Cid, CoLocationPattern>>,
154 last_accessed: parking_lot::Mutex<Option<Cid>>,
156 #[allow(dead_code)]
158 prefetch_queue: DashMap<Cid, PrefetchPrediction>,
159 prefetch_cache: DashMap<Cid, (Vec<u8>, SystemTime)>,
161 stats: PrefetchStats,
163 prefetch_semaphore: Arc<Semaphore>,
165 current_depth: AtomicUsize,
167}
168
169impl<S: BlockStore + Send + Sync + 'static> PredictivePrefetcher<S> {
170 pub fn new(store: Arc<S>, config: PrefetchConfig) -> Self {
172 let max_concurrent = config.max_concurrent_prefetch;
173 let initial_depth = config.max_prefetch_depth;
174
175 Self {
176 store,
177 config: parking_lot::RwLock::new(config),
178 access_history: DashMap::new(),
179 colocation_patterns: DashMap::new(),
180 last_accessed: parking_lot::Mutex::new(None),
181 prefetch_queue: DashMap::new(),
182 prefetch_cache: DashMap::new(),
183 stats: PrefetchStats::default(),
184 prefetch_semaphore: Arc::new(Semaphore::new(max_concurrent)),
185 current_depth: AtomicUsize::new(initial_depth),
186 }
187 }
188
189 pub fn record_access(&self, cid: &Cid) {
191 let now = SystemTime::now();
192 let previous = *self.last_accessed.lock();
193
194 {
196 let mut history = self.access_history.entry(*cid).or_default();
197 history.push_back(AccessRecord {
198 timestamp: now,
199 previous_cid: previous,
200 next_cid: None,
201 });
202
203 if history.len() > 100 {
205 history.pop_front();
206 }
207 } if let Some(prev_cid) = previous {
211 if prev_cid != *cid {
213 if let Some(mut prev_history) = self.access_history.get_mut(&prev_cid) {
214 if let Some(last_record) = prev_history.back_mut() {
215 last_record.next_cid = Some(*cid);
216 }
217 }
218 }
219
220 if self.config.read().enable_colocation {
222 self.update_colocation_pattern(&prev_cid, cid);
223 }
224 }
225
226 *self.last_accessed.lock() = Some(*cid);
228
229 if let Some(entry) = self.prefetch_cache.get(cid) {
231 let prefetch_time = entry.value().1;
232 let age = now.duration_since(prefetch_time).unwrap_or_default();
233 if age < Duration::from_secs(60) {
234 self.stats.record_hit(0); } else {
237 self.stats.record_miss();
238 }
239 }
240 }
241
242 fn update_colocation_pattern(&self, cid1: &Cid, cid2: &Cid) {
244 let patterns = self.colocation_patterns.entry(*cid1).or_default();
245
246 patterns
247 .entry(*cid2)
248 .and_modify(|pattern| {
249 pattern.count += 1;
250 pattern.last_seen = SystemTime::now();
251 let recency_factor = 0.9; pattern.confidence = (pattern.confidence * recency_factor + 0.1).min(1.0);
254 })
255 .or_insert_with(|| CoLocationPattern {
256 count: 1,
257 last_seen: SystemTime::now(),
258 confidence: 0.5,
259 });
260 }
261
262 pub fn predict_next_blocks(&self, current_cid: &Cid) -> Vec<PrefetchPrediction> {
264 let config = self.config.read();
265 let mut predictions = Vec::new();
266
267 if config.enable_sequential {
269 if let Some(seq_predictions) = self.predict_sequential(current_cid) {
270 predictions.extend(seq_predictions);
271 }
272 }
273
274 if config.enable_colocation {
276 if let Some(coloc_predictions) = self.predict_colocation(current_cid) {
277 predictions.extend(coloc_predictions);
278 }
279 }
280
281 predictions.retain(|p| p.confidence >= config.min_confidence);
283 predictions.sort_by(|a, b| {
284 b.confidence
285 .partial_cmp(&a.confidence)
286 .unwrap_or(std::cmp::Ordering::Equal)
287 });
288
289 let depth = self.current_depth.load(Ordering::Relaxed);
290 predictions.truncate(depth);
291
292 predictions
293 }
294
295 fn predict_sequential(&self, cid: &Cid) -> Option<Vec<PrefetchPrediction>> {
297 let history = self.access_history.get(cid)?;
298
299 let next_counts: DashMap<Cid, u64> = DashMap::new();
301
302 for record in history.iter() {
303 if let Some(next_cid) = record.next_cid {
304 *next_counts.entry(next_cid).or_insert(0) += 1;
305 }
306 }
307
308 if next_counts.is_empty() {
309 return None;
310 }
311
312 let mut predictions = Vec::new();
314 let total_accesses = history.len() as f64;
315
316 for entry in next_counts.iter() {
317 let count = *entry.value() as f64;
318 let confidence = count / total_accesses;
319
320 if confidence >= 0.3 {
321 predictions.push(PrefetchPrediction {
322 cid: *entry.key(),
323 confidence,
324 predicted_access: SystemTime::now(),
325 pattern: AccessPattern::Sequential,
326 });
327 }
328 }
329
330 Some(predictions)
331 }
332
333 fn predict_colocation(&self, cid: &Cid) -> Option<Vec<PrefetchPrediction>> {
335 let patterns = self.colocation_patterns.get(cid)?;
336
337 let mut predictions = Vec::new();
338
339 for entry in patterns.iter() {
340 let pattern = entry.value();
341
342 let age = SystemTime::now()
344 .duration_since(pattern.last_seen)
345 .unwrap_or_default();
346
347 if age < self.config.read().pattern_window {
348 predictions.push(PrefetchPrediction {
349 cid: *entry.key(),
350 confidence: pattern.confidence,
351 predicted_access: SystemTime::now(),
352 pattern: AccessPattern::Clustered,
353 });
354 }
355 }
356
357 Some(predictions)
358 }
359
360 pub async fn prefetch_background(&self, predictions: Vec<PrefetchPrediction>) {
362 for prediction in predictions {
363 let store = self.store.clone();
364 let cache = self.prefetch_cache.clone();
365 let stats = &self.stats;
366 let semaphore = self.prefetch_semaphore.clone();
367
368 stats.record_attempt();
369
370 let cid = prediction.cid;
371 trace!(
372 "Prefetching block {} (confidence: {:.2})",
373 cid,
374 prediction.confidence
375 );
376
377 tokio::spawn(async move {
379 let _permit = semaphore.acquire().await.ok();
380
381 if let Ok(Some(block)) = store.get(&cid).await {
382 cache.insert(cid, (block.data().to_vec(), SystemTime::now()));
383 debug!("Prefetched block {}", cid);
384 }
385 });
386 }
387 }
388
389 pub fn adapt_depth(&self) {
391 let hit_rate = self.stats.hit_rate();
392 let current = self.current_depth.load(Ordering::Relaxed);
393 let max_depth = self.config.read().max_prefetch_depth;
394
395 let new_depth = if hit_rate > 0.8 {
396 (current + 1).min(max_depth)
398 } else if hit_rate < 0.4 {
399 (current.saturating_sub(1)).max(1)
401 } else {
402 current
403 };
404
405 if new_depth != current {
406 self.current_depth.store(new_depth, Ordering::Relaxed);
407 debug!(
408 "Adapted prefetch depth: {} -> {} (hit rate: {:.2})",
409 current, new_depth, hit_rate
410 );
411 }
412 }
413
414 pub fn stats(&self) -> PrefetchStatsSnapshot {
416 PrefetchStatsSnapshot {
417 prefetch_attempts: self.stats.prefetch_attempts.load(Ordering::Relaxed),
418 prefetch_hits: self.stats.prefetch_hits.load(Ordering::Relaxed),
419 prefetch_misses: self.stats.prefetch_misses.load(Ordering::Relaxed),
420 bytes_prefetched: self.stats.bytes_prefetched.load(Ordering::Relaxed),
421 hit_rate: self.stats.hit_rate(),
422 current_depth: self.current_depth.load(Ordering::Relaxed),
423 }
424 }
425
426 pub fn clear_cache(&self) {
428 self.prefetch_cache.clear();
429 }
430
431 pub fn cache_size(&self) -> usize {
433 self.prefetch_cache.len()
434 }
435}
436
437#[derive(Debug, Clone, Serialize, Deserialize)]
439pub struct PrefetchStatsSnapshot {
440 pub prefetch_attempts: u64,
441 pub prefetch_hits: u64,
442 pub prefetch_misses: u64,
443 pub bytes_prefetched: u64,
444 pub hit_rate: f64,
445 pub current_depth: usize,
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451 use crate::memory::MemoryBlockStore;
452 use ipfrs_core::cid::CidBuilder;
453
454 fn test_cid(index: u64) -> Cid {
456 CidBuilder::new()
457 .build(&index.to_le_bytes())
458 .expect("failed to create test cid")
459 }
460
461 #[tokio::test]
462 async fn test_prefetcher_creation() {
463 let store = Arc::new(MemoryBlockStore::new());
464 let config = PrefetchConfig::default();
465 let prefetcher = PredictivePrefetcher::new(store, config);
466
467 let stats = prefetcher.stats();
468 assert_eq!(stats.prefetch_attempts, 0);
469 assert_eq!(stats.hit_rate, 0.0);
470 }
471
472 #[tokio::test]
473 async fn test_access_recording() {
474 let store = Arc::new(MemoryBlockStore::new());
475 let prefetcher = PredictivePrefetcher::new(store, PrefetchConfig::default());
476
477 let cid1 = test_cid(1);
478 let cid2 = test_cid(2);
479
480 prefetcher.record_access(&cid1);
481 prefetcher.record_access(&cid2);
482
483 assert!(prefetcher.colocation_patterns.contains_key(&cid1));
485 }
486
487 #[tokio::test]
488 async fn test_sequential_prediction() {
489 let store = Arc::new(MemoryBlockStore::new());
490 let prefetcher = PredictivePrefetcher::new(store, PrefetchConfig::default());
491
492 let cid1 = test_cid(1);
493 let cid2 = test_cid(2);
494
495 for _ in 0..5 {
497 prefetcher.record_access(&cid1);
498 prefetcher.record_access(&cid2);
499 }
500
501 let predictions = prefetcher.predict_next_blocks(&cid1);
502 assert!(!predictions.is_empty());
503
504 assert!(predictions
506 .iter()
507 .any(|p| p.pattern == AccessPattern::Sequential));
508 }
509}