1use core::f64::consts::E;
38
39const FNV_OFFSET: u64 = 0xcbf29ce484222325;
40const FNV_PRIME: u64 = 0x100000001b3;
41
42pub const MAX_DEPTH: usize = 16;
46
47const SNAPSHOT_MAGIC: [u8; 8] = *b"SUBMSCMS";
48const SNAPSHOT_VERSION: u16 = 1;
49const SNAPSHOT_HEADER: usize = 32;
50
51#[derive(Debug, PartialEq, Eq)]
53pub enum SnapshotError {
54 BadMagic,
55 UnsupportedVersion(u16),
56 BadShape { depth: usize, width: usize },
57 Truncated { expected: usize, actual: usize },
58}
59
60impl core::fmt::Display for SnapshotError {
61 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
62 match self {
63 SnapshotError::BadMagic => write!(f, "not a count-min-sketch snapshot"),
64 SnapshotError::UnsupportedVersion(v) => write!(f, "unsupported snapshot version {v}"),
65 SnapshotError::BadShape { depth, width } => {
66 write!(f, "invalid shape: depth={depth}, width={width}")
67 }
68 SnapshotError::Truncated { expected, actual } => {
69 write!(
70 f,
71 "truncated snapshot: expected {expected} bytes, got {actual}"
72 )
73 }
74 }
75 }
76}
77
78impl std::error::Error for SnapshotError {}
79
80pub struct CountMinSketch {
81 d: usize,
82 w: usize,
83 mask: usize,
84 seed: u64,
85 total: u64,
86 rows: Vec<Vec<u32>>,
87}
88
89impl core::fmt::Debug for CountMinSketch {
91 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
92 f.debug_struct("CountMinSketch")
93 .field("depth", &self.d)
94 .field("width", &self.w)
95 .field("seed", &self.seed)
96 .field("total", &self.total)
97 .finish()
98 }
99}
100
101impl CountMinSketch {
102 pub fn new(d: usize, w: usize) -> Self {
106 Self::with_seed(d, w, 0)
107 }
108
109 pub fn with_seed(d: usize, w: usize, seed: u64) -> Self {
112 let d = d.clamp(2, MAX_DEPTH);
113 let w = w.max(2).next_power_of_two();
114 let rows = (0..d).map(|_| vec![0u32; w]).collect();
115 Self {
116 d,
117 w,
118 mask: w - 1,
119 seed,
120 total: 0,
121 rows,
122 }
123 }
124
125 pub fn with_error_bounds(epsilon: f64, confidence: f64) -> Self {
129 Self::with_error_bounds_seeded(epsilon, confidence, 0)
130 }
131
132 pub fn with_error_bounds_seeded(epsilon: f64, confidence: f64, seed: u64) -> Self {
133 Self::with_seed(
134 Self::suggest_depth(confidence),
135 Self::suggest_width(epsilon),
136 seed,
137 )
138 }
139
140 pub fn suggest_width(epsilon: f64) -> usize {
143 if epsilon.is_nan() || epsilon <= 0.0 {
144 return 1 << 30;
145 }
146 let w = (E / epsilon).ceil();
147 if w >= (1u64 << 30) as f64 {
148 return 1 << 30;
149 }
150 (w as usize).max(2).next_power_of_two()
151 }
152
153 pub fn suggest_depth(confidence: f64) -> usize {
156 if confidence.is_nan() || confidence <= 0.0 {
157 return 2;
158 }
159 if confidence >= 1.0 {
160 return MAX_DEPTH;
161 }
162 let d = (1.0 / (1.0 - confidence)).ln().ceil();
163 if d >= MAX_DEPTH as f64 {
164 return MAX_DEPTH;
165 }
166 (d as usize).clamp(2, MAX_DEPTH)
167 }
168
169 pub fn depth(&self) -> usize {
170 self.d
171 }
172 pub fn width(&self) -> usize {
173 self.w
174 }
175 pub fn seed(&self) -> u64 {
176 self.seed
177 }
178
179 pub fn total(&self) -> u64 {
182 self.total
183 }
184
185 pub fn is_empty(&self) -> bool {
186 self.total == 0
187 }
188
189 pub fn relative_error(&self) -> f64 {
191 E / self.w as f64
192 }
193
194 pub fn confidence(&self) -> f64 {
196 1.0 - (-(self.d as f64)).exp()
197 }
198
199 pub fn error_margin(&self) -> u32 {
201 let m = (self.relative_error() * self.total as f64).ceil();
202 if m >= u32::MAX as f64 {
203 u32::MAX
204 } else {
205 m as u32
206 }
207 }
208
209 pub fn occupancy(&self) -> f64 {
213 let used: usize = self
214 .rows
215 .iter()
216 .map(|r| r.iter().filter(|&&c| c != 0).count())
217 .sum();
218 used as f64 / (self.d * self.w) as f64
219 }
220
221 pub fn heap_bytes(&self) -> usize {
224 self.d * self.w * core::mem::size_of::<u32>()
225 }
226
227 pub fn add(&mut self, key: &str) {
229 self.add_bytes_n(key.as_bytes(), 1);
230 }
231
232 pub fn add_n(&mut self, key: &str, n: u32) {
235 self.add_bytes_n(key.as_bytes(), n);
236 }
237
238 pub fn add_bytes(&mut self, key: &[u8]) {
239 self.add_bytes_n(key, 1);
240 }
241
242 pub fn add_bytes_n(&mut self, key: &[u8], n: u32) {
247 if n == 0 {
248 return;
249 }
250 let (h1, h2) = self.hashes(key);
251 let mut idxs = [0usize; MAX_DEPTH];
252 let mut min = u32::MAX;
253 for (i, slot) in idxs.iter_mut().take(self.d).enumerate() {
254 let idx = self.cell_index(h1, h2, i);
255 *slot = idx;
256 min = min.min(self.rows[i][idx]);
257 }
258 let floor = min.saturating_add(n);
259 for (i, &idx) in idxs.iter().take(self.d).enumerate() {
260 if self.rows[i][idx] < floor {
261 self.rows[i][idx] = floor;
262 }
263 }
264 self.total = self.total.saturating_add(n as u64);
265 }
266
267 pub fn add_u64(&mut self, key: u64) {
270 self.add_u64_n(key, 1);
271 }
272
273 pub fn add_u64_n(&mut self, key: u64, n: u32) {
274 self.add_bytes_n(&key.to_le_bytes(), n);
275 }
276
277 pub fn estimate(&self, key: &str) -> u32 {
280 self.estimate_bytes(key.as_bytes())
281 }
282
283 pub fn estimate_bytes(&self, key: &[u8]) -> u32 {
284 let (h1, h2) = self.hashes(key);
285 let mut min = u32::MAX;
286 for i in 0..self.d {
287 let idx = self.cell_index(h1, h2, i);
288 min = min.min(self.rows[i][idx]);
289 }
290 min
291 }
292
293 pub fn estimate_u64(&self, key: u64) -> u32 {
294 self.estimate_bytes(&key.to_le_bytes())
295 }
296
297 pub fn estimate_lower_bound(&self, key: &str) -> u32 {
300 self.estimate(key).saturating_sub(self.error_margin())
301 }
302
303 pub fn clear(&mut self) {
306 for row in self.rows.iter_mut() {
307 row.fill(0);
308 }
309 self.total = 0;
310 }
311
312 pub fn to_bytes(&self) -> Vec<u8> {
316 let mut out = Vec::with_capacity(SNAPSHOT_HEADER + self.heap_bytes());
317 out.extend_from_slice(&SNAPSHOT_MAGIC);
318 out.extend_from_slice(&SNAPSHOT_VERSION.to_le_bytes());
319 out.extend_from_slice(&(self.d as u16).to_le_bytes());
320 out.extend_from_slice(&(self.w as u32).to_le_bytes());
321 out.extend_from_slice(&self.seed.to_le_bytes());
322 out.extend_from_slice(&self.total.to_le_bytes());
323 for row in &self.rows {
324 for &cell in row {
325 out.extend_from_slice(&cell.to_le_bytes());
326 }
327 }
328 out
329 }
330
331 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SnapshotError> {
334 if bytes.len() < SNAPSHOT_HEADER {
335 return Err(SnapshotError::Truncated {
336 expected: SNAPSHOT_HEADER,
337 actual: bytes.len(),
338 });
339 }
340 if bytes[..8] != SNAPSHOT_MAGIC {
341 return Err(SnapshotError::BadMagic);
342 }
343 let version = u16::from_le_bytes([bytes[8], bytes[9]]);
344 if version != SNAPSHOT_VERSION {
345 return Err(SnapshotError::UnsupportedVersion(version));
346 }
347 let d = u16::from_le_bytes([bytes[10], bytes[11]]) as usize;
348 let w = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]) as usize;
349 if !(2..=MAX_DEPTH).contains(&d) || w < 2 || !w.is_power_of_two() {
350 return Err(SnapshotError::BadShape { depth: d, width: w });
351 }
352 let expected = SNAPSHOT_HEADER + d * w * 4;
353 if bytes.len() != expected {
354 return Err(SnapshotError::Truncated {
355 expected,
356 actual: bytes.len(),
357 });
358 }
359 let seed = u64::from_le_bytes(bytes[16..24].try_into().expect("8 bytes"));
360 let total = u64::from_le_bytes(bytes[24..32].try_into().expect("8 bytes"));
361
362 let mut sketch = Self::with_seed(d, w, seed);
363 let mut at = SNAPSHOT_HEADER;
364 for row in sketch.rows.iter_mut() {
365 for cell in row.iter_mut() {
366 *cell = u32::from_le_bytes(bytes[at..at + 4].try_into().expect("4 bytes"));
367 at += 4;
368 }
369 }
370 sketch.total = total;
371 Ok(sketch)
372 }
373
374 fn hashes(&self, key: &[u8]) -> (u64, u64) {
375 let h = mix(fnv1a64(key) ^ self.seed);
377 let h1 = h as u32 as u64;
378 let h2 = ((h >> 32) as u32 as u64) | 1;
380 (h1, h2)
381 }
382
383 fn cell_index(&self, h1: u64, h2: u64, i: usize) -> usize {
384 let idx = h1.wrapping_add((i as u64).wrapping_mul(h2));
386 (idx as usize) & self.mask
387 }
388
389 #[cfg(feature = "merge")]
392 pub(crate) fn apply_paired(&mut self, other: &CountMinSketch, sum: bool) {
393 for (i, row) in self.rows.iter_mut().enumerate() {
394 let src = &other.rows[i];
395 for (cell, &s) in row.iter_mut().zip(src.iter()) {
396 *cell = if sum {
397 cell.saturating_add(s)
398 } else if s > *cell {
399 s
400 } else {
401 *cell
402 };
403 }
404 }
405 self.total = self.total.saturating_add(other.total);
406 }
407}
408
409fn fnv1a64(bytes: &[u8]) -> u64 {
410 let mut h = FNV_OFFSET;
411 for &b in bytes {
412 h ^= b as u64;
413 h = h.wrapping_mul(FNV_PRIME);
414 }
415 h
416}
417
418fn mix(mut h: u64) -> u64 {
420 h ^= h >> 30;
421 h = h.wrapping_mul(0xbf58476d1ce4e5b9);
422 h ^= h >> 27;
423 h = h.wrapping_mul(0x94d049bb133111eb);
424 h ^= h >> 31;
425 h
426}
427
428#[cfg(feature = "harness")]
429pub mod recipe;
430
431#[cfg(any(feature = "heavy-hitters", feature = "windowed", feature = "merge"))]
435pub mod features;
436
437#[cfg(feature = "heavy-hitters")]
438pub use features::heavy_hitters::HeavyHitters;
439#[cfg(feature = "merge")]
440pub use features::merge::{MergeError, merge_disjoint_into, merge_into};
441#[cfg(feature = "windowed")]
442pub use features::windowed::WindowedCountMinSketch;
443
444#[cfg(test)]
445#[path = "cms_tests.rs"]
446mod cms_tests;
447
448#[cfg(test)]
449#[path = "sample_app_tests.rs"]
450mod sample_app_tests;