cbtop/incremental_snapshot/
snapshot.rs1use std::collections::HashMap;
4
5use super::types::{DeltaMetric, MetricData, RetentionTier};
6
7#[derive(Debug, Clone)]
9pub struct ProfileSnapshot {
10 pub index: usize,
12 pub timestamp_ns: u64,
14 pub workload_fingerprint: String,
16 pub metrics: HashMap<String, MetricData>,
18 pub metadata: HashMap<String, String>,
20 pub checksum: u32,
22}
23
24impl ProfileSnapshot {
25 pub fn new(index: usize) -> Self {
27 Self {
28 index,
29 timestamp_ns: std::time::SystemTime::now()
30 .duration_since(std::time::UNIX_EPOCH)
31 .map(|d| d.as_nanos() as u64)
32 .unwrap_or(0),
33 workload_fingerprint: String::new(),
34 metrics: HashMap::new(),
35 metadata: HashMap::new(),
36 checksum: 0,
37 }
38 }
39
40 pub fn add_metric(&mut self, metric: MetricData) {
42 self.metrics.insert(metric.name.clone(), metric);
43 }
44
45 pub fn get_metric(&self, name: &str) -> Option<&MetricData> {
47 self.metrics.get(name)
48 }
49
50 pub fn set_fingerprint(&mut self, fingerprint: impl Into<String>) {
52 self.workload_fingerprint = fingerprint.into();
53 }
54
55 pub fn size_bytes(&self) -> usize {
57 let metrics_size: usize = self.metrics.values().map(|m| m.size_bytes()).sum();
58 let metadata_size: usize = self.metadata.iter().map(|(k, v)| k.len() + v.len()).sum();
59
60 24 + self.workload_fingerprint.len() + metrics_size + metadata_size
61 }
62
63 pub fn compute_checksum(&mut self) {
65 let mut hash: u32 = 0;
67
68 hash = hash.wrapping_add(self.index as u32);
69 hash = hash.wrapping_mul(31);
70 hash = hash.wrapping_add((self.timestamp_ns & 0xFFFFFFFF) as u32);
71
72 let mut keys: Vec<_> = self.metrics.keys().collect();
74 keys.sort();
75
76 for name in keys {
77 if let Some(metric) = self.metrics.get(name) {
78 for c in name.bytes() {
79 hash = hash.wrapping_mul(31).wrapping_add(c as u32);
80 }
81 for &val in &metric.values {
82 let bits = val.to_bits();
84 hash = hash.wrapping_mul(31).wrapping_add((bits >> 32) as u32);
85 hash = hash
86 .wrapping_mul(31)
87 .wrapping_add((bits & 0xFFFFFFFF) as u32);
88 }
89 }
90 }
91
92 self.checksum = hash;
93 }
94
95 pub fn verify_checksum(&self) -> bool {
97 let mut copy = self.clone();
98 copy.compute_checksum();
99 copy.checksum == self.checksum
100 }
101}
102
103#[derive(Debug, Clone)]
105pub struct DeltaSnapshot {
106 pub index: usize,
108 pub base_index: usize,
110 pub timestamp_ns: u64,
112 pub workload_fingerprint: String,
114 pub deltas: HashMap<String, DeltaMetric>,
116 pub new_metrics: HashMap<String, MetricData>,
118 pub removed_metrics: Vec<String>,
120 pub checksum: u32,
122}
123
124impl DeltaSnapshot {
125 pub fn from_diff(base: &ProfileSnapshot, current: &ProfileSnapshot) -> Self {
127 let mut deltas = HashMap::new();
128 let mut new_metrics = HashMap::new();
129 let mut removed_metrics = Vec::new();
130
131 for (name, metric) in ¤t.metrics {
133 if let Some(base_metric) = base.metrics.get(name) {
134 let delta = metric.delta_from(base_metric);
135 if delta.size_bytes() < metric.size_bytes() {
137 deltas.insert(name.clone(), delta);
138 } else {
139 new_metrics.insert(name.clone(), metric.clone());
140 }
141 } else {
142 new_metrics.insert(name.clone(), metric.clone());
143 }
144 }
145
146 for name in base.metrics.keys() {
148 if !current.metrics.contains_key(name) {
149 removed_metrics.push(name.clone());
150 }
151 }
152
153 Self {
154 index: current.index,
155 base_index: base.index,
156 timestamp_ns: current.timestamp_ns,
157 workload_fingerprint: current.workload_fingerprint.clone(),
158 deltas,
159 new_metrics,
160 removed_metrics,
161 checksum: 0,
162 }
163 }
164
165 pub fn size_bytes(&self) -> usize {
167 let delta_size: usize = self.deltas.values().map(|d| d.size_bytes()).sum();
168 let new_size: usize = self.new_metrics.values().map(|m| m.size_bytes()).sum();
169 let removed_size: usize = self.removed_metrics.iter().map(|s| s.len()).sum();
170
171 24 + self.workload_fingerprint.len() + delta_size + new_size + removed_size
172 }
173
174 pub fn apply_to(&self, base: &ProfileSnapshot) -> ProfileSnapshot {
176 let mut result = ProfileSnapshot::new(self.index);
177 result.timestamp_ns = self.timestamp_ns;
178 result.workload_fingerprint = self.workload_fingerprint.clone();
179
180 for (name, metric) in &base.metrics {
182 if self.removed_metrics.contains(name) {
183 continue;
184 }
185
186 if let Some(delta) = self.deltas.get(name) {
187 let reconstructed = metric.apply_delta(delta);
188 result.metrics.insert(name.clone(), reconstructed);
189 } else {
190 result.metrics.insert(name.clone(), metric.clone());
191 }
192 }
193
194 for (name, metric) in &self.new_metrics {
196 result.metrics.insert(name.clone(), metric.clone());
197 }
198
199 result.compute_checksum();
200 result
201 }
202}
203
204#[derive(Debug, Clone)]
206pub struct SnapshotIndex {
207 pub index: usize,
209 pub timestamp_ns: u64,
211 pub fingerprint: String,
213 pub tier: RetentionTier,
215 pub offset: usize,
217 pub size_bytes: usize,
219 pub is_delta: bool,
221 pub base_index: Option<usize>,
223}