Skip to content

feat: non-global metrics usage, add metrics sets #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 218 additions & 27 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,48 +178,68 @@ impl Gauge {
}

/// Description of a group of metrics.
pub trait Metric:
Default + struct_iterable::Iterable + Sized + std::fmt::Debug + 'static + Send + Sync
{
pub trait Metric: struct_iterable::Iterable + std::fmt::Debug + 'static + Send + Sync {
/// Initializes this metric group.
#[cfg(feature = "metrics")]
fn new(registry: &mut prometheus_client::registry::Registry) -> Self {
let sub_registry = registry.sub_registry_with_prefix(Self::name());
fn register(&self, registry: &mut prometheus_client::registry::Registry) {
let sub_registry = registry.sub_registry_with_prefix(self.name());

let this = Self::default();
for (metric, counter) in this.iter() {
for (metric, counter) in self.iter() {
if let Some(counter) = counter.downcast_ref::<Counter>() {
sub_registry.register(metric, counter.description, counter.counter.clone());
}
}
this
}

/// Initializes this metric group.
#[cfg(not(feature = "metrics"))]
fn new(_: &mut ()) -> Self {
Self::default()
}

/// The name of this metric group.
fn name() -> &'static str;
fn name(&self) -> &'static str;

/// Access to this metrics group to record a metric.
/// Only records if this metric is registered in the global registry.
#[cfg(feature = "metrics")]
fn with_metric<T, F: FnOnce(&Self) -> T>(f: F) {
Self::try_get().map(f);
/// Returns the metrics descriptions.
fn describe(&self) -> Vec<MetricItem> {
let mut res = vec![];
for (metric, counter) in self.iter() {
if let Some(counter) = counter.downcast_ref::<Counter>() {
res.push(MetricItem {
name: metric.to_string(),
description: counter.description.to_string(),
r#type: "counter".to_string(),
});
}
}
res
}
}

/// Access to this metrics group to record a metric.
#[cfg(not(feature = "metrics"))]
fn with_metric<T, F: FnOnce(&Self) -> T>(_f: F) {
// nothing to do
/// Extension methods for types implementing [`Metric`].
///
/// This contains non-dyn-compatible methods, which is why they can't live on the [`Metric`] trait.
pub trait MetricExt: Metric + Default {
/// Create a new instance and register with a registry.
#[cfg(feature = "metrics")]
fn new(registry: &mut prometheus_client::registry::Registry) -> Self {
let m = Self::default();
m.register(registry);
m
}
}

impl<T> MetricExt for T where T: Metric + Default {}

/// Attempts to get the current metric from the global registry.
fn try_get() -> Option<&'static Self> {
Core::get().and_then(|c| c.get_collector::<Self>())
/// Trait for a set of structs implementing [`Metric`].
pub trait MetricSet {
/// Returns an iterator of references to structs implmenting [`Metric`].
fn iter<'a>(&'a self) -> impl IntoIterator<Item = &'a dyn Metric>;

/// Returns the name of this metrics group set.
fn name(&self) -> &'static str;

/// Register all metrics groups in this set onto a prometheus client registry.
#[cfg(feature = "metrics")]
fn register_all(&self, registry: &mut prometheus_client::registry::Registry) {
let sub_registry = registry.sub_registry_with_prefix(self.name());
for metric in self.iter() {
metric.register(sub_registry)
}
}
}

Expand Down Expand Up @@ -278,8 +298,179 @@ pub trait MetricType {
fn name(&self) -> &'static str;
}

/// Returns the metric item representation.
#[derive(Debug, Clone)]
pub struct MetricItem {
/// The name of the metric.
pub name: String,
/// The description of the metric.
pub description: String,
/// The type of the metric.
pub r#type: String,
}

/// Interface for all distribution based metrics.
pub trait HistogramType {
/// Returns the name of the metric
fn name(&self) -> &'static str;
}

#[cfg(test)]
mod tests {
use struct_iterable::Iterable;

use super::*;

#[derive(Debug, Clone, Iterable)]
pub struct FooMetrics {
pub metric_a: Counter,
pub metric_b: Counter,
}

impl Default for FooMetrics {
fn default() -> Self {
Self {
metric_a: Counter::new("metric_a"),
metric_b: Counter::new("metric_b"),
}
}
}

impl Metric for FooMetrics {
fn name(&self) -> &'static str {
"foo"
}
}

#[derive(Debug, Clone, Iterable)]
pub struct BarMetrics {
pub count: Counter,
}

impl Default for BarMetrics {
fn default() -> Self {
Self {
count: Counter::new("Bar Count"),
}
}
}

impl Metric for BarMetrics {
fn name(&self) -> &'static str {
"bar"
}
}

#[derive(Debug, Clone, Default)]
struct CombinedMetrics {
foo: FooMetrics,
bar: BarMetrics,
}

impl MetricSet for CombinedMetrics {
fn name(&self) -> &'static str {
"combined"
}

fn iter<'a>(&'a self) -> impl IntoIterator<Item = &'a dyn Metric> {
[&self.foo as &dyn Metric, &self.bar as &dyn Metric]
}
}

