#![deny(missing_docs)]
use hdrhistogram::Histogram;
use metrics_core::{Builder, Drain, Key, Label, Observer};
use metrics_util::{parse_quantiles, MetricsTree, Quantile};
use std::collections::HashMap;
pub struct JsonBuilder {
quantiles: Vec<Quantile>,
pretty: bool,
}
impl JsonBuilder {
pub fn new() -> Self {
let quantiles = parse_quantiles(&[0.0, 0.5, 0.9, 0.95, 0.99, 0.999, 1.0]);
Self {
quantiles,
pretty: false,
}
}
pub fn set_quantiles(mut self, quantiles: &[f64]) -> Self {
self.quantiles = parse_quantiles(quantiles);
self
}
pub fn set_pretty_json(mut self, pretty: bool) -> Self {
self.pretty = pretty;
self
}
}
impl Builder for JsonBuilder {
type Output = JsonObserver;
fn build(&self) -> Self::Output {
JsonObserver {
quantiles: self.quantiles.clone(),
pretty: self.pretty,
tree: MetricsTree::default(),
histos: HashMap::new(),
}
}
}
impl Default for JsonBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct JsonObserver {
pub(crate) quantiles: Vec<Quantile>,
pub(crate) pretty: bool,
pub(crate) tree: MetricsTree,
pub(crate) histos: HashMap<Key, Histogram<u64>>,
}
impl Observer for JsonObserver {
fn observe_counter(&mut self, key: Key, value: u64) {
let (levels, name) = key_to_parts(key);
self.tree.insert_value(levels, name, value);
}
fn observe_gauge(&mut self, key: Key, value: i64) {
let (levels, name) = key_to_parts(key);
self.tree.insert_value(levels, name, value);
}
fn observe_histogram(&mut self, key: Key, values: &[u64]) {
let entry = self
.histos
.entry(key)
.or_insert_with(|| Histogram::<u64>::new(3).expect("failed to create histogram"));
for value in values {
entry
.record(*value)
.expect("failed to observe histogram value");
}
}
}
impl Drain<String> for JsonObserver {
fn drain(&mut self) -> String {
for (key, h) in self.histos.drain() {
let (levels, name) = key_to_parts(key);
let values = hist_to_values(name, h.clone(), &self.quantiles);
self.tree.insert_values(levels, values);
}
let result = if self.pretty {
serde_json::to_string_pretty(&self.tree)
} else {
serde_json::to_string(&self.tree)
};
let rendered = result.expect("failed to render json output");
self.tree.clear();
rendered
}
}
fn key_to_parts(key: Key) -> (Vec<String>, String) {
let (name, labels) = key.into_parts();
let mut parts = name.split('.').map(ToOwned::to_owned).collect::<Vec<_>>();
let name = parts.pop().expect("name didn't have a single part");
let labels = labels
.into_iter()
.map(Label::into_parts)
.map(|(k, v)| format!("{}=\"{}\"", k, v))
.collect::<Vec<_>>()
.join(",");
let label = if labels.is_empty() {
String::new()
} else {
format!("{{{}}}", labels)
};
let fname = format!("{}{}", name, label);
(parts, fname)
}
fn hist_to_values(
name: String,
hist: Histogram<u64>,
quantiles: &[Quantile],
) -> Vec<(String, u64)> {
let mut values = Vec::new();
values.push((format!("{} count", name), hist.len()));
for quantile in quantiles {
let value = hist.value_at_quantile(quantile.value());
values.push((format!("{} {}", name, quantile.label()), value));
}
values
}