Skip to main content

cbtop/incremental_snapshot/
types.rs

1//! Core types for incremental snapshots: errors, retention tiers, and metric data.
2
3use std::time::Duration;
4
5/// Result type for snapshot operations
6pub type SnapshotResult<T> = Result<T, SnapshotError>;
7
8/// Errors in snapshot operations
9#[derive(Debug, Clone, PartialEq)]
10pub enum SnapshotError {
11    /// IO error
12    IoError { reason: String },
13    /// Snapshot not found
14    NotFound { index: usize },
15    /// Corrupt snapshot
16    Corrupt { reason: String },
17    /// Checksum mismatch
18    ChecksumMismatch { expected: u32, actual: u32 },
19    /// Index out of bounds
20    IndexOutOfBounds { index: usize, max: usize },
21    /// Memory limit exceeded
22    MemoryExceeded { limit_bytes: usize },
23}
24
25impl std::fmt::Display for SnapshotError {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            Self::IoError { reason } => write!(f, "IO error: {}", reason),
29            Self::NotFound { index } => write!(f, "Snapshot {} not found", index),
30            Self::Corrupt { reason } => write!(f, "Corrupt snapshot: {}", reason),
31            Self::ChecksumMismatch { expected, actual } => {
32                write!(
33                    f,
34                    "Checksum mismatch: expected {}, got {}",
35                    expected, actual
36                )
37            }
38            Self::IndexOutOfBounds { index, max } => {
39                write!(f, "Index {} out of bounds (max {})", index, max)
40            }
41            Self::MemoryExceeded { limit_bytes } => {
42                write!(f, "Memory limit {} exceeded", limit_bytes)
43            }
44        }
45    }
46}
47
48impl std::error::Error for SnapshotError {}
49
50/// Retention tier for snapshots
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum RetentionTier {
53    /// Raw snapshots (full data)
54    Raw,
55    /// Compressed snapshots (delta encoded)
56    Compressed,
57    /// Archived snapshots (highly compressed, read-only)
58    Archive,
59}
60
61impl RetentionTier {
62    /// Get maximum age for this tier
63    pub fn max_age(&self) -> Duration {
64        match self {
65            Self::Raw => Duration::from_secs(24 * 60 * 60), // 1 day
66            Self::Compressed => Duration::from_secs(30 * 24 * 60 * 60), // 30 days
67            Self::Archive => Duration::from_secs(365 * 24 * 60 * 60), // 1 year
68        }
69    }
70}
71
72/// Profile metric data
73#[derive(Debug, Clone)]
74pub struct MetricData {
75    /// Metric name
76    pub name: String,
77    /// Metric values (time series)
78    pub values: Vec<f64>,
79    /// Timestamps (nanoseconds since epoch)
80    pub timestamps: Vec<u64>,
81}
82
83impl MetricData {
84    /// Create new metric data
85    pub fn new(name: impl Into<String>) -> Self {
86        Self {
87            name: name.into(),
88            values: Vec::new(),
89            timestamps: Vec::new(),
90        }
91    }
92
93    /// Add a sample
94    pub fn add(&mut self, value: f64, timestamp: u64) {
95        self.values.push(value);
96        self.timestamps.push(timestamp);
97    }
98
99    /// Get serialized size estimate
100    pub fn size_bytes(&self) -> usize {
101        self.name.len() + self.values.len() * 8 + self.timestamps.len() * 8
102    }
103
104    /// Compute delta from another metric
105    pub fn delta_from(&self, other: &MetricData) -> DeltaMetric {
106        let mut changed_indices = Vec::new();
107        let mut changed_values = Vec::new();
108
109        // Find changed values
110        let max_len = self.values.len().max(other.values.len());
111        for i in 0..max_len {
112            let self_val = self.values.get(i).copied().unwrap_or(0.0);
113            let other_val = other.values.get(i).copied().unwrap_or(0.0);
114
115            if (self_val - other_val).abs() > 1e-10 {
116                changed_indices.push(i);
117                changed_values.push(self_val);
118            }
119        }
120
121        DeltaMetric {
122            name: self.name.clone(),
123            base_len: other.values.len(),
124            new_len: self.values.len(),
125            changed_indices,
126            changed_values,
127        }
128    }
129
130    /// Apply delta to reconstruct
131    pub fn apply_delta(&self, delta: &DeltaMetric) -> MetricData {
132        let mut result = MetricData::new(&delta.name);
133
134        // Start with base values
135        result.values = self.values.clone();
136        result.timestamps = self.timestamps.clone();
137
138        // Resize if needed
139        result.values.resize(delta.new_len, 0.0);
140        result.timestamps.resize(delta.new_len, 0);
141
142        // Apply changes
143        for (i, &idx) in delta.changed_indices.iter().enumerate() {
144            if idx < result.values.len() {
145                result.values[idx] = delta.changed_values[i];
146            }
147        }
148
149        result
150    }
151}
152
153/// Delta-encoded metric
154#[derive(Debug, Clone)]
155pub struct DeltaMetric {
156    /// Metric name
157    pub name: String,
158    /// Base array length
159    pub base_len: usize,
160    /// New array length
161    pub new_len: usize,
162    /// Indices of changed values
163    pub changed_indices: Vec<usize>,
164    /// Changed values
165    pub changed_values: Vec<f64>,
166}
167
168impl DeltaMetric {
169    /// Get compressed size estimate
170    pub fn size_bytes(&self) -> usize {
171        self.name.len() + 16 + self.changed_indices.len() * 8 + self.changed_values.len() * 8
172    }
173
174    /// Get compression ratio (compressed/original)
175    pub fn compression_ratio(&self, original_size: usize) -> f64 {
176        if original_size == 0 {
177            1.0
178        } else {
179            self.size_bytes() as f64 / original_size as f64
180        }
181    }
182}