#[cfg(feature = "metrics")]
#[test]
fn test_metric_description() -> Result<(), Box<dyn std::error::Error>> {
let metrics = FooMetrics::default();
let items = metrics.describe();
assert_eq!(items.len(), 2);
assert_eq!(items[0].name, "metric_a");
assert_eq!(items[0].description, "metric_a");
assert_eq!(items[0].r#type, "counter");
assert_eq!(items[1].name, "metric_b");
assert_eq!(items[1].description, "metric_b");
assert_eq!(items[1].r#type, "counter");

Ok(())
}

#[cfg(feature = "metrics")]
#[test]
fn test_solo_registry() -> Result<(), Box<dyn std::error::Error>> {
let mut registry = Registry::default();
let metrics = FooMetrics::default();
metrics.register(&mut registry);

metrics.metric_a.inc();
metrics.metric_b.inc_by(2);
metrics.metric_b.inc_by(3);
assert_eq!(metrics.metric_a.get(), 1);
assert_eq!(metrics.metric_b.get(), 5);
metrics.metric_a.set(0);
metrics.metric_b.set(0);
assert_eq!(metrics.metric_a.get(), 0);
assert_eq!(metrics.metric_b.get(), 0);
metrics.metric_a.inc_by(5);
metrics.metric_b.inc_by(2);
assert_eq!(metrics.metric_a.get(), 5);
assert_eq!(metrics.metric_b.get(), 2);

let exp = "# HELP foo_metric_a metric_a.
# TYPE foo_metric_a counter
foo_metric_a_total 5
# HELP foo_metric_b metric_b.
# TYPE foo_metric_b counter
foo_metric_b_total 2
# EOF
";
let mut enc = String::new();
encode(&mut enc, &registry).expect("writing to string always works");

assert_eq!(enc, exp);
Ok(())
}

#[cfg(feature = "metrics")]
#[test]
fn test_metric_sets() {
let metrics = CombinedMetrics::default();
metrics.foo.metric_a.inc();
metrics.bar.count.inc_by(10);

let mut collected = vec![];
// manual collection and iteration if not using prometheus_client
for group in metrics.iter() {
for (name, metric) in group.iter() {
if let Some(counter) = metric.downcast_ref::<Counter>() {
collected.push((group.name(), name, counter.description, counter.get()));
}
}
}
assert_eq!(
collected,
vec![
("foo", "metric_a", "metric_a", 1),
("foo", "metric_b", "metric_b", 0),
("bar", "count", "Bar Count", 10),
]
);

// automatic collection and encoding with prometheus_client
let mut registry = Registry::default();
metrics.register_all(&mut registry);
let exp = "# HELP combined_foo_metric_a metric_a.
# TYPE combined_foo_metric_a counter
combined_foo_metric_a_total 1
# HELP combined_foo_metric_b metric_b.
# TYPE combined_foo_metric_b counter
combined_foo_metric_b_total 0
# HELP combined_bar_count Bar Count.
# TYPE combined_bar_count counter
combined_bar_count_total 10
# EOF
";
let mut enc = String::new();
encode(&mut enc, &registry).expect("writing to string always works");

assert_eq!(enc, exp);
}
}
16 changes: 12 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,19 @@ pub enum Error {
#[macro_export]
macro_rules! inc {
($m:ty, $f:ident) => {
<$m as $crate::core::Metric>::with_metric(|m| m.$f.inc());
if let Some(m) = $crate::core::Core::get().and_then(|c| c.get_collector::<$m>()) {
m.$f.inc();
}
};
}

/// Increments the given counter or gauge by `n`.
#[macro_export]
macro_rules! inc_by {
($m:ty, $f:ident, $n:expr) => {
<$m as $crate::core::Metric>::with_metric(|m| m.$f.inc_by($n));
if let Some(m) = $crate::core::Core::get().and_then(|c| c.get_collector::<$m>()) {
m.$f.inc_by($n);
}
};
}

Expand All @@ -56,15 +60,19 @@ macro_rules! set {
#[macro_export]
macro_rules! dec {
($m:ty, $f:ident) => {
<$m as $crate::core::Metric>::with_metric(|m| m.$f.dec());
if let Some(m) = $crate::core::Core::get().and_then(|c| c.get_collector::<$m>()) {
m.$f.dec();
}
};
}

/// Decrements the given gauge `n`.
#[macro_export]
macro_rules! dec_by {
($m:ty, $f:ident, $n:expr) => {
<$m as $crate::core::Metric>::with_metric(|m| m.$f.dec_by($n));
if let Some(m) = $crate::core::Core::get().and_then(|c| c.get_collector::<$m>()) {
m.$f.dec_by($n);
}
};
}

Expand Down
6 changes: 4 additions & 2 deletions src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@
//! }
//!
//! impl Metric for Metrics {
//! fn name() -> &'static str {
//! fn name(&self) -> &'static str {
//! "my_metrics"
//! }
//! }
//!
//! Core::init(|reg, metrics| {
//! metrics.insert(Metrics::new(reg));
//! let m = Metrics::default();
//! m.register(reg);
//! metrics.insert(m);
//! });
//!
//! inc_by!(Metrics, things_added, 2);
Expand Down
Loading