use std::time::Duration;
pub type SnapshotResult<T> = Result<T, SnapshotError>;
#[derive(Debug, Clone, PartialEq)]
pub enum SnapshotError {
IoError { reason: String },
NotFound { index: usize },
Corrupt { reason: String },
ChecksumMismatch { expected: u32, actual: u32 },
IndexOutOfBounds { index: usize, max: usize },
MemoryExceeded { limit_bytes: usize },
}
impl std::fmt::Display for SnapshotError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::IoError { reason } => write!(f, "IO error: {}", reason),
Self::NotFound { index } => write!(f, "Snapshot {} not found", index),
Self::Corrupt { reason } => write!(f, "Corrupt snapshot: {}", reason),
Self::ChecksumMismatch { expected, actual } => {
write!(
f,
"Checksum mismatch: expected {}, got {}",
expected, actual
)
}
Self::IndexOutOfBounds { index, max } => {
write!(f, "Index {} out of bounds (max {})", index, max)
}
Self::MemoryExceeded { limit_bytes } => {
write!(f, "Memory limit {} exceeded", limit_bytes)
}
}
}
}
impl std::error::Error for SnapshotError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RetentionTier {
Raw,
Compressed,
Archive,
}
impl RetentionTier {
pub fn max_age(&self) -> Duration {
match self {
Self::Raw => Duration::from_secs(24 * 60 * 60), Self::Compressed => Duration::from_secs(30 * 24 * 60 * 60), Self::Archive => Duration::from_secs(365 * 24 * 60 * 60), }
}
}
#[derive(Debug, Clone)]
pub struct MetricData {
pub name: String,
pub values: Vec<f64>,
pub timestamps: Vec<u64>,
}
impl MetricData {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
values: Vec::new(),
timestamps: Vec::new(),
}
}
pub fn add(&mut self, value: f64, timestamp: u64) {
self.values.push(value);
self.timestamps.push(timestamp);
}
pub fn size_bytes(&self) -> usize {
self.name.len() + self.values.len() * 8 + self.timestamps.len() * 8
}
pub fn delta_from(&self, other: &MetricData) -> DeltaMetric {
let mut changed_indices = Vec::new();
let mut changed_values = Vec::new();
let max_len = self.values.len().max(other.values.len());
for i in 0..max_len {
let self_val = self.values.get(i).copied().unwrap_or(0.0);
let other_val = other.values.get(i).copied().unwrap_or(0.0);
if (self_val - other_val).abs() > 1e-10 {
changed_indices.push(i);
changed_values.push(self_val);
}
}
DeltaMetric {
name: self.name.clone(),
base_len: other.values.len(),
new_len: self.values.len(),
changed_indices,
changed_values,
}
}
pub fn apply_delta(&self, delta: &DeltaMetric) -> MetricData {
let mut result = MetricData::new(&delta.name);
result.values = self.values.clone();
result.timestamps = self.timestamps.clone();
result.values.resize(delta.new_len, 0.0);
result.timestamps.resize(delta.new_len, 0);
for (i, &idx) in delta.changed_indices.iter().enumerate() {
if idx < result.values.len() {
result.values[idx] = delta.changed_values[i];
}
}
result
}
}
#[derive(Debug, Clone)]
pub struct DeltaMetric {
pub name: String,
pub base_len: usize,
pub new_len: usize,
pub changed_indices: Vec<usize>,
pub changed_values: Vec<f64>,
}
impl DeltaMetric {
pub fn size_bytes(&self) -> usize {
self.name.len() + 16 + self.changed_indices.len() * 8 + self.changed_values.len() * 8
}
pub fn compression_ratio(&self, original_size: usize) -> f64 {
if original_size == 0 {
1.0
} else {
self.size_bytes() as f64 / original_size as f64
}
}
}