diff --git a/CHANGELOG.md b/CHANGELOG.md index fba31be1..a8c43a15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - coop: prefer a time-based budgeting if the telemetry is enabled. - proxy: add `Proxy::try_send_to()` and `Proxy::request_to()`. - telemeter: support gzip. +- telemeter/openmetrics: expose units of registered metrics. ### Changed - telemeter: a new sharded-by-threads storage, it increases perf and dramatically reduces contention. diff --git a/elfo-telemeter/src/protocol.rs b/elfo-telemeter/src/protocol.rs index e9794487..b01e771a 100644 --- a/elfo-telemeter/src/protocol.rs +++ b/elfo-telemeter/src/protocol.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use fxhash::FxHashMap; -use metrics::Key; +use metrics::{Key, Unit}; use sketches_ddsketch::{Config as DDSketchConfig, DDSketch}; use tracing::warn; @@ -26,6 +26,11 @@ pub(crate) struct GetSnapshot; pub(crate) type GaugeEpoch = u64; +pub(crate) struct Description { + pub(crate) details: Option<&'static str>, + pub(crate) unit: Option, +} + /// Actual values of all metrics. #[derive(Default, Clone)] pub struct Snapshot { diff --git a/elfo-telemeter/src/render.rs b/elfo-telemeter/src/render.rs index 084fcc32..ce274abd 100644 --- a/elfo-telemeter/src/render.rs +++ b/elfo-telemeter/src/render.rs @@ -1,13 +1,12 @@ use fxhash::FxHashMap; use metrics::Label; +use self::openmetrics::OpenMetricsRenderer; use crate::{ config::{Config, Quantile}, - protocol::Snapshot, + protocol::{Description, Snapshot}, }; -use self::openmetrics::OpenMetricsRenderer; - mod openmetrics; #[derive(Default)] @@ -19,7 +18,7 @@ pub(crate) struct Renderer { struct RenderOptions<'a> { quantiles: &'a [(Quantile, Label)], - descriptions: &'a FxHashMap, + descriptions: &'a FxHashMap, global_labels: &'a [Label], } @@ -45,7 +44,7 @@ impl Renderer { pub(crate) fn render( &mut self, snapshot: &Snapshot, - descriptions: &FxHashMap, + descriptions: &FxHashMap, ) -> String { let options = RenderOptions { quantiles: &self.quantiles, diff --git a/elfo-telemeter/src/render/openmetrics.rs b/elfo-telemeter/src/render/openmetrics.rs index a5ac7b10..c360254d 100644 --- a/elfo-telemeter/src/render/openmetrics.rs +++ b/elfo-telemeter/src/render/openmetrics.rs @@ -11,7 +11,7 @@ use fxhash::FxHashSet; use metrics::{Key, Label}; use super::RenderOptions; -use crate::protocol::{Distribution, Metrics, Snapshot}; +use crate::protocol::{Description, Distribution, Metrics, Snapshot}; #[derive(Default)] pub(super) struct OpenMetricsRenderer { @@ -47,12 +47,12 @@ fn render( for ((kind, original_name), by_labels) in group_by_name(snapshot) { let name = &*sanitize_name(original_name); + write_type_line(buffer, name, kind); + if let Some(desc) = options.descriptions.get(original_name) { write_help_line(buffer, name, desc); } - write_type_line(buffer, name, kind); - for (meta, value) in by_labels { let actor_group_label = meta .actor_group @@ -114,6 +114,8 @@ fn render( buffer.push('\n'); } + + buffer.push_str("# EOF\n"); } type GroupedData<'a> = BTreeMap<(MetricKind, &'a str), BTreeMap, MetricValue<'a>>>; @@ -145,8 +147,8 @@ fn group_by_name(snapshot: &Snapshot) -> GroupedData<'_> { ); } - for (group, per_group) in &snapshot.groupwise { - for (key, value, kind) in iter_metrics(per_group) { + for (group, groupwise) in &snapshot.groupwise { + for (key, value, kind) in iter_metrics(groupwise) { data.entry((kind, key.name())).or_default().insert( MetricMeta { actor_group: Some(group), @@ -158,8 +160,8 @@ fn group_by_name(snapshot: &Snapshot) -> GroupedData<'_> { } } - for (actor_meta, per_actor) in &snapshot.actorwise { - for (key, value, kind) in iter_metrics(per_actor) { + for (actor_meta, actorwise) in &snapshot.actorwise { + for (key, value, kind) in iter_metrics(actorwise) { data.entry((kind, key.name())).or_default().insert( MetricMeta { actor_group: Some(&actor_meta.group), @@ -191,14 +193,6 @@ fn iter_metrics(metrics: &Metrics) -> impl Iterator( buffer: &mut String, name: &'a str, diff --git a/elfo-telemeter/src/storage.rs b/elfo-telemeter/src/storage.rs index ca3f5cb4..0778c74f 100644 --- a/elfo-telemeter/src/storage.rs +++ b/elfo-telemeter/src/storage.rs @@ -11,7 +11,7 @@ use elfo_core::{coop, scope::Scope, ActorMeta, Addr}; use crate::{ metrics::{Counter, Gauge, GaugeOrigin, Histogram, MetricKind}, - protocol::{Metrics, Snapshot}, + protocol::{Description, Metrics, Snapshot}, }; // === Scopes === @@ -137,7 +137,7 @@ pub(crate) struct Storage { shards: ThreadLocal, // Shared gauge origins between shards. See `Gauge` for more details. gauge_shared: GaugeShared, - descriptions: Mutex>, + descriptions: Mutex>, } #[derive(Default)] @@ -205,23 +205,17 @@ impl Default for Storage { } impl Storage { - pub(crate) fn descriptions(&self) -> MutexGuard<'_, FxHashMap> { + pub(crate) fn descriptions(&self) -> MutexGuard<'_, FxHashMap> { self.descriptions.lock() } - // TODO: use `unit` - pub(crate) fn describe( - &self, - key: &Key, - _unit: Option, - description: Option<&'static str>, - ) { - if let Some(description) = description { - let mut descriptions = self.descriptions.lock(); - if !descriptions.contains_key(key.name().to_string().as_str()) { - descriptions.insert(key.name().to_string(), description); - } + pub(crate) fn describe(&self, key: &Key, unit: Option, details: Option<&'static str>) { + if unit.is_none() && details.is_none() { + return; } + + let mut descriptions = self.descriptions.lock(); + descriptions.insert(key.name().to_string(), Description { details, unit }); } pub(crate) fn upsert(&self, scope: &S::Scope, key: &Key, value: M::Value) diff --git a/examples/examples/usage/main.rs b/examples/examples/usage/main.rs index e7c1cfd8..2a913c64 100644 --- a/examples/examples/usage/main.rs +++ b/examples/examples/usage/main.rs @@ -187,7 +187,7 @@ mod reporter { prelude::*, time::Interval, }; - use metrics::increment_counter; + use metrics::{increment_counter, register_counter, Unit}; use serde::{Deserialize, Serialize}; use crate::protocol::*; @@ -203,6 +203,10 @@ mod reporter { struct SummarizeTick; pub fn new() -> Blueprint { + // Optionally, register metrics to provide additional information. + // It's not required, unregistered metrics are published anyway. + register_counter!("ticks_total", Unit::Count, "Total number of ticks"); + ActorGroup::new().config::().exec(reporter) }