use alice_db::AliceDB;
pub struct AnalyticsSink {
cardinality_db: AliceDB,
quantile_db: AliceDB,
anomaly_db: AliceDB,
}
impl AnalyticsSink {
pub fn open(dir: &str) -> Result<Self, String> {
let cardinality_db = AliceDB::open(format!("{}/cardinality", dir))
.map_err(|e| format!("cardinality db: {}", e))?;
let quantile_db = AliceDB::open(format!("{}/quantile", dir))
.map_err(|e| format!("quantile db: {}", e))?;
let anomaly_db =
AliceDB::open(format!("{}/anomaly", dir)).map_err(|e| format!("anomaly db: {}", e))?;
Ok(Self {
cardinality_db,
quantile_db,
anomaly_db,
})
}
pub fn record_cardinality(&self, timestamp_ms: u64, estimate: f64) {
let _ = self
.cardinality_db
.put(timestamp_ms as i64, estimate as f32);
}
pub fn record_quantile(&self, timestamp_ms: u64, value: f64) {
let _ = self.quantile_db.put(timestamp_ms as i64, value as f32);
}
pub fn record_anomaly(&self, timestamp_ms: u64, count: u32) {
let _ = self.anomaly_db.put(timestamp_ms as i64, count as f32);
}
pub fn record_cardinality_batch(&self, entries: &[(u64, f64)]) {
let data: Vec<(i64, f32)> = entries
.iter()
.map(|&(ts, est)| (ts as i64, est as f32))
.collect();
let _ = self.cardinality_db.put_batch(&data);
}
pub fn query_cardinality(&self, from_ms: u64, to_ms: u64) -> Vec<(u64, f64)> {
self.cardinality_db
.scan(from_ms as i64, to_ms as i64)
.unwrap_or_default()
.into_iter()
.map(|(ts, v)| (ts as u64, v as f64))
.collect()
}
pub fn query_quantile(&self, from_ms: u64, to_ms: u64) -> Vec<(u64, f64)> {
self.quantile_db
.scan(from_ms as i64, to_ms as i64)
.unwrap_or_default()
.into_iter()
.map(|(ts, v)| (ts as u64, v as f64))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analytics_sink_open() {
let dir = "/tmp/alice_analytics_test";
let result = AnalyticsSink::open(dir);
if result.is_ok() {
let sink = result.unwrap();
sink.record_cardinality(1000, 42.0);
let data = sink.query_cardinality(0, 2000);
assert!(!data.is_empty());
}
}
}