use fnv::FnvBuildHasher;
use hashbrown::HashMap;
use hdrhistogram::Histogram as HdrHistogram;
use std::{
fmt::{self, Display},
hash::Hash,
marker::PhantomData,
};
pub mod counter;
pub mod gauge;
pub mod histogram;
pub(crate) use self::{counter::Counter, gauge::Gauge, histogram::Histogram};
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum Facet<T> {
Count(T),
Gauge(T),
TimingPercentile(T),
ValuePercentile(T),
}
#[derive(Debug)]
pub(crate) enum Sample<T> {
Timing(T, u64, u64, u64),
Count(T, i64),
Value(T, u64),
}
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub(crate) struct ScopedKey<T: Clone + Eq + Hash + Display>(usize, T);
impl<T: Clone + Eq + Hash + Display> ScopedKey<T> {
pub(crate) fn id(&self) -> usize { self.0 }
pub(crate) fn into_string_scoped(self, scope: String) -> StringScopedKey<T> { StringScopedKey(scope, self.1) }
}
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub(crate) struct StringScopedKey<T: Clone + Eq + Hash + Display>(String, T);
impl<T: Clone + Hash + Eq + Display> Display for StringScopedKey<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.0.is_empty() {
write!(f, "{}", self.1)
} else {
write!(f, "{}.{}", self.0, self.1)
}
}
}
impl<T: Clone + Eq + Hash + Display> Facet<T> {
pub(crate) fn into_scoped(self, scope_id: usize) -> Facet<ScopedKey<T>> {
match self {
Facet::Count(key) => Facet::Count(ScopedKey(scope_id, key)),
Facet::Gauge(key) => Facet::Gauge(ScopedKey(scope_id, key)),
Facet::TimingPercentile(key) => Facet::TimingPercentile(ScopedKey(scope_id, key)),
Facet::ValuePercentile(key) => Facet::ValuePercentile(ScopedKey(scope_id, key)),
}
}
}
impl<T: Clone + Eq + Hash + Display> Sample<T> {
pub(crate) fn into_scoped(self, scope_id: usize) -> Sample<ScopedKey<T>> {
match self {
Sample::Timing(key, start, end, count) => Sample::Timing(ScopedKey(scope_id, key), start, end, count),
Sample::Count(key, value) => Sample::Count(ScopedKey(scope_id, key), value),
Sample::Value(key, value) => Sample::Value(ScopedKey(scope_id, key), value),
}
}
}
#[derive(Clone)]
pub struct Percentile(pub String, pub f64);
pub fn default_percentiles() -> Vec<Percentile> {
let mut p = Vec::new();
p.push(Percentile("min".to_owned(), 0.0));
p.push(Percentile("p50".to_owned(), 50.0));
p.push(Percentile("p90".to_owned(), 90.0));
p.push(Percentile("p99".to_owned(), 99.0));
p.push(Percentile("p999".to_owned(), 99.9));
p.push(Percentile("max".to_owned(), 100.0));
p
}
#[derive(Default)]
pub struct Snapshot {
signed_data: HashMap<String, i64, FnvBuildHasher>,
unsigned_data: HashMap<String, u64, FnvBuildHasher>,
}
impl Snapshot {
pub fn count(&self, key: &str) -> Option<&i64> {
let fkey = format!("{}_count", key);
self.signed_data.get(&fkey)
}
pub fn value(&self, key: &str) -> Option<&u64> {
let fkey = format!("{}_value", key);
self.unsigned_data.get(&fkey)
}
pub fn timing_percentile(&self, key: &str, percentile: Percentile) -> Option<&u64> {
let fkey = format!("{}_ns_{}", key, percentile.0);
self.unsigned_data.get(&fkey)
}
pub fn value_percentile(&self, key: &str, percentile: Percentile) -> Option<&u64> {
let fkey = format!("{}_value_{}", key, percentile.0);
self.unsigned_data.get(&fkey)
}
pub fn get_signed_data(&self) -> Vec<(String, i64)> {
self.signed_data.iter().map(|(a, b)| (a.clone(), *b)).collect()
}
pub fn get_unsigned_data(&self) -> Vec<(String, u64)> {
self.unsigned_data.iter().map(|(a, b)| (a.clone(), *b)).collect()
}
}
pub(crate) struct SnapshotBuilder<T> {
inner: Snapshot,
_marker: PhantomData<T>,
}
#[allow(dead_code)]
impl<T: Send + Eq + Hash + Send + Display + Clone> SnapshotBuilder<T> {
pub(crate) fn new() -> SnapshotBuilder<T> {
SnapshotBuilder {
inner: Default::default(),
_marker: PhantomData,
}
}
pub(crate) fn set_count(&mut self, key: T, value: i64) {
let fkey = format!("{}_count", key);
self.inner.signed_data.insert(fkey, value);
}
pub(crate) fn set_value(&mut self, key: T, value: u64) {
let fkey = format!("{}_value", key);
self.inner.unsigned_data.insert(fkey, value);
}
pub(crate) fn set_timing_percentiles(&mut self, key: T, h: HdrHistogram<u64>, percentiles: &[Percentile]) {
for percentile in percentiles {
let fkey = format!("{}_ns_{}", key, percentile.0);
let value = h.value_at_percentile(percentile.1);
self.inner.unsigned_data.insert(fkey, value);
}
}
pub(crate) fn set_value_percentiles(&mut self, key: T, h: HdrHistogram<u64>, percentiles: &[Percentile]) {
for percentile in percentiles {
let fkey = format!("{}_value_{}", key, percentile.0);
let value = h.value_at_percentile(percentile.1);
self.inner.unsigned_data.insert(fkey, value);
}
}
pub(crate) fn count(&self, key: &T) -> Option<&i64> {
let fkey = format!("{}_count", key);
self.inner.signed_data.get(&fkey)
}
pub(crate) fn value(&self, key: &T) -> Option<&u64> {
let fkey = format!("{}_value", key);
self.inner.unsigned_data.get(&fkey)
}
pub(crate) fn timing_percentile(&self, key: &T, percentile: Percentile) -> Option<&u64> {
let fkey = format!("{}_ns_{}", key, percentile.0);
self.inner.unsigned_data.get(&fkey)
}
pub(crate) fn value_percentile(&self, key: &T, percentile: Percentile) -> Option<&u64> {
let fkey = format!("{}_value_{}", key, percentile.0);
self.inner.unsigned_data.get(&fkey)
}
pub(crate) fn into_inner(self) -> Snapshot { self.inner }
}
#[cfg(test)]
mod tests {
use super::{Percentile, SnapshotBuilder};
use hdrhistogram::Histogram;
#[test]
fn test_snapshot_simple_set_and_get() {
let key = "ok".to_owned();
let mut snapshot = SnapshotBuilder::new();
snapshot.set_count(key.clone(), 1);
snapshot.set_value(key.clone(), 42);
assert_eq!(snapshot.count(&key).unwrap(), &1);
assert_eq!(snapshot.value(&key).unwrap(), &42);
}
#[test]
fn test_snapshot_percentiles() {
let mut snapshot = SnapshotBuilder::new();
{
let mut h1 = Histogram::<u64>::new_with_bounds(1, u64::max_value(), 3).unwrap();
h1.saturating_record(500_000);
h1.saturating_record(750_000);
h1.saturating_record(1_000_000);
h1.saturating_record(1_250_000);
let tkey = "ok".to_owned();
let mut tpercentiles = Vec::new();
tpercentiles.push(Percentile("min".to_owned(), 0.0));
tpercentiles.push(Percentile("p50".to_owned(), 50.0));
tpercentiles.push(Percentile("p99".to_owned(), 99.0));
tpercentiles.push(Percentile("max".to_owned(), 100.0));
snapshot.set_timing_percentiles(tkey.clone(), h1, &tpercentiles);
let min_tpercentile = snapshot.timing_percentile(&tkey, tpercentiles[0].clone());
let p50_tpercentile = snapshot.timing_percentile(&tkey, tpercentiles[1].clone());
let p99_tpercentile = snapshot.timing_percentile(&tkey, tpercentiles[2].clone());
let max_tpercentile = snapshot.timing_percentile(&tkey, tpercentiles[3].clone());
let fake_tpercentile = snapshot.timing_percentile(&tkey, Percentile("fake".to_owned(), 63.0));
assert!(min_tpercentile.is_some());
assert!(p50_tpercentile.is_some());
assert!(p99_tpercentile.is_some());
assert!(max_tpercentile.is_some());
assert!(fake_tpercentile.is_none());
}
{
let mut h2 = Histogram::<u64>::new_with_bounds(1, u64::max_value(), 3).unwrap();
h2.saturating_record(500_000);
h2.saturating_record(750_000);
h2.saturating_record(1_000_000);
h2.saturating_record(1_250_000);
let vkey = "ok".to_owned();
let mut vpercentiles = Vec::new();
vpercentiles.push(Percentile("min".to_owned(), 0.0));
vpercentiles.push(Percentile("p50".to_owned(), 50.0));
vpercentiles.push(Percentile("p99".to_owned(), 99.0));
vpercentiles.push(Percentile("max".to_owned(), 100.0));
snapshot.set_value_percentiles(vkey.clone(), h2, &vpercentiles);
let min_vpercentile = snapshot.value_percentile(&vkey, vpercentiles[0].clone());
let p50_vpercentile = snapshot.value_percentile(&vkey, vpercentiles[1].clone());
let p99_vpercentile = snapshot.value_percentile(&vkey, vpercentiles[2].clone());
let max_vpercentile = snapshot.value_percentile(&vkey, vpercentiles[3].clone());
let fake_vpercentile = snapshot.value_percentile(&vkey, Percentile("fake".to_owned(), 63.0));
assert!(min_vpercentile.is_some());
assert!(p50_vpercentile.is_some());
assert!(p99_vpercentile.is_some());
assert!(max_vpercentile.is_some());
assert!(fake_vpercentile.is_none());
}
}
}