174 lines
5.0 KiB
Rust
174 lines
5.0 KiB
Rust
//! Observes metrics in YAML format.
|
|
//!
|
|
//! Metric scopes are used to provide the hierarchy and indentation of metrics. As an example, for
|
|
//! a snapshot with two metrics — `server.msgs_received` and `server.msgs_sent` — we would
|
|
//! expect to see this output:
|
|
//!
|
|
//! ```c
|
|
//! server:
|
|
//! msgs_received: 42
|
|
//! msgs_sent: 13
|
|
//! ```
|
|
//!
|
|
//! If we added another metric — `configuration_reloads` — we would expect to see:
|
|
//!
|
|
//! ```c
|
|
//! configuration_reloads: 2
|
|
//! server:
|
|
//! msgs_received: 42
|
|
//! msgs_sent: 13
|
|
//! ```
|
|
//!
|
|
//! Metrics are sorted alphabetically.
|
|
//!
|
|
//! ## Histograms
|
|
//!
|
|
//! Histograms are rendered with a configurable set of quantiles that are provided when creating an
|
|
//! instance of `YamlBuilder`. They are formatted using human-readable labels when displayed to
|
|
//! the user. For example, 0.0 is rendered as "min", 1.0 as "max", and anything in between using
|
|
//! the common "pXXX" format i.e. a quantile of 0.5 or percentile of 50 would be p50, a quantile of
|
|
//! 0.999 or percentile of 99.9 would be p999, and so on.
|
|
//!
|
|
//! All histograms have the sample count of the histogram provided in the output.
|
|
//!
|
|
//! ```c
|
|
//! connect_time count: 15
|
|
//! connect_time min: 1334
|
|
//! connect_time p50: 1934
|
|
//! connect_time p99: 5330
|
|
//! connect_time max: 139389
|
|
//! ```
|
|
//!
|
|
#![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;
|
|
|
|
/// Builder for [`YamlObserver`].
|
|
pub struct YamlBuilder {
|
|
quantiles: Vec<Quantile>,
|
|
}
|
|
|
|
impl YamlBuilder {
|
|
/// Creates a new [`YamlBuilder`] with default values.
|
|
pub fn new() -> Self {
|
|
let quantiles = parse_quantiles(&[0.0, 0.5, 0.9, 0.95, 0.99, 0.999, 1.0]);
|
|
|
|
Self { quantiles }
|
|
}
|
|
|
|
/// Sets the quantiles to use when rendering histograms.
|
|
///
|
|
/// Quantiles represent a scale of 0 to 1, where percentiles represent a scale of 1 to 100, so
|
|
/// a quantile of 0.99 is the 99th percentile, and a quantile of 0.99 is the 99.9th percentile.
|
|
///
|
|
/// By default, the quantiles will be set to: 0.0, 0.5, 0.9, 0.95, 0.99, 0.999, and 1.0.
|
|
pub fn set_quantiles(mut self, quantiles: &[f64]) -> Self {
|
|
self.quantiles = parse_quantiles(quantiles);
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Builder for YamlBuilder {
|
|
type Output = YamlObserver;
|
|
|
|
fn build(&self) -> Self::Output {
|
|
YamlObserver {
|
|
quantiles: self.quantiles.clone(),
|
|
tree: MetricsTree::default(),
|
|
histos: HashMap::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for YamlBuilder {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// Observess metrics in YAML format.
|
|
pub struct YamlObserver {
|
|
pub(crate) quantiles: Vec<Quantile>,
|
|
pub(crate) tree: MetricsTree,
|
|
pub(crate) histos: HashMap<Key, Histogram<u64>>,
|
|
}
|
|
|
|
impl Observer for YamlObserver {
|
|
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 YamlObserver {
|
|
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 rendered = serde_yaml::to_string(&self.tree).expect("failed to render yaml 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
|
|
}
|