use base64::{Engine as _, engine::general_purpose::STANDARD as B64};
use hdrhistogram::Histogram;
use hdrhistogram::serialization::{Deserializer, Serializer, V2Serializer};
use std::io::Cursor;
use crate::error::{AppError, AppResult, MetricsError};
#[derive(Debug)]
pub struct LatencyHistogram {
hist: Histogram<u64>,
}
impl LatencyHistogram {
pub fn new() -> AppResult<Self> {
let hist = Histogram::<u64>::new(3).map_err(|err| {
AppError::metrics(MetricsError::Histogram {
context: "create histogram",
source: Box::new(err),
})
})?;
Ok(Self { hist })
}
pub fn record(&mut self, latency_ms: u64) -> AppResult<()> {
let value = latency_ms.max(1);
self.hist.record(value).map_err(|err| {
AppError::metrics(MetricsError::Histogram {
context: "record latency",
source: Box::new(err),
})
})
}
pub fn merge(&mut self, other: &LatencyHistogram) -> AppResult<()> {
self.hist.add(&other.hist).map_err(|err| {
AppError::metrics(MetricsError::Histogram {
context: "merge histogram",
source: Box::new(err),
})
})
}
#[must_use]
pub fn percentiles(&self) -> (u64, u64, u64) {
let count = self.count();
if count == 0 {
return (0, 0, 0);
}
(
self.hist.value_at_quantile(0.5),
self.hist.value_at_quantile(0.9),
self.hist.value_at_quantile(0.99),
)
}
#[must_use]
pub fn count(&self) -> u64 {
self.hist.len()
}
pub fn encode_base64(&self) -> AppResult<String> {
let mut buffer = Vec::new();
V2Serializer::new()
.serialize(&self.hist, &mut buffer)
.map_err(|err| {
AppError::metrics(MetricsError::Histogram {
context: "serialize histogram",
source: Box::new(err),
})
})?;
Ok(B64.encode(buffer))
}
pub fn decode_base64(encoded: &str) -> AppResult<Self> {
let bytes = B64.decode(encoded.as_bytes()).map_err(|err| {
AppError::metrics(MetricsError::Histogram {
context: "decode histogram",
source: Box::new(err),
})
})?;
let mut cursor = Cursor::new(bytes);
let hist: Histogram<u64> = Deserializer::new()
.deserialize(&mut cursor)
.map_err(|err| {
AppError::metrics(MetricsError::Histogram {
context: "deserialize histogram",
source: Box::new(err),
})
})?;
Ok(Self { hist })
}
}