Skip to main content

oxigdal_cloud/
prefetch.rs

1//! Intelligent prefetching algorithms for cloud storage
2//!
3//! This module provides comprehensive prefetching capabilities including:
4//!
5//! - **Predictive prefetching**: Pattern-based prediction of future accesses
6//! - **Spatial locality**: Prefetch adjacent tiles and regions
7//! - **Temporal patterns**: Detect time-based access patterns
8//! - **Adaptive buffer sizing**: Dynamic buffer allocation based on workload
9//! - **Priority-based prefetching**: Prioritize high-value prefetch targets
10//! - **Memory-aware prefetching**: Respect memory constraints
11//! - **Bandwidth-aware prefetching**: Throttle based on available bandwidth
12
13use std::collections::{BTreeMap, HashMap, VecDeque};
14use std::sync::Arc;
15use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
16use std::time::{Duration, Instant};
17
18#[cfg(feature = "async")]
19use tokio::sync::RwLock;
20
21use crate::error::Result;
22
23// ============================================================================
24// Access Pattern Types and Detection
25// ============================================================================
26
27/// Access pattern type for predictive prefetching
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
29pub enum AccessPattern {
30    /// Sequential forward access (file_0, file_1, file_2, ...)
31    SequentialForward,
32    /// Sequential backward access (file_10, file_9, file_8, ...)
33    SequentialBackward,
34    /// Strided access (file_0, file_2, file_4, ...)
35    Strided { stride: i64 },
36    /// Random access pattern
37    Random,
38    /// Spatial access pattern (for tiles in 2D/3D)
39    Spatial,
40    /// Temporal periodic access (repeated access at intervals)
41    TemporalPeriodic { period_ms: u64 },
42    /// Burst access (many requests in short time)
43    Burst,
44    /// Unknown pattern (insufficient data)
45    #[default]
46    Unknown,
47}
48
49/// Priority level for prefetch operations
50#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
51pub enum PrefetchPriority {
52    /// Critical prefetch (user is likely waiting)
53    Critical = 4,
54    /// High priority (very likely to be accessed soon)
55    High = 3,
56    /// Medium priority (likely to be accessed)
57    #[default]
58    Medium = 2,
59    /// Low priority (might be accessed)
60    Low = 1,
61    /// Background prefetch (speculative)
62    Background = 0,
63}
64
65// ============================================================================
66// Access Records and History
67// ============================================================================
68
69/// Access record for pattern analysis
70#[derive(Debug, Clone)]
71pub struct AccessRecord {
72    /// Accessed key/path
73    pub key: String,
74    /// Access timestamp
75    pub timestamp: Instant,
76    /// Access coordinates for spatial data (x, y, z/level)
77    pub coordinates: Option<(usize, usize, usize)>,
78    /// Size of accessed data in bytes
79    pub size: Option<usize>,
80    /// Access latency (time to fetch)
81    pub latency: Option<Duration>,
82    /// Whether this was a cache hit
83    pub cache_hit: bool,
84}
85
86impl AccessRecord {
87    /// Creates a new access record
88    #[must_use]
89    pub fn new(key: String) -> Self {
90        Self {
91            key,
92            timestamp: Instant::now(),
93            coordinates: None,
94            size: None,
95            latency: None,
96            cache_hit: false,
97        }
98    }
99
100    /// Creates an access record with coordinates
101    #[must_use]
102    pub fn with_coordinates(key: String, x: usize, y: usize, z: usize) -> Self {
103        Self {
104            key,
105            timestamp: Instant::now(),
106            coordinates: Some((x, y, z)),
107            size: None,
108            latency: None,
109            cache_hit: false,
110        }
111    }
112
113    /// Sets the access size
114    #[must_use]
115    pub fn with_size(mut self, size: usize) -> Self {
116        self.size = Some(size);
117        self
118    }
119
120    /// Sets the access latency
121    #[must_use]
122    pub fn with_latency(mut self, latency: Duration) -> Self {
123        self.latency = Some(latency);
124        self
125    }
126
127    /// Sets whether this was a cache hit
128    #[must_use]
129    pub fn with_cache_hit(mut self, hit: bool) -> Self {
130        self.cache_hit = hit;
131        self
132    }
133}
134
135/// Prefetch target with priority and metadata
136#[derive(Debug, Clone)]
137pub struct PrefetchTarget {
138    /// Key to prefetch
139    pub key: String,
140    /// Priority level
141    pub priority: PrefetchPriority,
142    /// Expected coordinates (for spatial data)
143    pub coordinates: Option<(usize, usize, usize)>,
144    /// Confidence score (0.0 - 1.0)
145    pub confidence: f64,
146    /// Estimated size in bytes
147    pub estimated_size: Option<usize>,
148}
149
150impl PrefetchTarget {
151    /// Creates a new prefetch target
152    #[must_use]
153    pub fn new(key: String, priority: PrefetchPriority, confidence: f64) -> Self {
154        Self {
155            key,
156            priority,
157            coordinates: None,
158            confidence,
159            estimated_size: None,
160        }
161    }
162
163    /// Sets coordinates for spatial data
164    #[must_use]
165    pub fn with_coordinates(mut self, x: usize, y: usize, z: usize) -> Self {
166        self.coordinates = Some((x, y, z));
167        self
168    }
169
170    /// Sets estimated size
171    #[must_use]
172    pub fn with_estimated_size(mut self, size: usize) -> Self {
173        self.estimated_size = Some(size);
174        self
175    }
176}
177
178// ============================================================================
179// Temporal Pattern Detection
180// ============================================================================
181
182/// Temporal pattern analyzer for detecting time-based access patterns
183#[derive(Debug)]
184pub struct TemporalPatternAnalyzer {
185    /// Access intervals in milliseconds
186    intervals: VecDeque<u64>,
187    /// Maximum number of intervals to track
188    max_intervals: usize,
189    /// Detected periodic interval (if any)
190    detected_period: Option<u64>,
191    /// Burst detection threshold (accesses per second)
192    burst_threshold: f64,
193}
194
195impl TemporalPatternAnalyzer {
196    /// Creates a new temporal pattern analyzer
197    #[must_use]
198    pub fn new(max_intervals: usize) -> Self {
199        Self {
200            intervals: VecDeque::with_capacity(max_intervals),
201            max_intervals,
202            detected_period: None,
203            burst_threshold: 10.0, // 10 accesses per second
204        }
205    }
206
207    /// Records a new access interval
208    pub fn record_interval(&mut self, interval_ms: u64) {
209        if self.intervals.len() >= self.max_intervals {
210            self.intervals.pop_front();
211        }
212        self.intervals.push_back(interval_ms);
213        self.analyze_periodicity();
214    }
215
216    /// Analyzes intervals for periodic patterns
217    fn analyze_periodicity(&mut self) {
218        if self.intervals.len() < 5 {
219            self.detected_period = None;
220            return;
221        }
222
223        // Calculate mean and standard deviation
224        let sum: u64 = self.intervals.iter().sum();
225        let mean = sum as f64 / self.intervals.len() as f64;
226
227        let variance: f64 = self
228            .intervals
229            .iter()
230            .map(|&x| {
231                let diff = x as f64 - mean;
232                diff * diff
233            })
234            .sum::<f64>()
235            / self.intervals.len() as f64;
236
237        let std_dev = variance.sqrt();
238
239        // If coefficient of variation is low, we have periodic access
240        let cv = std_dev / mean;
241        if cv < 0.3 && mean > 100.0 {
242            // CV < 30% indicates regularity
243            self.detected_period = Some(mean as u64);
244        } else {
245            self.detected_period = None;
246        }
247    }
248
249    /// Detects if current access pattern is a burst
250    #[must_use]
251    pub fn is_burst(&self) -> bool {
252        if self.intervals.len() < 3 {
253            return false;
254        }
255
256        // Check recent intervals (last 5)
257        let recent: Vec<_> = self.intervals.iter().rev().take(5).copied().collect();
258        if recent.is_empty() {
259            return false;
260        }
261
262        let avg_interval = recent.iter().sum::<u64>() as f64 / recent.len() as f64;
263
264        // If average interval is less than 1000ms / threshold, it's a burst
265        avg_interval < (1000.0 / self.burst_threshold)
266    }
267
268    /// Returns the detected periodic interval
269    #[must_use]
270    pub fn detected_period(&self) -> Option<u64> {
271        self.detected_period
272    }
273
274    /// Predicts next access time based on periodicity
275    #[must_use]
276    pub fn predict_next_access(&self, last_access: Instant) -> Option<Instant> {
277        self.detected_period
278            .map(|period| last_access + Duration::from_millis(period))
279    }
280}
281
282// ============================================================================
283// Spatial Locality Analyzer
284// ============================================================================
285
286/// Spatial locality analyzer for tile-based data
287#[derive(Debug)]
288pub struct SpatialLocalityAnalyzer {
289    /// Recent coordinate accesses
290    coordinates: VecDeque<(usize, usize, usize)>,
291    /// Maximum history size
292    max_history: usize,
293    /// Movement direction tracking (dx, dy)
294    direction: Option<(i64, i64)>,
295    /// Zoom level tracking
296    zoom_direction: Option<i64>,
297}
298
299impl SpatialLocalityAnalyzer {
300    /// Creates a new spatial locality analyzer
301    #[must_use]
302    pub fn new(max_history: usize) -> Self {
303        Self {
304            coordinates: VecDeque::with_capacity(max_history),
305            max_history,
306            direction: None,
307            zoom_direction: None,
308        }
309    }
310
311    /// Records a coordinate access
312    pub fn record_access(&mut self, x: usize, y: usize, z: usize) {
313        // Calculate movement direction from previous access
314        if let Some(&(prev_x, prev_y, prev_z)) = self.coordinates.back() {
315            let dx = x as i64 - prev_x as i64;
316            let dy = y as i64 - prev_y as i64;
317            let dz = z as i64 - prev_z as i64;
318
319            // Update direction with exponential moving average
320            if let Some((curr_dx, curr_dy)) = self.direction {
321                self.direction = Some((
322                    (curr_dx + dx) / 2, // Simple average
323                    (curr_dy + dy) / 2,
324                ));
325            } else {
326                self.direction = Some((dx, dy));
327            }
328
329            self.zoom_direction = if dz != 0 { Some(dz) } else { None };
330        }
331
332        if self.coordinates.len() >= self.max_history {
333            self.coordinates.pop_front();
334        }
335        self.coordinates.push_back((x, y, z));
336    }
337
338    /// Predicts adjacent tiles to prefetch
339    #[must_use]
340    pub fn predict_adjacent(&self, count: usize) -> Vec<(usize, usize, usize)> {
341        let Some(&(x, y, z)) = self.coordinates.back() else {
342            return Vec::new();
343        };
344
345        let mut predictions = Vec::with_capacity(count);
346
347        // Add tiles in direction of movement first
348        if let Some((dx, dy)) = self.direction {
349            // Normalize direction
350            let norm_dx = dx.signum();
351            let norm_dy = dy.signum();
352
353            // Primary direction tiles
354            if norm_dx != 0 {
355                let new_x = if norm_dx > 0 {
356                    x + 1
357                } else {
358                    x.saturating_sub(1)
359                };
360                predictions.push((new_x, y, z));
361            }
362            if norm_dy != 0 {
363                let new_y = if norm_dy > 0 {
364                    y + 1
365                } else {
366                    y.saturating_sub(1)
367                };
368                predictions.push((x, new_y, z));
369            }
370            // Diagonal in direction
371            if norm_dx != 0 && norm_dy != 0 {
372                let new_x = if norm_dx > 0 {
373                    x + 1
374                } else {
375                    x.saturating_sub(1)
376                };
377                let new_y = if norm_dy > 0 {
378                    y + 1
379                } else {
380                    y.saturating_sub(1)
381                };
382                predictions.push((new_x, new_y, z));
383            }
384        }
385
386        // Add remaining adjacent tiles in spiral order
387        let spiral_offsets: [(i64, i64); 8] = [
388            (1, 0),
389            (0, 1),
390            (-1, 0),
391            (0, -1),
392            (1, 1),
393            (-1, 1),
394            (-1, -1),
395            (1, -1),
396        ];
397
398        for (ox, oy) in spiral_offsets {
399            if predictions.len() >= count {
400                break;
401            }
402            let new_x = (x as i64 + ox).max(0) as usize;
403            let new_y = (y as i64 + oy).max(0) as usize;
404            let coord = (new_x, new_y, z);
405            if !predictions.contains(&coord) {
406                predictions.push(coord);
407            }
408        }
409
410        // Add zoom level transitions if detected
411        if let Some(dz) = self.zoom_direction {
412            let new_z = (z as i64 + dz).max(0) as usize;
413            if predictions.len() < count {
414                predictions.push((x, y, new_z));
415            }
416        }
417
418        predictions.truncate(count);
419        predictions
420    }
421
422    /// Gets the current movement direction
423    #[must_use]
424    pub fn movement_direction(&self) -> Option<(i64, i64)> {
425        self.direction
426    }
427}
428
429// ============================================================================
430// Pattern Analyzer
431// ============================================================================
432
433/// Comprehensive pattern analyzer for predictive prefetching
434pub struct PatternAnalyzer {
435    /// Recent access history
436    history: VecDeque<AccessRecord>,
437    /// Maximum history size
438    max_history: usize,
439    /// Current detected pattern
440    current_pattern: AccessPattern,
441    /// Temporal pattern analyzer
442    temporal: TemporalPatternAnalyzer,
443    /// Spatial locality analyzer
444    spatial: SpatialLocalityAnalyzer,
445    /// Key frequency map for hot spot detection
446    key_frequency: HashMap<String, u64>,
447    /// Stride detector
448    detected_stride: Option<i64>,
449}
450
451impl PatternAnalyzer {
452    /// Creates a new pattern analyzer
453    #[must_use]
454    pub fn new(max_history: usize) -> Self {
455        Self {
456            history: VecDeque::with_capacity(max_history),
457            max_history,
458            current_pattern: AccessPattern::Unknown,
459            temporal: TemporalPatternAnalyzer::new(50),
460            spatial: SpatialLocalityAnalyzer::new(20),
461            key_frequency: HashMap::new(),
462            detected_stride: None,
463        }
464    }
465
466    /// Records an access and updates pattern analysis
467    pub fn record_access(&mut self, record: AccessRecord) {
468        // Update temporal patterns
469        if let Some(last) = self.history.back() {
470            let interval = record.timestamp.duration_since(last.timestamp);
471            self.temporal.record_interval(interval.as_millis() as u64);
472        }
473
474        // Update spatial patterns
475        if let Some((x, y, z)) = record.coordinates {
476            self.spatial.record_access(x, y, z);
477        }
478
479        // Update frequency map
480        *self.key_frequency.entry(record.key.clone()).or_insert(0) += 1;
481
482        // Add to history
483        if self.history.len() >= self.max_history {
484            self.history.pop_front();
485        }
486        self.history.push_back(record);
487
488        // Analyze patterns
489        self.analyze_patterns();
490    }
491
492    /// Analyzes all patterns to determine current access pattern
493    fn analyze_patterns(&mut self) {
494        if self.history.len() < 3 {
495            self.current_pattern = AccessPattern::Unknown;
496            return;
497        }
498
499        // Check for structural patterns first (these take precedence over temporal patterns)
500
501        // Check for spatial pattern
502        if self.is_spatial() {
503            self.current_pattern = AccessPattern::Spatial;
504            return;
505        }
506
507        // Check for strided pattern
508        if let Some(stride) = self.detect_stride() {
509            self.detected_stride = Some(stride);
510            self.current_pattern = AccessPattern::Strided { stride };
511            return;
512        }
513
514        // Check for sequential patterns
515        if self.is_sequential_forward() {
516            self.current_pattern = AccessPattern::SequentialForward;
517            return;
518        }
519
520        if self.is_sequential_backward() {
521            self.current_pattern = AccessPattern::SequentialBackward;
522            return;
523        }
524
525        // Check for temporal patterns (only if no structural pattern is found)
526
527        // Check for temporal periodic pattern
528        if let Some(period) = self.temporal.detected_period() {
529            self.current_pattern = AccessPattern::TemporalPeriodic { period_ms: period };
530            return;
531        }
532
533        // Check for burst pattern (only if no other pattern is detected)
534        // Burst should be a fallback when timing is rapid but there's no other pattern
535        if self.temporal.is_burst() {
536            self.current_pattern = AccessPattern::Burst;
537            return;
538        }
539
540        self.current_pattern = AccessPattern::Random;
541    }
542
543    /// Checks for forward sequential pattern
544    fn is_sequential_forward(&self) -> bool {
545        let recent: Vec<_> = self.history.iter().rev().take(5).collect();
546        if recent.len() < 3 {
547            return false;
548        }
549
550        let mut sequential_count = 0;
551        for window in recent.windows(2) {
552            if let (Some(&a), Some(&b)) = (window.first(), window.get(1))
553                && let (Some(na), Some(nb)) = (
554                    extract_trailing_number(&a.key),
555                    extract_trailing_number(&b.key),
556                )
557            {
558                // recent is reversed, so for forward pattern: na - 1 == nb
559                // (e.g., file_3 -> file_2 -> file_1 is forward in original order)
560                if na == nb + 1 {
561                    sequential_count += 1;
562                }
563            }
564        }
565
566        sequential_count >= (recent.len() - 1) / 2
567    }
568
569    /// Checks for backward sequential pattern
570    fn is_sequential_backward(&self) -> bool {
571        let recent: Vec<_> = self.history.iter().rev().take(5).collect();
572        if recent.len() < 3 {
573            return false;
574        }
575
576        let mut backward_count = 0;
577        for window in recent.windows(2) {
578            if let (Some(&a), Some(&b)) = (window.first(), window.get(1))
579                && let (Some(na), Some(nb)) = (
580                    extract_trailing_number(&a.key),
581                    extract_trailing_number(&b.key),
582                )
583            {
584                // recent is reversed, so for backward pattern: na + 1 == nb
585                // (e.g., file_3 -> file_4 -> file_5 is backward in original order)
586                if nb == na + 1 {
587                    backward_count += 1;
588                }
589            }
590        }
591
592        backward_count >= (recent.len() - 1) / 2
593    }
594
595    /// Detects strided access pattern
596    fn detect_stride(&self) -> Option<i64> {
597        let recent: Vec<_> = self.history.iter().rev().take(6).collect();
598        if recent.len() < 4 {
599            return None;
600        }
601
602        // Extract numbers from keys
603        let numbers: Vec<i64> = recent
604            .iter()
605            .filter_map(|r| extract_trailing_number(&r.key))
606            .collect();
607
608        if numbers.len() < 4 {
609            return None;
610        }
611
612        // Calculate differences
613        let diffs: Vec<i64> = numbers.windows(2).map(|w| w[0] - w[1]).collect();
614
615        if diffs.is_empty() {
616            return None;
617        }
618
619        // Check if differences are consistent
620        let first_diff = diffs[0];
621        if first_diff.abs() <= 1 {
622            // Not a stride pattern, just sequential
623            return None;
624        }
625
626        let consistent = diffs.iter().all(|&d| d == first_diff);
627        if consistent { Some(first_diff) } else { None }
628    }
629
630    /// Checks for spatial access pattern
631    fn is_spatial(&self) -> bool {
632        let coords_count = self
633            .history
634            .iter()
635            .rev()
636            .take(5)
637            .filter(|r| r.coordinates.is_some())
638            .count();
639
640        coords_count >= 3
641    }
642
643    /// Returns the current detected pattern
644    #[must_use]
645    pub fn current_pattern(&self) -> AccessPattern {
646        self.current_pattern
647    }
648
649    /// Predicts next likely accesses with priorities
650    #[must_use]
651    pub fn predict_next(&self, count: usize) -> Vec<PrefetchTarget> {
652        match self.current_pattern {
653            AccessPattern::SequentialForward => self.predict_sequential_forward(count),
654            AccessPattern::SequentialBackward => self.predict_sequential_backward(count),
655            AccessPattern::Strided { stride } => self.predict_strided(count, stride),
656            AccessPattern::Spatial => self.predict_spatial(count),
657            AccessPattern::Burst | AccessPattern::Random => self.predict_hot_spots(count),
658            AccessPattern::TemporalPeriodic { .. } => self.predict_recent(count),
659            AccessPattern::Unknown => Vec::new(),
660        }
661    }
662
663    /// Predicts forward sequential accesses
664    fn predict_sequential_forward(&self, count: usize) -> Vec<PrefetchTarget> {
665        let Some(last) = self.history.back() else {
666            return Vec::new();
667        };
668
669        let mut predictions = Vec::new();
670        for i in 1..=count {
671            if let Some(next_key) = increment_key(&last.key, i as i64) {
672                let priority = if i == 1 {
673                    PrefetchPriority::Critical
674                } else if i <= 3 {
675                    PrefetchPriority::High
676                } else {
677                    PrefetchPriority::Medium
678                };
679                let confidence = 1.0 - (i as f64 * 0.1).min(0.5);
680                predictions.push(PrefetchTarget::new(next_key, priority, confidence));
681            }
682        }
683        predictions
684    }
685
686    /// Predicts backward sequential accesses
687    fn predict_sequential_backward(&self, count: usize) -> Vec<PrefetchTarget> {
688        let Some(last) = self.history.back() else {
689            return Vec::new();
690        };
691
692        let mut predictions = Vec::new();
693        for i in 1..=count {
694            if let Some(next_key) = increment_key(&last.key, -(i as i64)) {
695                let priority = if i == 1 {
696                    PrefetchPriority::Critical
697                } else if i <= 3 {
698                    PrefetchPriority::High
699                } else {
700                    PrefetchPriority::Medium
701                };
702                let confidence = 1.0 - (i as f64 * 0.1).min(0.5);
703                predictions.push(PrefetchTarget::new(next_key, priority, confidence));
704            }
705        }
706        predictions
707    }
708
709    /// Predicts strided accesses
710    fn predict_strided(&self, count: usize, stride: i64) -> Vec<PrefetchTarget> {
711        let Some(last) = self.history.back() else {
712            return Vec::new();
713        };
714
715        let mut predictions = Vec::new();
716        for i in 1..=count {
717            if let Some(next_key) = increment_key(&last.key, stride * i as i64) {
718                let priority = if i == 1 {
719                    PrefetchPriority::High
720                } else {
721                    PrefetchPriority::Medium
722                };
723                let confidence = 0.8 - (i as f64 * 0.1).min(0.4);
724                predictions.push(PrefetchTarget::new(next_key, priority, confidence));
725            }
726        }
727        predictions
728    }
729
730    /// Predicts spatial accesses (adjacent tiles)
731    fn predict_spatial(&self, count: usize) -> Vec<PrefetchTarget> {
732        let coords = self.spatial.predict_adjacent(count);
733
734        coords
735            .into_iter()
736            .enumerate()
737            .map(|(i, (x, y, z))| {
738                let key = format!("tile_{x}_{y}_{z}");
739                let priority = if i == 0 {
740                    PrefetchPriority::High
741                } else if i < 4 {
742                    PrefetchPriority::Medium
743                } else {
744                    PrefetchPriority::Low
745                };
746                let confidence = 0.9 - (i as f64 * 0.1).min(0.5);
747                PrefetchTarget::new(key, priority, confidence).with_coordinates(x, y, z)
748            })
749            .collect()
750    }
751
752    /// Predicts based on hot spots (frequently accessed)
753    fn predict_hot_spots(&self, count: usize) -> Vec<PrefetchTarget> {
754        let mut freq_vec: Vec<_> = self.key_frequency.iter().collect();
755        freq_vec.sort_by(|a, b| b.1.cmp(a.1));
756
757        freq_vec
758            .into_iter()
759            .take(count)
760            .map(|(key, freq)| {
761                let priority = if *freq > 10 {
762                    PrefetchPriority::High
763                } else if *freq > 5 {
764                    PrefetchPriority::Medium
765                } else {
766                    PrefetchPriority::Low
767                };
768                let confidence = (*freq as f64 / 20.0).min(0.8);
769                PrefetchTarget::new(key.clone(), priority, confidence)
770            })
771            .collect()
772    }
773
774    /// Predicts based on recent accesses
775    fn predict_recent(&self, count: usize) -> Vec<PrefetchTarget> {
776        self.history
777            .iter()
778            .rev()
779            .take(count)
780            .enumerate()
781            .map(|(i, record)| {
782                let priority = if i == 0 {
783                    PrefetchPriority::Medium
784                } else {
785                    PrefetchPriority::Low
786                };
787                PrefetchTarget::new(record.key.clone(), priority, 0.5)
788            })
789            .collect()
790    }
791}
792
793// ============================================================================
794// Adaptive Buffer Management
795// ============================================================================
796
797/// Statistics for adaptive buffer sizing
798#[derive(Debug, Default)]
799pub struct BufferStats {
800    /// Total prefetch operations
801    pub total_prefetches: AtomicU64,
802    /// Successful prefetches (data was used)
803    pub successful_prefetches: AtomicU64,
804    /// Wasted prefetches (data was not used)
805    pub wasted_prefetches: AtomicU64,
806    /// Average prefetch latency in microseconds
807    pub avg_latency_us: AtomicU64,
808    /// Current buffer utilization percentage
809    pub utilization_pct: AtomicU64,
810}
811
812impl BufferStats {
813    /// Records a prefetch operation
814    pub fn record_prefetch(&self, used: bool, latency_us: u64) {
815        self.total_prefetches.fetch_add(1, Ordering::Relaxed);
816        if used {
817            self.successful_prefetches.fetch_add(1, Ordering::Relaxed);
818        } else {
819            self.wasted_prefetches.fetch_add(1, Ordering::Relaxed);
820        }
821
822        // Update average latency (simple moving average)
823        let current = self.avg_latency_us.load(Ordering::Relaxed);
824        let new_avg = (current * 9 + latency_us) / 10;
825        self.avg_latency_us.store(new_avg, Ordering::Relaxed);
826    }
827
828    /// Calculates the hit rate
829    #[must_use]
830    pub fn hit_rate(&self) -> f64 {
831        let total = self.total_prefetches.load(Ordering::Relaxed);
832        if total == 0 {
833            return 0.0;
834        }
835        let successful = self.successful_prefetches.load(Ordering::Relaxed);
836        successful as f64 / total as f64
837    }
838}
839
840/// Adaptive buffer sizer that adjusts buffer size based on workload
841pub struct AdaptiveBufferSizer {
842    /// Minimum buffer size in bytes
843    min_size: usize,
844    /// Maximum buffer size in bytes
845    max_size: usize,
846    /// Current buffer size in bytes
847    current_size: AtomicUsize,
848    /// Buffer statistics
849    stats: Arc<BufferStats>,
850    /// Target hit rate
851    target_hit_rate: f64,
852    /// Last adjustment time
853    last_adjustment: RwLock<Instant>,
854    /// Adjustment interval
855    adjustment_interval: Duration,
856}
857
858impl AdaptiveBufferSizer {
859    /// Creates a new adaptive buffer sizer
860    #[must_use]
861    pub fn new(min_size: usize, max_size: usize) -> Self {
862        Self {
863            min_size,
864            max_size,
865            current_size: AtomicUsize::new(min_size),
866            stats: Arc::new(BufferStats::default()),
867            target_hit_rate: 0.7,
868            last_adjustment: RwLock::new(Instant::now()),
869            adjustment_interval: Duration::from_secs(10),
870        }
871    }
872
873    /// Gets the current buffer size
874    #[must_use]
875    pub fn current_size(&self) -> usize {
876        self.current_size.load(Ordering::Relaxed)
877    }
878
879    /// Gets the buffer statistics
880    #[must_use]
881    pub fn stats(&self) -> Arc<BufferStats> {
882        Arc::clone(&self.stats)
883    }
884
885    /// Adjusts the buffer size based on statistics
886    pub async fn maybe_adjust(&self) {
887        let last = *self.last_adjustment.read().await;
888        if last.elapsed() < self.adjustment_interval {
889            return;
890        }
891
892        let hit_rate = self.stats.hit_rate();
893        let current = self.current_size.load(Ordering::Relaxed);
894
895        let new_size = if hit_rate < self.target_hit_rate - 0.1 {
896            // Hit rate too low, increase buffer
897            (current as f64 * 1.5) as usize
898        } else if hit_rate > self.target_hit_rate + 0.1 {
899            // Hit rate high enough, we might be able to reduce buffer
900            (current as f64 * 0.9) as usize
901        } else {
902            current
903        };
904
905        let clamped = new_size.clamp(self.min_size, self.max_size);
906        self.current_size.store(clamped, Ordering::Relaxed);
907        *self.last_adjustment.write().await = Instant::now();
908
909        tracing::debug!(
910            "Adjusted prefetch buffer: {} -> {} bytes (hit_rate: {:.2})",
911            current,
912            clamped,
913            hit_rate
914        );
915    }
916}
917
918// ============================================================================
919// Memory and Bandwidth Awareness
920// ============================================================================
921
922/// Memory-aware prefetch controller
923pub struct MemoryAwarePrefetcher {
924    /// Maximum memory usage for prefetch buffer in bytes
925    max_memory: usize,
926    /// Current memory usage in bytes
927    current_usage: AtomicUsize,
928    /// Memory pressure threshold (0.0 - 1.0)
929    pressure_threshold: f64,
930}
931
932impl MemoryAwarePrefetcher {
933    /// Creates a new memory-aware prefetcher
934    #[must_use]
935    pub fn new(max_memory: usize) -> Self {
936        Self {
937            max_memory,
938            current_usage: AtomicUsize::new(0),
939            pressure_threshold: 0.8,
940        }
941    }
942
943    /// Checks if prefetching is allowed given current memory pressure
944    #[must_use]
945    pub fn can_prefetch(&self, size: usize) -> bool {
946        let current = self.current_usage.load(Ordering::Relaxed);
947        let usage_ratio = current as f64 / self.max_memory as f64;
948
949        // Don't prefetch if we're under memory pressure
950        if usage_ratio > self.pressure_threshold {
951            return false;
952        }
953
954        // Check if we have room for this prefetch
955        current + size <= self.max_memory
956    }
957
958    /// Allocates memory for a prefetch
959    pub fn allocate(&self, size: usize) -> bool {
960        let current = self.current_usage.load(Ordering::Relaxed);
961        if current + size > self.max_memory {
962            return false;
963        }
964        self.current_usage.fetch_add(size, Ordering::Relaxed);
965        true
966    }
967
968    /// Releases memory from a completed/evicted prefetch
969    pub fn release(&self, size: usize) {
970        self.current_usage.fetch_sub(size, Ordering::Relaxed);
971    }
972
973    /// Gets the current memory usage
974    #[must_use]
975    pub fn current_usage(&self) -> usize {
976        self.current_usage.load(Ordering::Relaxed)
977    }
978
979    /// Gets the memory pressure (0.0 - 1.0)
980    #[must_use]
981    pub fn pressure(&self) -> f64 {
982        let current = self.current_usage.load(Ordering::Relaxed);
983        current as f64 / self.max_memory as f64
984    }
985}
986
987/// Bandwidth-aware prefetch controller
988pub struct BandwidthAwarePrefetcher {
989    /// Maximum bandwidth in bytes per second
990    max_bandwidth: usize,
991    /// Bandwidth used in current window
992    bandwidth_used: AtomicUsize,
993    /// Window start time
994    window_start: RwLock<Instant>,
995    /// Window duration
996    window_duration: Duration,
997    /// Bandwidth samples for estimation
998    samples: RwLock<VecDeque<(Instant, usize)>>,
999}
1000
1001impl BandwidthAwarePrefetcher {
1002    /// Creates a new bandwidth-aware prefetcher
1003    #[must_use]
1004    pub fn new(max_bandwidth: usize) -> Self {
1005        Self {
1006            max_bandwidth,
1007            bandwidth_used: AtomicUsize::new(0),
1008            window_start: RwLock::new(Instant::now()),
1009            window_duration: Duration::from_secs(1),
1010            samples: RwLock::new(VecDeque::with_capacity(100)),
1011        }
1012    }
1013
1014    /// Checks if bandwidth allows prefetching
1015    pub async fn can_prefetch(&self, size: usize) -> bool {
1016        self.maybe_reset_window().await;
1017        let used = self.bandwidth_used.load(Ordering::Relaxed);
1018        used + size <= self.max_bandwidth
1019    }
1020
1021    /// Records bandwidth usage
1022    pub async fn record_usage(&self, size: usize) {
1023        self.maybe_reset_window().await;
1024        self.bandwidth_used.fetch_add(size, Ordering::Relaxed);
1025
1026        // Record sample for bandwidth estimation
1027        let mut samples = self.samples.write().await;
1028        samples.push_back((Instant::now(), size));
1029        if samples.len() > 100 {
1030            samples.pop_front();
1031        }
1032    }
1033
1034    /// Resets bandwidth window if needed
1035    async fn maybe_reset_window(&self) {
1036        let start = *self.window_start.read().await;
1037        if start.elapsed() >= self.window_duration {
1038            self.bandwidth_used.store(0, Ordering::Relaxed);
1039            *self.window_start.write().await = Instant::now();
1040        }
1041    }
1042
1043    /// Estimates current bandwidth usage rate
1044    pub async fn estimated_bandwidth(&self) -> usize {
1045        let samples = self.samples.read().await;
1046        if samples.len() < 2 {
1047            return 0;
1048        }
1049
1050        let first = samples.front();
1051        let last = samples.back();
1052
1053        if let (Some((first_time, _)), Some((last_time, _))) = (first, last) {
1054            let duration = last_time.duration_since(*first_time);
1055            if duration.as_secs_f64() > 0.0 {
1056                let total_bytes: usize = samples.iter().map(|(_, s)| s).sum();
1057                return (total_bytes as f64 / duration.as_secs_f64()) as usize;
1058            }
1059        }
1060        0
1061    }
1062
1063    /// Gets the remaining bandwidth in the current window
1064    #[must_use]
1065    pub fn remaining_bandwidth(&self) -> usize {
1066        let used = self.bandwidth_used.load(Ordering::Relaxed);
1067        self.max_bandwidth.saturating_sub(used)
1068    }
1069}
1070
1071// ============================================================================
1072// Prefetch Configuration and Manager
1073// ============================================================================
1074
1075/// Prefetch configuration
1076#[derive(Debug, Clone)]
1077pub struct PrefetchConfig {
1078    /// Enable prefetching
1079    pub enabled: bool,
1080    /// Number of items to prefetch ahead
1081    pub prefetch_count: usize,
1082    /// Maximum concurrent prefetch operations
1083    pub max_concurrent: usize,
1084    /// Bandwidth limit in bytes per second (None = unlimited)
1085    pub bandwidth_limit: Option<usize>,
1086    /// Memory limit in bytes for prefetch buffer
1087    pub memory_limit: usize,
1088    /// Enable pattern-based prediction
1089    pub pattern_prediction: bool,
1090    /// Enable adaptive buffer sizing
1091    pub adaptive_sizing: bool,
1092    /// Minimum confidence threshold for prefetching
1093    pub min_confidence: f64,
1094}
1095
1096impl Default for PrefetchConfig {
1097    fn default() -> Self {
1098        Self {
1099            enabled: true,
1100            prefetch_count: 5,
1101            max_concurrent: 4,
1102            bandwidth_limit: None,
1103            memory_limit: 64 * 1024 * 1024, // 64 MB
1104            pattern_prediction: true,
1105            adaptive_sizing: true,
1106            min_confidence: 0.3,
1107        }
1108    }
1109}
1110
1111impl PrefetchConfig {
1112    /// Creates a new prefetch configuration
1113    #[must_use]
1114    pub fn new() -> Self {
1115        Self::default()
1116    }
1117
1118    /// Enables or disables prefetching
1119    #[must_use]
1120    pub fn with_enabled(mut self, enabled: bool) -> Self {
1121        self.enabled = enabled;
1122        self
1123    }
1124
1125    /// Sets the prefetch count
1126    #[must_use]
1127    pub fn with_prefetch_count(mut self, count: usize) -> Self {
1128        self.prefetch_count = count;
1129        self
1130    }
1131
1132    /// Sets the maximum concurrent prefetch operations
1133    #[must_use]
1134    pub fn with_max_concurrent(mut self, max: usize) -> Self {
1135        self.max_concurrent = max;
1136        self
1137    }
1138
1139    /// Sets the bandwidth limit
1140    #[must_use]
1141    pub fn with_bandwidth_limit(mut self, limit: usize) -> Self {
1142        self.bandwidth_limit = Some(limit);
1143        self
1144    }
1145
1146    /// Sets the memory limit
1147    #[must_use]
1148    pub fn with_memory_limit(mut self, limit: usize) -> Self {
1149        self.memory_limit = limit;
1150        self
1151    }
1152
1153    /// Enables adaptive sizing
1154    #[must_use]
1155    pub fn with_adaptive_sizing(mut self, enabled: bool) -> Self {
1156        self.adaptive_sizing = enabled;
1157        self
1158    }
1159}
1160
1161/// Priority queue for prefetch targets
1162#[cfg(feature = "async")]
1163pub struct PrefetchQueue {
1164    /// Queued targets by priority
1165    queues: RwLock<BTreeMap<PrefetchPriority, VecDeque<PrefetchTarget>>>,
1166    /// Set of queued keys (for deduplication)
1167    queued_keys: RwLock<std::collections::HashSet<String>>,
1168}
1169
1170#[cfg(feature = "async")]
1171impl PrefetchQueue {
1172    /// Creates a new prefetch queue
1173    #[must_use]
1174    pub fn new() -> Self {
1175        Self {
1176            queues: RwLock::new(BTreeMap::new()),
1177            queued_keys: RwLock::new(std::collections::HashSet::new()),
1178        }
1179    }
1180
1181    /// Enqueues a prefetch target
1182    pub async fn enqueue(&self, target: PrefetchTarget) {
1183        let mut keys = self.queued_keys.write().await;
1184        if keys.contains(&target.key) {
1185            return; // Already queued
1186        }
1187        keys.insert(target.key.clone());
1188
1189        let mut queues = self.queues.write().await;
1190        queues
1191            .entry(target.priority)
1192            .or_insert_with(VecDeque::new)
1193            .push_back(target);
1194    }
1195
1196    /// Dequeues the highest priority target
1197    pub async fn dequeue(&self) -> Option<PrefetchTarget> {
1198        let mut queues = self.queues.write().await;
1199
1200        // Iterate in reverse order (highest priority first)
1201        for (_, queue) in queues.iter_mut().rev() {
1202            if let Some(target) = queue.pop_front() {
1203                let mut keys = self.queued_keys.write().await;
1204                keys.remove(&target.key);
1205                return Some(target);
1206            }
1207        }
1208        None
1209    }
1210
1211    /// Returns the total queue length
1212    pub async fn len(&self) -> usize {
1213        self.queued_keys.read().await.len()
1214    }
1215
1216    /// Checks if the queue is empty
1217    pub async fn is_empty(&self) -> bool {
1218        self.queued_keys.read().await.is_empty()
1219    }
1220}
1221
1222#[cfg(feature = "async")]
1223impl Default for PrefetchQueue {
1224    fn default() -> Self {
1225        Self::new()
1226    }
1227}
1228
1229/// Comprehensive prefetch manager
1230#[cfg(feature = "async")]
1231pub struct PrefetchManager {
1232    /// Configuration
1233    config: PrefetchConfig,
1234    /// Pattern analyzer
1235    analyzer: Arc<RwLock<PatternAnalyzer>>,
1236    /// Prefetch queue
1237    queue: Arc<PrefetchQueue>,
1238    /// Memory controller
1239    memory: Arc<MemoryAwarePrefetcher>,
1240    /// Bandwidth controller
1241    bandwidth: Arc<BandwidthAwarePrefetcher>,
1242    /// Adaptive buffer sizer
1243    buffer_sizer: Option<Arc<AdaptiveBufferSizer>>,
1244}
1245
1246#[cfg(feature = "async")]
1247impl PrefetchManager {
1248    /// Creates a new prefetch manager
1249    #[must_use]
1250    pub fn new(config: PrefetchConfig) -> Self {
1251        let memory = Arc::new(MemoryAwarePrefetcher::new(config.memory_limit));
1252
1253        let bandwidth = Arc::new(BandwidthAwarePrefetcher::new(
1254            config.bandwidth_limit.unwrap_or(usize::MAX),
1255        ));
1256
1257        let buffer_sizer = if config.adaptive_sizing {
1258            Some(Arc::new(AdaptiveBufferSizer::new(
1259                config.memory_limit / 4,
1260                config.memory_limit,
1261            )))
1262        } else {
1263            None
1264        };
1265
1266        Self {
1267            config,
1268            analyzer: Arc::new(RwLock::new(PatternAnalyzer::new(100))),
1269            queue: Arc::new(PrefetchQueue::new()),
1270            memory,
1271            bandwidth,
1272            buffer_sizer,
1273        }
1274    }
1275
1276    /// Records an access and triggers prefetching
1277    pub async fn record_access(&self, record: AccessRecord) -> Vec<PrefetchTarget> {
1278        if !self.config.enabled {
1279            return Vec::new();
1280        }
1281
1282        let mut analyzer = self.analyzer.write().await;
1283        analyzer.record_access(record);
1284
1285        if self.config.pattern_prediction {
1286            let mut predictions = analyzer.predict_next(self.config.prefetch_count);
1287
1288            // Filter by confidence
1289            predictions.retain(|t| t.confidence >= self.config.min_confidence);
1290
1291            // Enqueue predictions
1292            for target in &predictions {
1293                self.queue.enqueue(target.clone()).await;
1294            }
1295
1296            predictions
1297        } else {
1298            Vec::new()
1299        }
1300    }
1301
1302    /// Checks if prefetching is allowed (memory and bandwidth)
1303    pub async fn can_prefetch(&self, size: usize) -> bool {
1304        // Check memory
1305        if !self.memory.can_prefetch(size) {
1306            return false;
1307        }
1308
1309        // Check bandwidth
1310        if !self.bandwidth.can_prefetch(size).await {
1311            return false;
1312        }
1313
1314        true
1315    }
1316
1317    /// Records a completed prefetch
1318    pub async fn record_prefetch(&self, size: usize, used: bool, latency: Duration) {
1319        self.bandwidth.record_usage(size).await;
1320
1321        if let Some(ref sizer) = self.buffer_sizer {
1322            sizer
1323                .stats()
1324                .record_prefetch(used, latency.as_micros() as u64);
1325            sizer.maybe_adjust().await;
1326        }
1327    }
1328
1329    /// Allocates memory for a prefetch
1330    pub fn allocate_memory(&self, size: usize) -> bool {
1331        self.memory.allocate(size)
1332    }
1333
1334    /// Releases memory from a prefetch
1335    pub fn release_memory(&self, size: usize) {
1336        self.memory.release(size);
1337    }
1338
1339    /// Gets the next prefetch target from the queue
1340    pub async fn next_target(&self) -> Option<PrefetchTarget> {
1341        self.queue.dequeue().await
1342    }
1343
1344    /// Returns the current access pattern
1345    pub async fn current_pattern(&self) -> AccessPattern {
1346        self.analyzer.read().await.current_pattern()
1347    }
1348
1349    /// Returns the queue length
1350    pub async fn queue_len(&self) -> usize {
1351        self.queue.len().await
1352    }
1353
1354    /// Gets memory pressure (0.0 - 1.0)
1355    #[must_use]
1356    pub fn memory_pressure(&self) -> f64 {
1357        self.memory.pressure()
1358    }
1359
1360    /// Gets remaining bandwidth
1361    #[must_use]
1362    pub fn remaining_bandwidth(&self) -> usize {
1363        self.bandwidth.remaining_bandwidth()
1364    }
1365
1366    /// Gets the prefetch hit rate
1367    #[must_use]
1368    pub fn hit_rate(&self) -> f64 {
1369        self.buffer_sizer
1370            .as_ref()
1371            .map_or(0.0, |s| s.stats().hit_rate())
1372    }
1373}
1374
1375// ============================================================================
1376// Helper Functions
1377// ============================================================================
1378
1379/// Extracts trailing number from a key
1380fn extract_trailing_number(key: &str) -> Option<i64> {
1381    let parts: Vec<&str> = key
1382        .split(|c: char| !c.is_ascii_digit() && c != '-')
1383        .collect();
1384    parts
1385        .iter()
1386        .rev()
1387        .find(|s| !s.is_empty())
1388        .and_then(|s| s.parse().ok())
1389}
1390
1391/// Increments the trailing number in a key
1392fn increment_key(key: &str, offset: i64) -> Option<String> {
1393    let parts: Vec<&str> = key.split('_').collect();
1394
1395    for i in (0..parts.len()).rev() {
1396        if let Ok(num) = parts[i].parse::<i64>() {
1397            let new_num = num + offset;
1398            if new_num < 0 {
1399                return None;
1400            }
1401            let mut new_parts: Vec<String> = parts.iter().map(|s| (*s).to_string()).collect();
1402            new_parts[i] = new_num.to_string();
1403            return Some(new_parts.join("_"));
1404        }
1405    }
1406    None
1407}
1408
1409// ============================================================================
1410// Tests
1411// ============================================================================
1412
1413#[cfg(test)]
1414mod tests {
1415    use super::*;
1416
1417    #[test]
1418    fn test_extract_trailing_number() {
1419        assert_eq!(extract_trailing_number("file_5"), Some(5));
1420        assert_eq!(extract_trailing_number("tile_10_20_3"), Some(3));
1421        assert_eq!(extract_trailing_number("data"), None);
1422    }
1423
1424    #[test]
1425    fn test_increment_key() {
1426        assert_eq!(increment_key("file_5", 1), Some("file_6".to_string()));
1427        assert_eq!(increment_key("file_5", -1), Some("file_4".to_string()));
1428        assert_eq!(
1429            increment_key("tile_10_20", 1),
1430            Some("tile_10_21".to_string())
1431        );
1432        assert_eq!(increment_key("file_0", -1), None);
1433    }
1434
1435    #[test]
1436    fn test_temporal_pattern_analyzer() {
1437        let mut analyzer = TemporalPatternAnalyzer::new(20);
1438
1439        // Record consistent intervals
1440        for _ in 0..10 {
1441            analyzer.record_interval(1000); // 1 second intervals
1442        }
1443
1444        assert!(analyzer.detected_period().is_some());
1445        let period = analyzer.detected_period();
1446        assert!(period.is_some_and(|p| (p as i64 - 1000).abs() < 100));
1447    }
1448
1449    #[test]
1450    fn test_temporal_burst_detection() {
1451        let mut analyzer = TemporalPatternAnalyzer::new(20);
1452
1453        // Record rapid intervals
1454        for _ in 0..10 {
1455            analyzer.record_interval(50); // 50ms intervals = 20/sec
1456        }
1457
1458        assert!(analyzer.is_burst());
1459    }
1460
1461    #[test]
1462    fn test_spatial_locality_analyzer() {
1463        let mut analyzer = SpatialLocalityAnalyzer::new(10);
1464
1465        // Record a path moving right
1466        analyzer.record_access(0, 0, 0);
1467        analyzer.record_access(1, 0, 0);
1468        analyzer.record_access(2, 0, 0);
1469
1470        let predictions = analyzer.predict_adjacent(4);
1471        assert!(!predictions.is_empty());
1472
1473        // First prediction should be in the direction of movement (right)
1474        assert!(predictions.iter().any(|&(x, _, _)| x == 3));
1475    }
1476
1477    #[test]
1478    fn test_pattern_analyzer_sequential() {
1479        let mut analyzer = PatternAnalyzer::new(10);
1480
1481        analyzer.record_access(AccessRecord::new("file_0".to_string()));
1482        analyzer.record_access(AccessRecord::new("file_1".to_string()));
1483        analyzer.record_access(AccessRecord::new("file_2".to_string()));
1484        analyzer.record_access(AccessRecord::new("file_3".to_string()));
1485
1486        assert!(matches!(
1487            analyzer.current_pattern(),
1488            AccessPattern::SequentialForward | AccessPattern::SequentialBackward
1489        ));
1490
1491        let predictions = analyzer.predict_next(3);
1492        assert!(!predictions.is_empty());
1493    }
1494
1495    #[test]
1496    fn test_pattern_analyzer_spatial() {
1497        let mut analyzer = PatternAnalyzer::new(10);
1498
1499        analyzer.record_access(AccessRecord::with_coordinates(
1500            "tile_0_0_0".to_string(),
1501            0,
1502            0,
1503            0,
1504        ));
1505        analyzer.record_access(AccessRecord::with_coordinates(
1506            "tile_1_0_0".to_string(),
1507            1,
1508            0,
1509            0,
1510        ));
1511        analyzer.record_access(AccessRecord::with_coordinates(
1512            "tile_2_0_0".to_string(),
1513            2,
1514            0,
1515            0,
1516        ));
1517        analyzer.record_access(AccessRecord::with_coordinates(
1518            "tile_3_0_0".to_string(),
1519            3,
1520            0,
1521            0,
1522        ));
1523
1524        assert_eq!(analyzer.current_pattern(), AccessPattern::Spatial);
1525
1526        let predictions = analyzer.predict_next(4);
1527        assert!(!predictions.is_empty());
1528    }
1529
1530    #[test]
1531    fn test_prefetch_target_priority_ordering() {
1532        let targets = [
1533            PrefetchTarget::new("low".to_string(), PrefetchPriority::Low, 0.5),
1534            PrefetchTarget::new("critical".to_string(), PrefetchPriority::Critical, 0.9),
1535            PrefetchTarget::new("medium".to_string(), PrefetchPriority::Medium, 0.7),
1536        ];
1537
1538        let mut sorted: Vec<_> = targets.iter().map(|t| t.priority).collect();
1539        sorted.sort();
1540
1541        assert_eq!(sorted[0], PrefetchPriority::Low);
1542        assert_eq!(sorted[1], PrefetchPriority::Medium);
1543        assert_eq!(sorted[2], PrefetchPriority::Critical);
1544    }
1545
1546    #[test]
1547    fn test_memory_aware_prefetcher() {
1548        let prefetcher = MemoryAwarePrefetcher::new(1000);
1549
1550        assert!(prefetcher.can_prefetch(500));
1551        assert!(prefetcher.allocate(500));
1552        assert_eq!(prefetcher.current_usage(), 500);
1553
1554        assert!(prefetcher.can_prefetch(400));
1555        assert!(!prefetcher.can_prefetch(600)); // Would exceed limit
1556
1557        prefetcher.release(200);
1558        assert_eq!(prefetcher.current_usage(), 300);
1559    }
1560
1561    #[test]
1562    fn test_buffer_stats() {
1563        let stats = BufferStats::default();
1564
1565        stats.record_prefetch(true, 100);
1566        stats.record_prefetch(true, 200);
1567        stats.record_prefetch(false, 150);
1568
1569        assert_eq!(stats.total_prefetches.load(Ordering::Relaxed), 3);
1570        assert_eq!(stats.successful_prefetches.load(Ordering::Relaxed), 2);
1571        assert_eq!(stats.wasted_prefetches.load(Ordering::Relaxed), 1);
1572
1573        let hit_rate = stats.hit_rate();
1574        assert!((hit_rate - 0.666).abs() < 0.01);
1575    }
1576
1577    #[test]
1578    fn test_prefetch_config_builder() {
1579        let config = PrefetchConfig::new()
1580            .with_enabled(true)
1581            .with_prefetch_count(10)
1582            .with_max_concurrent(8)
1583            .with_bandwidth_limit(1_000_000)
1584            .with_memory_limit(128 * 1024 * 1024)
1585            .with_adaptive_sizing(true);
1586
1587        assert!(config.enabled);
1588        assert_eq!(config.prefetch_count, 10);
1589        assert_eq!(config.max_concurrent, 8);
1590        assert_eq!(config.bandwidth_limit, Some(1_000_000));
1591        assert_eq!(config.memory_limit, 128 * 1024 * 1024);
1592        assert!(config.adaptive_sizing);
1593    }
1594
1595    #[cfg(feature = "async")]
1596    #[tokio::test]
1597    async fn test_prefetch_queue() {
1598        let queue = PrefetchQueue::new();
1599
1600        queue
1601            .enqueue(PrefetchTarget::new(
1602                "low".to_string(),
1603                PrefetchPriority::Low,
1604                0.5,
1605            ))
1606            .await;
1607        queue
1608            .enqueue(PrefetchTarget::new(
1609                "high".to_string(),
1610                PrefetchPriority::High,
1611                0.9,
1612            ))
1613            .await;
1614        queue
1615            .enqueue(PrefetchTarget::new(
1616                "medium".to_string(),
1617                PrefetchPriority::Medium,
1618                0.7,
1619            ))
1620            .await;
1621
1622        assert_eq!(queue.len().await, 3);
1623
1624        // Should dequeue highest priority first
1625        let first = queue.dequeue().await;
1626        assert!(first.is_some());
1627        assert_eq!(first.map(|t| t.key), Some("high".to_string()));
1628
1629        let second = queue.dequeue().await;
1630        assert!(second.is_some());
1631        assert_eq!(second.map(|t| t.key), Some("medium".to_string()));
1632    }
1633
1634    #[cfg(feature = "async")]
1635    #[tokio::test]
1636    async fn test_prefetch_queue_deduplication() {
1637        let queue = PrefetchQueue::new();
1638
1639        queue
1640            .enqueue(PrefetchTarget::new(
1641                "key1".to_string(),
1642                PrefetchPriority::High,
1643                0.9,
1644            ))
1645            .await;
1646        queue
1647            .enqueue(PrefetchTarget::new(
1648                "key1".to_string(),
1649                PrefetchPriority::Critical,
1650                0.95,
1651            ))
1652            .await;
1653
1654        // Should only have one entry
1655        assert_eq!(queue.len().await, 1);
1656    }
1657
1658    #[cfg(feature = "async")]
1659    #[tokio::test]
1660    async fn test_prefetch_manager() {
1661        let config = PrefetchConfig::new()
1662            .with_prefetch_count(3)
1663            .with_memory_limit(1024 * 1024);
1664        let manager = PrefetchManager::new(config);
1665
1666        // Record sequential accesses
1667        manager
1668            .record_access(AccessRecord::new("file_0".to_string()))
1669            .await;
1670        manager
1671            .record_access(AccessRecord::new("file_1".to_string()))
1672            .await;
1673        manager
1674            .record_access(AccessRecord::new("file_2".to_string()))
1675            .await;
1676
1677        let predictions = manager
1678            .record_access(AccessRecord::new("file_3".to_string()))
1679            .await;
1680
1681        // Should predict sequential pattern
1682        assert!(!predictions.is_empty());
1683    }
1684
1685    #[cfg(feature = "async")]
1686    #[tokio::test]
1687    async fn test_prefetch_manager_memory_pressure() {
1688        let config = PrefetchConfig::new().with_memory_limit(1000);
1689        let manager = PrefetchManager::new(config);
1690
1691        // Allocate most of the memory
1692        assert!(manager.allocate_memory(800));
1693        assert!(manager.memory_pressure() > 0.7);
1694
1695        // Should not allow more prefetching
1696        assert!(!manager.can_prefetch(300).await);
1697
1698        // Release memory
1699        manager.release_memory(500);
1700        assert!(manager.can_prefetch(300).await);
1701    }
1702
1703    #[cfg(feature = "async")]
1704    #[tokio::test]
1705    async fn test_bandwidth_aware_prefetcher() {
1706        let prefetcher = BandwidthAwarePrefetcher::new(1000);
1707
1708        assert!(prefetcher.can_prefetch(500).await);
1709        prefetcher.record_usage(500).await;
1710
1711        assert!(prefetcher.can_prefetch(400).await);
1712        prefetcher.record_usage(400).await;
1713
1714        // Should be near limit
1715        assert!(!prefetcher.can_prefetch(200).await);
1716        assert!(prefetcher.remaining_bandwidth() < 200);
1717    }
1718}