From ab69b10b536df099025ef2afbb1f5c1f3a12972f Mon Sep 17 00:00:00 2001 From: Frando Date: Mon, 28 Apr 2025 10:31:29 +0200 Subject: [PATCH 1/4] wip --- Cargo.lock | 89 +-------------- Cargo.toml | 12 +- iroh-metrics-derive/src/lib.rs | 35 ++---- src/base.rs | 199 ++++++++++++++++----------------- src/encoding.rs | 119 ++++++++++++++++++++ src/iterable.rs | 6 +- src/lib.rs | 23 ++-- src/metrics.rs | 96 ++++++++-------- src/registry.rs | 115 +++++++++++++++++++ src/service.rs | 43 ++----- src/static_core.rs | 51 +++------ 11 files changed, 436 insertions(+), 352 deletions(-) create mode 100644 src/encoding.rs create mode 100644 src/registry.rs diff --git a/Cargo.lock b/Cargo.lock index ccea75a..35a21e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,12 +44,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - [[package]] name = "bumpalo" version = "3.16.0" @@ -100,12 +94,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dtoa" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" - [[package]] name = "erased_set" version = "0.8.0" @@ -442,7 +430,7 @@ dependencies = [ "hyper", "hyper-util", "iroh-metrics-derive", - "prometheus-client", + "itoa", "reqwest", "serde", "thiserror", @@ -462,9 +450,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" @@ -488,16 +476,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.22" @@ -551,29 +529,6 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - [[package]] name = "percent-encoding" version = "2.3.1" @@ -610,29 +565,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prometheus-client" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" -dependencies = [ - "dtoa", - "itoa", - "parking_lot", - "prometheus-client-derive-encode", -] - -[[package]] -name = "prometheus-client-derive-encode" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "quinn" version = "0.11.6" @@ -724,15 +656,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags", -] - [[package]] name = "reqwest" version = "0.12.9" @@ -850,12 +773,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "serde" version = "1.0.216" diff --git a/Cargo.toml b/Cargo.toml index e02d413..a976740 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,13 +30,12 @@ unexpected_cfgs = { level = "warn", check-cfg = ["cfg(iroh_docsrs)"] } unused-async = "warn" [dependencies] +iroh-metrics-derive = { path = "./iroh-metrics-derive", version = "0.1.0" } +itoa = "1" serde = { version = "1", features = ["derive"] } thiserror = "2.0.6" tracing = "0.1" -# metrics feature -prometheus-client = { version = "0.22", optional = true } - # static_core feature erased_set = { version = "0.8", optional = true } @@ -47,9 +46,6 @@ hyper-util = { version = "0.1.1", features = ["tokio"], optional = true } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"], optional = true } tokio = { version = "1", features = ["rt", "net", "fs"], optional = true } -# derive feature -iroh-metrics-derive = { path = "./iroh-metrics-derive", version = "0.1.0" } - [dev-dependencies] tokio = { version = "1", features = ["io-util", "sync", "rt", "net", "fs", "macros", "time", "test-util"] } @@ -57,9 +53,7 @@ tokio = { version = "1", features = ["io-util", "sync", "rt", "net", "fs", "macr default = ["metrics"] # Enables counters and other metrics being tracked. # If disabled, all counters return 0. Macros like `inc!` will do nothing. -metrics = [ - "dep:prometheus-client", -] +metrics = [] # Enables functionality to run a local metrics server that current metrics # are served at in prometheus format. # Pulls in quite a few libraries to make exposing an HTTP server possible. diff --git a/iroh-metrics-derive/src/lib.rs b/iroh-metrics-derive/src/lib.rs index efec92d..2eedf98 100644 --- a/iroh-metrics-derive/src/lib.rs +++ b/iroh-metrics-derive/src/lib.rs @@ -31,8 +31,13 @@ fn expand_iterable(input: &DeriveInput) -> Result Some((#ident_str, &self.#ident as &dyn ::iroh_metrics::Metric)), + #i => Some(::iroh_metrics::MetricItem::new(#ident_str, #help, &self.#ident as &dyn ::iroh_metrics::Metric)), }); } @@ -42,7 +47,7 @@ fn expand_iterable(input: &DeriveInput) -> Result Option<(&'static str, &dyn ::iroh_metrics::Metric)> { + fn field_ref(&self, n: usize) -> Option<::iroh_metrics::MetricItem<'_>> { match n { #match_arms _ => None, @@ -53,37 +58,13 @@ fn expand_iterable(input: &DeriveInput) -> Result Result { - let (name, fields) = parse_named_struct(input)?; - - let mut field_defaults = quote! {}; - for field in fields { - let field_name = field.ident.as_ref().unwrap(); - let ty = &field.ty; - let attr = parse_metrics_attr(&field.attrs)?; - let help = attr - .help - .or_else(|| parse_doc_first_line(&field.attrs)) - .unwrap_or_else(|| field_name.to_string()); - - field_defaults.extend(quote! { - #field_name: #ty::new(#help), - }); - } - + let (name, _fields) = parse_named_struct(input)?; let attr = parse_metrics_attr(&input.attrs)?; let name_str = attr .name .unwrap_or_else(|| name.to_string().to_snake_case()); Ok(quote! { - impl ::std::default::Default for #name { - fn default() -> Self { - Self { - #field_defaults - } - } - } - impl ::iroh_metrics::MetricsGroup for #name { fn name(&self) -> &'static str { #name_str diff --git a/src/base.rs b/src/base.rs index 4284c62..67f9140 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,83 +1,62 @@ use std::any::Any; - -#[cfg(feature = "metrics")] -pub use prometheus_client::registry::Registry; +use std::sync::Arc; use crate::{ iterable::{FieldIter, IntoIterable, Iterable}, - Metric, + Metric, MetricType, MetricValue, }; /// Trait for structs containing metric items. pub trait MetricsGroup: Any + Iterable + IntoIterable + std::fmt::Debug + 'static + Send + Sync { - /// Registers all metric items in this metrics group to a [`prometheus_client::registry::Registry`]. - #[cfg(feature = "metrics")] - fn register(&self, registry: &mut prometheus_client::registry::Registry) { - use crate::{Counter, Gauge}; - let sub_registry = registry.sub_registry_with_prefix(self.name()); - for item in self.iter() { - // Remove trailing dot, becausse `Registry::register` adds it automatically. - let help = item.help().trim_end_matches('.'); - if let Some(counter) = item.as_any().downcast_ref::() { - sub_registry.register(item.name(), help, counter.counter.clone()); - } - if let Some(gauge) = item.as_any().downcast_ref::() { - sub_registry.register(item.name(), help, gauge.gauge.clone()); - } - } - } - /// Returns the name of this metrics group. fn name(&self) -> &'static str; /// Returns an iterator over all metric items with their values and helps. - fn iter(&self) -> MetricsIter { - MetricsIter { - inner: self.field_iter(), - } - } -} - -/// Iterator over metric items. -/// -/// Returned from [`MetricsGroup::iter`]. -#[derive(Debug)] -pub struct MetricsIter<'a> { - inner: FieldIter<'a>, -} - -impl<'a> Iterator for MetricsIter<'a> { - type Item = MetricItem<'a>; - fn next(&mut self) -> Option { - let (name, metric) = self.inner.next()?; - Some(MetricItem { name, metric }) - } - - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() + fn iter(&self) -> FieldIter { + self.field_iter() } } /// A metric item with its current value. +/// +/// Returned from [`MetricsGroup::iter`] and [`MetricsGroupSet::iter`]. #[derive(Debug, Clone, Copy)] pub struct MetricItem<'a> { name: &'static str, + help: &'static str, metric: &'a dyn Metric, } -impl MetricItem<'_> { +impl<'a> MetricItem<'a> { + /// Returns a new metric item. + pub fn new(name: &'static str, help: &'static str, metric: &'a dyn Metric) -> Self { + Self { name, help, metric } + } /// Returns the name of this metric item. pub fn name(&self) -> &'static str { self.name } -} -impl<'a> std::ops::Deref for MetricItem<'a> { - type Target = &'a dyn Metric; - fn deref(&self) -> &Self::Target { - &self.metric + /// Returns the help of this metric item. + pub fn help(&self) -> &'static str { + self.help + } + + /// Returns the [`MetricType`] for this item. + pub fn r#type(&self) -> MetricType { + self.metric.r#type() + } + + /// Returns the current value of this item. + pub fn value(&self) -> MetricValue { + self.metric.value() + } + + /// Returns the inner metric as [`Any`], for further downcasting to concrete metric types. + pub fn as_any(&self) -> &dyn Any { + self.metric.as_any() } } @@ -86,24 +65,19 @@ pub trait MetricsGroupSet { /// Returns the name of this metrics group set. fn name(&self) -> &'static str; + /// Returns an iterator over owned clones of the [`MetricsGroup`] in this struct. + fn groups_cloned(&self) -> impl Iterator>; + + /// Returns an iterator over references to the [`MetricsGroup`] in this struct. + fn groups(&self) -> impl Iterator; + /// Returns an iterator over all metrics in this metrics group set. /// /// The iterator yields tuples of `(&str, MetricItem)`. The `&str` is the group name. - fn iter(&self) -> impl Iterator)> { + fn iter(&self) -> impl Iterator)> + '_ { self.groups() .flat_map(|group| group.iter().map(|item| (group.name(), item))) } - - /// Returns an iterator over the [`MetricsGroup`] in this struct. - fn groups(&self) -> impl Iterator; - - /// Register all metrics groups in this set onto a prometheus client registry. - #[cfg(feature = "metrics")] - fn register(&self, registry: &mut prometheus_client::registry::Registry) { - for group in self.groups() { - group.register(registry) - } - } } /// Ensure metrics can be used without `metrics` feature. @@ -114,7 +88,7 @@ mod tests { #[test] fn test() { - let counter = Counter::new("foo"); + let counter = Counter::new(); counter.inc(); assert_eq!(counter.get(), 0); } @@ -124,9 +98,9 @@ mod tests { #[cfg(all(test, feature = "metrics"))] mod tests { use super::*; - use crate::{iterable::Iterable, Counter, Gauge, MetricType}; + use crate::{iterable::Iterable, Counter, Gauge, MetricType, MetricsSource, Registry}; - #[derive(Debug, Clone, Iterable)] + #[derive(Debug, Iterable)] pub struct FooMetrics { pub metric_a: Counter, pub metric_b: Counter, @@ -135,8 +109,8 @@ mod tests { impl Default for FooMetrics { fn default() -> Self { Self { - metric_a: Counter::new("metric_a"), - metric_b: Counter::new("metric_b"), + metric_a: Counter::new(), + metric_b: Counter::new(), } } } @@ -147,29 +121,22 @@ mod tests { } } - #[derive(Debug, Clone, Iterable)] + #[derive(Debug, Default, Iterable)] pub struct BarMetrics { + /// Bar Count pub count: Counter, } - impl Default for BarMetrics { - fn default() -> Self { - Self { - count: Counter::new("Bar Count"), - } - } - } - impl MetricsGroup for BarMetrics { fn name(&self) -> &'static str { "bar" } } - #[derive(Debug, Clone, Default)] + #[derive(Debug, Default)] struct CombinedMetrics { - foo: FooMetrics, - bar: BarMetrics, + foo: Arc, + bar: Arc, } impl MetricsGroupSet for CombinedMetrics { @@ -177,10 +144,18 @@ mod tests { "combined" } + fn groups_cloned(&self) -> impl Iterator> { + [ + self.foo.clone() as Arc, + self.bar.clone() as Arc, + ] + .into_iter() + } + fn groups(&self) -> impl Iterator { [ - &self.foo as &dyn MetricsGroup, - &self.bar as &dyn MetricsGroup, + &*self.foo as &dyn MetricsGroup, + &*self.bar as &dyn MetricsGroup, ] .into_iter() } @@ -203,11 +178,9 @@ mod tests { #[test] fn test_solo_registry() -> Result<(), Box> { - use prometheus_client::{encoding::text::encode, registry::Registry}; - let mut registry = Registry::default(); - let metrics = FooMetrics::default(); - metrics.register(&mut registry); + let metrics = Arc::new(FooMetrics::default()); + registry.register(metrics.clone()); metrics.metric_a.inc(); metrics.metric_b.inc_by(2); @@ -231,17 +204,13 @@ foo_metric_a_total 5 foo_metric_b_total 2 # EOF "; - let mut enc = String::new(); - encode(&mut enc, ®istry).expect("writing to string always works"); - + let enc = registry.encode_openmetrics_to_string()?; assert_eq!(enc, exp); Ok(()) } #[test] fn test_metric_sets() { - use prometheus_client::{encoding::text::encode, registry::Registry}; - let metrics = CombinedMetrics::default(); metrics.foo.metric_a.inc(); metrics.bar.count.inc_by(10); @@ -277,32 +246,54 @@ foo_metric_b_total 2 ] ); - // automatic collection and encoding with prometheus_client + // automatic collection and encoding with a registry let mut registry = Registry::default(); - let sub = registry.sub_registry_with_prefix("combined"); - metrics.register(sub); - let exp = "# HELP combined_foo_metric_a metric_a. + let sub = registry.sub_registry_with_prefix("boo"); + sub.register_all(&metrics); + let exp = "# HELP boo_foo_metric_a metric_a. +# TYPE boo_foo_metric_a counter +boo_foo_metric_a_total 1 +# HELP boo_foo_metric_b metric_b. +# TYPE boo_foo_metric_b counter +boo_foo_metric_b_total 0 +# HELP boo_bar_count Bar Count. +# TYPE boo_bar_count counter +boo_bar_count_total 10 +# EOF +"; + assert_eq!(registry.encode_openmetrics_to_string().unwrap(), exp); + + let sub = registry.sub_registry_with_labels([("x", "y")]); + sub.register_all_prefixed(&metrics); + let exp = r#"# HELP boo_foo_metric_a metric_a. +# TYPE boo_foo_metric_a counter +boo_foo_metric_a_total 1 +# HELP boo_foo_metric_b metric_b. +# TYPE boo_foo_metric_b counter +boo_foo_metric_b_total 0 +# HELP boo_bar_count Bar Count. +# TYPE boo_bar_count counter +boo_bar_count_total 10 +# HELP combined_foo_metric_a metric_a. # TYPE combined_foo_metric_a counter -combined_foo_metric_a_total 1 +combined_foo_metric_a_total{x="y"} 1 # HELP combined_foo_metric_b metric_b. # TYPE combined_foo_metric_b counter -combined_foo_metric_b_total 0 +combined_foo_metric_b_total{x="y"} 0 # HELP combined_bar_count Bar Count. # TYPE combined_bar_count counter -combined_bar_count_total 10 +combined_bar_count_total{x="y"} 10 # EOF -"; - let mut enc = String::new(); - encode(&mut enc, ®istry).expect("writing to string always works"); +"#; - assert_eq!(enc, exp); + assert_eq!(registry.encode_openmetrics_to_string().unwrap(), exp); } #[test] fn test_derive() { use crate::{MetricValue, MetricsGroup}; - #[derive(Debug, Clone, MetricsGroup)] + #[derive(Debug, Default, MetricsGroup)] #[metrics(name = "my-metrics")] struct Metrics { /// Counts foos @@ -337,7 +328,7 @@ combined_bar_count_total 10 assert_eq!(baz.name(), "baz"); assert_eq!(baz.help(), "Measures baz"); - #[derive(Debug, Clone, MetricsGroup)] + #[derive(Debug, Default, MetricsGroup)] struct FooMetrics {} let metrics = FooMetrics::default(); assert_eq!(metrics.name(), "foo_metrics"); diff --git a/src/encoding.rs b/src/encoding.rs new file mode 100644 index 0000000..dde04e2 --- /dev/null +++ b/src/encoding.rs @@ -0,0 +1,119 @@ +//! Functions to encode metrics into the [OpenMetrics text format]. +//! +//! [OpenMetrics text format]: https://github.com/prometheus/OpenMetrics/blob/main/specification/OpenMetrics.md + +use std::{ + borrow::Cow, + fmt::{self, Write}, +}; + +use crate::{MetricItem, MetricType, MetricValue, MetricsGroup}; + +pub(crate) fn write_eof(writer: &mut impl Write) -> fmt::Result { + writer.write_str("# EOF\n") +} + +impl dyn MetricsGroup { + pub(crate) fn encode_openmetrics<'a>( + &self, + writer: &'a mut impl Write, + prefix: Option<&'a str>, + labels: &[(Cow<'a, str>, Cow<'a, str>)], + ) -> fmt::Result { + let name = self.name(); + let prefixes = if let Some(prefix) = prefix { + &[prefix, name] as &[&str] + } else { + &[name] + }; + for metric in self.iter() { + let labels = labels.iter().map(|(k, v)| (k.as_ref(), v.as_ref())); + metric.encode_openmetrics(writer, prefixes, labels)?; + } + Ok(()) + } +} + +impl MetricItem<'_> { + pub(crate) fn encode_openmetrics<'a>( + &self, + writer: &mut impl Write, + prefixes: &[&str], + labels: impl Iterator + 'a, + ) -> fmt::Result { + writer.write_str("# HELP ")?; + write_prefix_name(writer, prefixes, self.name())?; + writer.write_str(" ")?; + writer.write_str(self.help())?; + writer.write_str(".\n")?; + + writer.write_str("# TYPE ")?; + write_prefix_name(writer, prefixes, self.name())?; + writer.write_str(" ")?; + writer.write_str(self.r#type().as_str())?; + writer.write_str("\n")?; + + write_prefix_name(writer, prefixes, self.name())?; + let suffix = match self.r#type() { + MetricType::Counter => "_total", + MetricType::Gauge => "", + }; + writer.write_str(suffix)?; + write_labels(writer, labels)?; + writer.write_char(' ')?; + match self.value() { + MetricValue::Counter(value) => { + encode_u64(writer, value)?; + } + MetricValue::Gauge(value) => { + encode_i64(writer, value)?; + } + } + writer.write_str("\n")?; + Ok(()) + } +} + +fn write_labels<'a>( + writer: &mut impl Write, + labels: impl Iterator + 'a, +) -> fmt::Result { + let mut is_first = true; + let mut labels = labels.peekable(); + while let Some((key, value)) = labels.next() { + let is_last = labels.peek().is_none(); + if is_first { + writer.write_char('{')?; + is_first = false; + } + writer.write_str(key)?; + writer.write_str("=\"")?; + writer.write_str(value)?; + writer.write_str("\"")?; + if is_last { + writer.write_char('}')?; + } else { + writer.write_char(',')?; + } + } + Ok(()) +} + +fn encode_u64(writer: &mut impl Write, v: u64) -> fmt::Result { + writer.write_str(itoa::Buffer::new().format(v))?; + Ok(()) +} + +fn encode_i64(writer: &mut impl Write, v: i64) -> fmt::Result { + writer.write_str(itoa::Buffer::new().format(v))?; + Ok(()) +} + +fn write_prefix_name(writer: &mut impl Write, prefixes: &[&str], name: &str) -> fmt::Result { + for prefix in prefixes { + writer.write_str(prefix)?; + writer.write_str("_")?; + } + writer.write_str(name)?; + Ok(()) +} diff --git a/src/iterable.rs b/src/iterable.rs index 9230fa1..149095f 100644 --- a/src/iterable.rs +++ b/src/iterable.rs @@ -11,14 +11,14 @@ use std::fmt; /// [`MetricsGroup`]: ::iroh_metrics::MetricsGroup pub use iroh_metrics_derive::Iterable; -use crate::Metric; +use crate::MetricItem; /// Trait for iterating over the fields of a struct. pub trait Iterable { /// Returns the number of fields in the struct. fn field_count(&self) -> usize; /// Returns the field name and dyn reference to the field. - fn field_ref(&self, n: usize) -> Option<(&'static str, &dyn Metric)>; + fn field_ref(&self, n: usize) -> Option>; } /// Helper trait to convert from `self` to `dyn Iterable`. @@ -61,7 +61,7 @@ impl<'a> FieldIter<'a> { } } impl<'a> Iterator for FieldIter<'a> { - type Item = (&'static str, &'a dyn Metric); + type Item = MetricItem<'a>; fn next(&mut self) -> Option { if self.pos == self.inner.field_count() { diff --git a/src/lib.rs b/src/lib.rs index 39da4c4..583f72e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,21 +3,21 @@ #![deny(missing_docs, rustdoc::broken_intra_doc_links)] #![cfg_attr(iroh_docsrs, feature(doc_auto_cfg))] -mod base; -pub use base::*; - -mod metrics; -pub use metrics::*; +pub use self::base::*; +pub use self::metrics::*; +pub use self::registry::*; +mod base; +pub(crate) mod encoding; pub mod iterable; - -#[cfg(feature = "static_core")] -pub mod static_core; - +mod metrics; +mod registry; #[cfg(feature = "service")] pub mod service; +#[cfg(feature = "static_core")] +pub mod static_core; -/// Derives [`MetricsGroup`], [`Iterable`] and [`Default`] for a struct. +/// Derives [`MetricsGroup`] and [`Iterable`]. /// /// This derive macro only works on structs with named fields. /// @@ -48,6 +48,9 @@ pub enum Error { /// Indicates that the metrics have not been enabled. #[error("Metrics not enabled")] NoMetrics, + /// Writing the metrics to the output buffer failed. + #[error("Writing the metrics to the output buffer failed")] + Fmt(#[from] std::fmt::Error), /// Any IO related error. #[error("IO: {0}")] Io(#[from] std::io::Error), diff --git a/src/metrics.rs b/src/metrics.rs index fc7def9..6d83072 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -8,16 +8,31 @@ use std::any::Any; +#[cfg(feature = "metrics")] +use std::sync::atomic::{AtomicI64, AtomicU64, Ordering}; + +use serde::{Deserialize, Serialize}; + /// The types of metrics supported by this crate. #[derive(Debug, Clone, Copy, Eq, PartialEq)] #[non_exhaustive] pub enum MetricType { - /// A [`Counter]. + /// A [`Counter`]. Counter, - /// A [`Gauge]. + /// A [`Gauge`]. Gauge, } +impl MetricType { + /// Returns the given metric type's str representation. + pub fn as_str(&self) -> &str { + match self { + MetricType::Counter => "counter", + MetricType::Gauge => "gauge", + } + } +} + /// The value of an individual metric item. #[derive(Debug, Clone, Copy, Eq, PartialEq)] #[non_exhaustive] @@ -36,6 +51,14 @@ impl MetricValue { MetricValue::Gauge(value) => *value as f32, } } + + /// Returns the [`MetricType`] for this metric value. + pub fn r#type(&self) -> MetricType { + match self { + MetricValue::Counter(_) => MetricType::Counter, + MetricValue::Gauge(_) => MetricType::Gauge, + } + } } /// Trait for metric items. @@ -46,23 +69,18 @@ pub trait Metric: std::fmt::Debug { /// Returns the current value of this metric. fn value(&self) -> MetricValue; - /// Returns the help string for this metric. - fn help(&self) -> &'static str; - /// Casts this metric to [`Any`] for downcasting to concrete types. fn as_any(&self) -> &dyn Any; } -/// Open Metrics [`Counter`] to measure discrete events. +/// OpenMetrics [`Counter`] to measure discrete events. /// /// Single monotonically increasing value metric. -#[derive(Debug, Clone)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct Counter { - /// The actual prometheus counter. + /// The counter value. #[cfg(feature = "metrics")] - pub(crate) counter: prometheus_client::metrics::counter::Counter, - /// What this counter measures. - help: &'static str, + pub(crate) value: AtomicU64, } impl Metric for Counter { @@ -74,10 +92,6 @@ impl Metric for Counter { MetricType::Counter } - fn help(&self) -> &'static str { - self.help - } - fn as_any(&self) -> &dyn Any { self } @@ -85,19 +99,15 @@ impl Metric for Counter { impl Counter { /// Constructs a new counter, based on the given `help`. - pub fn new(help: &'static str) -> Self { - Counter { - #[cfg(feature = "metrics")] - counter: Default::default(), - help, - } + pub fn new() -> Self { + Self::default() } /// Increases the [`Counter`] by 1, returning the previous value. pub fn inc(&self) -> u64 { #[cfg(feature = "metrics")] { - self.counter.inc() + self.value.fetch_add(1, Ordering::Relaxed) } #[cfg(not(feature = "metrics"))] 0 @@ -107,7 +117,7 @@ impl Counter { pub fn inc_by(&self, v: u64) -> u64 { #[cfg(feature = "metrics")] { - self.counter.inc_by(v) + self.value.fetch_add(v, Ordering::Relaxed) } #[cfg(not(feature = "metrics"))] { @@ -122,9 +132,7 @@ impl Counter { pub fn set(&self, v: u64) -> u64 { #[cfg(feature = "metrics")] { - self.counter - .inner() - .swap(v, std::sync::atomic::Ordering::Relaxed) + self.value.swap(v, Ordering::Relaxed) } #[cfg(not(feature = "metrics"))] { @@ -137,21 +145,19 @@ impl Counter { pub fn get(&self) -> u64 { #[cfg(feature = "metrics")] { - self.counter.get() + self.value.load(Ordering::Relaxed) } #[cfg(not(feature = "metrics"))] 0 } } -/// Open Metrics [`Gauge`]. -#[derive(Debug, Clone)] +/// OpenMetrics [`Gauge`]. +#[derive(Debug, Default, Serialize, Deserialize)] pub struct Gauge { - /// The actual prometheus gauge. + /// The gauge value. #[cfg(feature = "metrics")] - pub(crate) gauge: prometheus_client::metrics::gauge::Gauge, - /// What this gauge tracks. - help: &'static str, + pub(crate) value: AtomicI64, } impl Metric for Gauge { @@ -159,10 +165,6 @@ impl Metric for Gauge { MetricType::Gauge } - fn help(&self) -> &'static str { - self.help - } - fn value(&self) -> MetricValue { MetricValue::Gauge(self.get()) } @@ -174,19 +176,15 @@ impl Metric for Gauge { impl Gauge { /// Constructs a new gauge, based on the given `help`. - pub fn new(help: &'static str) -> Self { - Self { - #[cfg(feature = "metrics")] - gauge: Default::default(), - help, - } + pub fn new() -> Self { + Self::default() } /// Increases the [`Gauge`] by 1, returning the previous value. pub fn inc(&self) -> i64 { #[cfg(feature = "metrics")] { - self.gauge.inc() + self.value.fetch_add(1, Ordering::Relaxed) } #[cfg(not(feature = "metrics"))] 0 @@ -196,7 +194,7 @@ impl Gauge { pub fn inc_by(&self, v: i64) -> i64 { #[cfg(feature = "metrics")] { - self.gauge.inc_by(v) + self.value.fetch_add(v, Ordering::Relaxed) } #[cfg(not(feature = "metrics"))] { @@ -209,7 +207,7 @@ impl Gauge { pub fn dec(&self) -> i64 { #[cfg(feature = "metrics")] { - self.gauge.dec() + self.value.fetch_sub(1, Ordering::Relaxed) } #[cfg(not(feature = "metrics"))] 0 @@ -219,7 +217,7 @@ impl Gauge { pub fn dec_by(&self, v: i64) -> i64 { #[cfg(feature = "metrics")] { - self.gauge.dec_by(v) + self.value.fetch_sub(v, Ordering::Relaxed) } #[cfg(not(feature = "metrics"))] { @@ -232,7 +230,7 @@ impl Gauge { pub fn set(&self, v: i64) -> i64 { #[cfg(feature = "metrics")] { - self.gauge.set(v) + self.value.swap(v, Ordering::Relaxed) } #[cfg(not(feature = "metrics"))] { @@ -245,7 +243,7 @@ impl Gauge { pub fn get(&self) -> i64 { #[cfg(feature = "metrics")] { - self.gauge.get() + self.value.load(Ordering::Relaxed) } #[cfg(not(feature = "metrics"))] 0 diff --git a/src/registry.rs b/src/registry.rs new file mode 100644 index 0000000..f4ab890 --- /dev/null +++ b/src/registry.rs @@ -0,0 +1,115 @@ +//! Registry to register metrics groups and encode them in the OpenMetrics text format. + +use std::{ + borrow::Cow, + fmt::{self, Write}, + sync::Arc, +}; + +use crate::{encoding::write_eof, Error, MetricsGroup, MetricsGroupSet}; + +/// A registry for [`MetricsGroup`]. +#[derive(Debug, Default)] +pub struct Registry { + metrics: Vec>, + prefix: Option>, + labels: Vec<(Cow<'static, str>, Cow<'static, str>)>, + sub_registries: Vec, +} + +impl Registry { + /// Creates a subregistry where all metrics are prefixed with `prefix`. + /// + /// Returns a mutable refernce to the subregistry. + pub fn sub_registry_with_prefix(&mut self, prefix: impl Into>) -> &mut Self { + let prefix = self.prefix.to_owned().map(|p| p + "_").unwrap_or_default() + prefix.into(); + let sub_registry = Registry { + metrics: Default::default(), + prefix: Some(prefix), + labels: self.labels.clone(), + sub_registries: Default::default(), + }; + self.sub_registries.push(sub_registry); + self.sub_registries.last_mut().unwrap() + } + + /// Creates a subregistry where all metrics are labeled. + /// + /// Returns a mutable refernce to the subregistry. + pub fn sub_registry_with_labels( + &mut self, + labels: impl IntoIterator>, impl Into>)>, + ) -> &mut Self { + let mut all_labels = self.labels.clone(); + all_labels.extend(labels.into_iter().map(|(k, v)| (k.into(), v.into()))); + let sub_registry = Registry { + prefix: self.prefix.clone(), + labels: all_labels, + metrics: Default::default(), + sub_registries: Default::default(), + }; + self.sub_registries.push(sub_registry); + self.sub_registries.last_mut().unwrap() + } + + /// Creates a subregistry where all metrics have a `key=value` label. + /// + /// Returns a mutable refernce to the subregistry. + pub fn sub_registry_with_label( + &mut self, + key: impl Into>, + value: impl Into>, + ) -> &mut Self { + self.sub_registry_with_labels([(key, value)]) + } + + /// Registers a [`MetricsGroup`] into this registry. + pub fn register(&mut self, metrics_group: Arc) { + self.metrics.push(metrics_group); + } + + /// Registers a [`MetricsGroupSet`] into this registry. + pub fn register_all(&mut self, metrics_group_set: &impl MetricsGroupSet) { + for group in metrics_group_set.groups_cloned() { + self.register(group) + } + } + + /// Registers a [`MetricsGroupSet`] into this registry, prefixing all metrics with the group set's name. + pub fn register_all_prefixed(&mut self, metrics_group_set: &impl MetricsGroupSet) { + let registry = self.sub_registry_with_prefix(metrics_group_set.name()); + registry.register_all(metrics_group_set) + } + + fn encode_inner(&self, writer: &mut impl Write) -> fmt::Result { + for group in &self.metrics { + group.encode_openmetrics(writer, self.prefix.as_deref(), &self.labels)?; + } + + for sub in self.sub_registries.iter() { + sub.encode_inner(writer)?; + } + Ok(()) + } +} + +/// Helper trait to abstract over different ways to access metrics. +pub trait MetricsSource: Send + 'static { + /// Encodes all metrics into a string in the OpenMetrics text format. + fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error>; + + /// Encodes the metrics in the OpenMetrics text format into a newly allocated string. + fn encode_openmetrics_to_string(&self) -> Result { + let mut s = String::new(); + self.encode_openmetrics(&mut s)?; + Ok(s) + } +} + +impl MetricsSource for Registry { + fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error> { + self.encode_inner(writer)?; + write_eof(writer)?; + Ok(()) + } +} diff --git a/src/service.rs b/src/service.rs index 1fd943d..1e09273 100644 --- a/src/service.rs +++ b/src/service.rs @@ -8,29 +8,13 @@ use std::{ }; use hyper::{service::service_fn, Request, Response}; -use prometheus_client::registry::Registry; use tokio::{io::AsyncWriteExt as _, net::TcpListener}; use tracing::{debug, error, info, warn}; -use crate::{parse_prometheus_metrics, Error}; +use crate::{parse_prometheus_metrics, Error, MetricsSource, Registry}; type BytesBody = http_body_util::Full; -/// Helper trait to abstract over different ways to access metrics. -pub trait MetricsSource: Send + 'static { - /// Encodes all metrics into a string in the Open Metrics text format. - fn encode_openmetrics(&self) -> Result; -} - -impl MetricsSource for Registry { - fn encode_openmetrics(&self) -> Result { - let mut buf = String::new(); - prometheus_client::encoding::text::encode(&mut buf, self) - .expect("writing to string always works"); - Ok(buf) - } -} - /// A cloneable [`Registry`] in a read-write lock. /// /// Useful if you need mutable access to a registry, while also using the services @@ -38,15 +22,15 @@ impl MetricsSource for Registry { pub type RwLockRegistry = Arc>; impl MetricsSource for RwLockRegistry { - fn encode_openmetrics(&self) -> Result { + fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error> { let inner = self.read().expect("poisoned"); - inner.encode_openmetrics() + inner.encode_openmetrics(writer) } } impl MetricsSource for Arc { - fn encode_openmetrics(&self) -> Result { - Arc::deref(self).encode_openmetrics() + fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error> { + Arc::deref(self).encode_openmetrics(writer) } } @@ -101,7 +85,10 @@ pub async fn start_metrics_dumper( } /// Start a metrics exporter service. -pub async fn start_metrics_exporter(cfg: MetricsExporterConfig, registry: impl MetricsSource) { +pub async fn start_metrics_exporter( + cfg: MetricsExporterConfig, + registry: impl MetricsSource, +) -> Result<(), Error> { let MetricsExporterConfig { interval, endpoint, @@ -117,13 +104,7 @@ pub async fn start_metrics_exporter(cfg: MetricsExporterConfig, registry: impl M ); loop { tokio::time::sleep(interval).await; - let buf = match registry.encode_openmetrics() { - Ok(buf) => buf, - Err(err) => { - tracing::warn!("Failed to encode metrics: {err:#}"); - break; - } - }; + let buf = registry.encode_openmetrics_to_string()?; let mut req = push_client.post(&prom_gateway_uri); if let Some(username) = username.clone() { req = req.basic_auth(username, Some(password.clone())); @@ -154,7 +135,7 @@ async fn handler( _req: Request, registry: impl MetricsSource, ) -> Result, Error> { - let content = registry.encode_openmetrics()?; + let content = registry.encode_openmetrics_to_string()?; let response = Response::builder() .header(hyper::header::CONTENT_TYPE, "text/plain; charset=utf-8") .body(body_full(content)) @@ -175,7 +156,7 @@ async fn dump_metrics( registry: &impl MetricsSource, write_header: bool, ) -> Result<(), Error> { - let m = registry.encode_openmetrics()?; + let m = registry.encode_openmetrics_to_string()?; let m = parse_prometheus_metrics(&m); let time_since_start = start.elapsed().as_millis() as f64; diff --git a/src/static_core.rs b/src/static_core.rs index ba67cb7..2ec2c12 100644 --- a/src/static_core.rs +++ b/src/static_core.rs @@ -10,32 +10,19 @@ //! //! # Example: //! ```rust -//! use iroh_metrics::{inc, inc_by, iterable::Iterable, static_core::Core, Counter, MetricsGroup}; +//! use std::sync::Arc; +//! use iroh_metrics::{inc, inc_by, static_core::Core, Counter, MetricsGroup}; //! -//! #[derive(Debug, Clone, Iterable)] +//! #[derive(Debug, Default, MetricsGroup)] +//! #[metrics(name = "my_metrics")] //! pub struct Metrics { +//! /// things_added tracks the number of things we have added //! pub things_added: Counter, //! } //! -//! impl Default for Metrics { -//! fn default() -> Self { -//! Self { -//! things_added: Counter::new( -//! "things_added tracks the number of things we have added", -//! ), -//! } -//! } -//! } -//! -//! impl MetricsGroup for Metrics { -//! fn name(&self) -> &'static str { -//! "my_metrics" -//! } -//! } -//! //! Core::init(|reg, metrics| { -//! let m = Metrics::default(); -//! m.register(reg); +//! let m = Arc::new(Metrics::default()); +//! reg.register(m.clone()); //! metrics.insert(m); //! }); //! @@ -45,11 +32,8 @@ use std::sync::OnceLock; +use crate::{base::MetricsGroup, Error, Registry}; use erased_set::ErasedSyncSet; -#[cfg(feature = "metrics")] -use prometheus_client::{encoding::text::encode, registry::Registry}; - -use crate::base::MetricsGroup; #[cfg(not(feature = "metrics"))] type Registry = (); @@ -60,11 +44,10 @@ type Registry = (); #[derive(Clone, Copy, Debug)] pub struct GlobalRegistry; -#[cfg(feature = "service")] -impl crate::service::MetricsSource for GlobalRegistry { - fn encode_openmetrics(&self) -> Result { - let core = crate::static_core::Core::get().ok_or(crate::Error::NoMetrics)?; - Ok(core.encode()) +impl crate::MetricsSource for GlobalRegistry { + fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error> { + let core = crate::static_core::Core::get().ok_or(Error::NoMetrics)?; + core.registry.encode_openmetrics(writer) } } @@ -123,10 +106,12 @@ impl Core { /// Encodes the current metrics registry to a string in /// the prometheus text exposition format. #[cfg(feature = "metrics")] - pub fn encode(&self) -> String { - let mut buf = String::new(); - encode(&mut buf, &self.registry).expect("writing to string always works"); - buf + pub fn encode_openmetrics(&self) -> String { + use crate::MetricsSource; + + self.registry() + .encode_openmetrics_to_string() + .expect("encoding to string never fails") } } From 2686513ccc193eb03e851b0baa378ffb8fc597d8 Mon Sep 17 00:00:00 2001 From: Frando Date: Mon, 28 Apr 2025 10:48:07 +0200 Subject: [PATCH 2/4] chore: fmt --- src/base.rs | 3 +-- src/lib.rs | 4 +--- src/metrics.rs | 1 - src/registry.rs | 6 +++--- src/static_core.rs | 4 +++- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/base.rs b/src/base.rs index 67f9140..53ac737 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,5 +1,4 @@ -use std::any::Any; -use std::sync::Arc; +use std::{any::Any, sync::Arc}; use crate::{ iterable::{FieldIter, IntoIterable, Iterable}, diff --git a/src/lib.rs b/src/lib.rs index 583f72e..a8fe7d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,7 @@ #![deny(missing_docs, rustdoc::broken_intra_doc_links)] #![cfg_attr(iroh_docsrs, feature(doc_auto_cfg))] -pub use self::base::*; -pub use self::metrics::*; -pub use self::registry::*; +pub use self::{base::*, metrics::*, registry::*}; mod base; pub(crate) mod encoding; diff --git a/src/metrics.rs b/src/metrics.rs index 6d83072..86fd814 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -7,7 +7,6 @@ //! and the structs don't collect actual data. use std::any::Any; - #[cfg(feature = "metrics")] use std::sync::atomic::{AtomicI64, AtomicU64, Ordering}; diff --git a/src/registry.rs b/src/registry.rs index f4ab890..ed9ecaa 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -20,7 +20,7 @@ pub struct Registry { impl Registry { /// Creates a subregistry where all metrics are prefixed with `prefix`. /// - /// Returns a mutable refernce to the subregistry. + /// Returns a mutable reference to the subregistry. pub fn sub_registry_with_prefix(&mut self, prefix: impl Into>) -> &mut Self { let prefix = self.prefix.to_owned().map(|p| p + "_").unwrap_or_default() + prefix.into(); let sub_registry = Registry { @@ -35,7 +35,7 @@ impl Registry { /// Creates a subregistry where all metrics are labeled. /// - /// Returns a mutable refernce to the subregistry. + /// Returns a mutable reference to the subregistry. pub fn sub_registry_with_labels( &mut self, labels: impl IntoIterator>, impl Into>)>, @@ -54,7 +54,7 @@ impl Registry { /// Creates a subregistry where all metrics have a `key=value` label. /// - /// Returns a mutable refernce to the subregistry. + /// Returns a mutable reference to the subregistry. pub fn sub_registry_with_label( &mut self, key: impl Into>, diff --git a/src/static_core.rs b/src/static_core.rs index 2ec2c12..68c19ef 100644 --- a/src/static_core.rs +++ b/src/static_core.rs @@ -11,6 +11,7 @@ //! # Example: //! ```rust //! use std::sync::Arc; +//! //! use iroh_metrics::{inc, inc_by, static_core::Core, Counter, MetricsGroup}; //! //! #[derive(Debug, Default, MetricsGroup)] @@ -32,9 +33,10 @@ use std::sync::OnceLock; -use crate::{base::MetricsGroup, Error, Registry}; use erased_set::ErasedSyncSet; +use crate::{base::MetricsGroup, Error, Registry}; + #[cfg(not(feature = "metrics"))] type Registry = (); From 9dbea72cba6c7cda0d866da06f1e8223b195d167 Mon Sep 17 00:00:00 2001 From: Frando Date: Mon, 28 Apr 2025 10:49:25 +0200 Subject: [PATCH 3/4] chore: update sscache github action --- .github/workflows/ci.yaml | 10 +++++----- .github/workflows/docs.yaml | 2 +- .github/workflows/tests.yaml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index add9fdb..019b9ff 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -157,7 +157,7 @@ jobs: with: fetch-depth: 0 - name: Install sccache - uses: mozilla-actions/sccache-action@v0.0.7 + uses: mozilla-actions/sccache-action@v0.0.9 - name: Setup Environment (PR) if: ${{ github.event_name == 'pull_request' }} @@ -189,7 +189,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - - uses: mozilla-actions/sccache-action@v0.0.7 + - uses: mozilla-actions/sccache-action@v0.0.9 - uses: taiki-e/install-action@cargo-make - run: cargo make format-check @@ -206,7 +206,7 @@ jobs: with: toolchain: nightly-2024-11-30 - name: Install sccache - uses: mozilla-actions/sccache-action@v0.0.7 + uses: mozilla-actions/sccache-action@v0.0.9 - name: Docs run: cargo doc --workspace --all-features --no-deps --document-private-items @@ -225,7 +225,7 @@ jobs: with: components: clippy - name: Install sccache - uses: mozilla-actions/sccache-action@v0.0.7 + uses: mozilla-actions/sccache-action@v0.0.9 # TODO: We have a bunch of platform-dependent code so should # probably run this job on the full platform matrix @@ -252,7 +252,7 @@ jobs: with: toolchain: ${{ env.MSRV }} - name: Install sccache - uses: mozilla-actions/sccache-action@v0.0.7 + uses: mozilla-actions/sccache-action@v0.0.9 - name: Check MSRV all features run: | diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index cb5ebbf..f142cfc 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -34,7 +34,7 @@ jobs: with: toolchain: nightly-2024-11-30 - name: Install sccache - uses: mozilla-actions/sccache-action@v0.0.7 + uses: mozilla-actions/sccache-action@v0.0.9 - name: Generate Docs run: cargo doc --workspace --all-features --no-deps diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 69cec86..0b24d7a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -69,7 +69,7 @@ jobs: tool: nextest - name: Install sccache - uses: mozilla-actions/sccache-action@v0.0.7 + uses: mozilla-actions/sccache-action@v0.0.9 - name: Select features run: | @@ -202,7 +202,7 @@ jobs: } - name: Install sccache - uses: mozilla-actions/sccache-action@v0.0.7 + uses: mozilla-actions/sccache-action@v0.0.9 - uses: msys2/setup-msys2@v2 From 5ece9c33e7936dcc78a5e00d442bfb2a5c336d07 Mon Sep 17 00:00:00 2001 From: Frando Date: Mon, 28 Apr 2025 13:25:32 +0200 Subject: [PATCH 4/4] tests: add serde test, enable serde rc feature --- Cargo.lock | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +- src/base.rs | 57 ++++++++++++++++++--------- src/lib.rs | 1 + 4 files changed, 151 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35a21e2..cd6a3f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -83,6 +92,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "displaydoc" version = "0.2.5" @@ -94,6 +115,18 @@ dependencies = [ "syn", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "erased_set" version = "0.8.0" @@ -167,6 +200,29 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -431,6 +487,7 @@ dependencies = [ "hyper-util", "iroh-metrics-derive", "itoa", + "postcard", "reqwest", "serde", "thiserror", @@ -476,6 +533,16 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -547,6 +614,19 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "postcard" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -724,6 +804,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustls" version = "0.23.20" @@ -773,6 +862,18 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.216" @@ -848,6 +949,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index a976740..01f3f47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ unused-async = "warn" [dependencies] iroh-metrics-derive = { path = "./iroh-metrics-derive", version = "0.1.0" } itoa = "1" -serde = { version = "1", features = ["derive"] } +serde = { version = "1", features = ["derive", "rc"] } thiserror = "2.0.6" tracing = "0.1" @@ -47,6 +47,7 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "rus tokio = { version = "1", features = ["rt", "net", "fs"], optional = true } [dev-dependencies] +postcard = { version = "1.1.1", features = ["use-std"] } tokio = { version = "1", features = ["io-util", "sync", "rt", "net", "fs", "macros", "time", "test-util"] } [features] diff --git a/src/base.rs b/src/base.rs index 53ac737..73da411 100644 --- a/src/base.rs +++ b/src/base.rs @@ -96,20 +96,22 @@ mod tests { /// Tests with the `metrics` feature, #[cfg(all(test, feature = "metrics"))] mod tests { + use serde::{Deserialize, Serialize}; + use super::*; use crate::{iterable::Iterable, Counter, Gauge, MetricType, MetricsSource, Registry}; - #[derive(Debug, Iterable)] + #[derive(Debug, Iterable, Serialize, Deserialize)] pub struct FooMetrics { pub metric_a: Counter, - pub metric_b: Counter, + pub metric_b: Gauge, } impl Default for FooMetrics { fn default() -> Self { Self { metric_a: Counter::new(), - metric_b: Counter::new(), + metric_b: Gauge::new(), } } } @@ -120,7 +122,7 @@ mod tests { } } - #[derive(Debug, Default, Iterable)] + #[derive(Debug, Default, Iterable, Serialize, Deserialize)] pub struct BarMetrics { /// Bar Count pub count: Counter, @@ -132,7 +134,7 @@ mod tests { } } - #[derive(Debug, Default)] + #[derive(Debug, Default, Serialize, Deserialize)] struct CombinedMetrics { foo: Arc, bar: Arc, @@ -170,7 +172,7 @@ mod tests { assert_eq!(items[0].r#type(), MetricType::Counter); assert_eq!(items[1].name(), "metric_b"); assert_eq!(items[1].help(), "metric_b"); - assert_eq!(items[1].r#type(), MetricType::Counter); + assert_eq!(items[1].r#type(), MetricType::Gauge); Ok(()) } @@ -199,8 +201,8 @@ mod tests { # 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 +# TYPE foo_metric_b gauge +foo_metric_b 2 # EOF "; let enc = registry.encode_openmetrics_to_string()?; @@ -212,6 +214,7 @@ foo_metric_b_total 2 fn test_metric_sets() { let metrics = CombinedMetrics::default(); metrics.foo.metric_a.inc(); + metrics.foo.metric_b.set(-42); metrics.bar.count.inc_by(10); // Using `iter` to iterate over all metrics in the group set. @@ -222,7 +225,7 @@ foo_metric_b_total 2 collected.collect::>(), vec![ ("foo", "metric_a", "metric_a", 1.0), - ("foo", "metric_b", "metric_b", 0.0), + ("foo", "metric_b", "metric_b", -42.0), ("bar", "count", "Bar Count", 10.0), ] ); @@ -232,16 +235,19 @@ foo_metric_b_total 2 for group in metrics.groups() { for metric in group.iter() { if let Some(counter) = metric.as_any().downcast_ref::() { - collected.push((group.name(), metric.name(), counter.get())); + collected.push((group.name(), metric.name(), counter.value())); + } + if let Some(gauge) = metric.as_any().downcast_ref::() { + collected.push((group.name(), metric.name(), gauge.value())); } } } assert_eq!( collected, vec![ - ("foo", "metric_a", 1), - ("foo", "metric_b", 0), - ("bar", "count", 10), + ("foo", "metric_a", MetricValue::Counter(1)), + ("foo", "metric_b", MetricValue::Gauge(-42)), + ("bar", "count", MetricValue::Counter(10)), ] ); @@ -253,8 +259,8 @@ foo_metric_b_total 2 # TYPE boo_foo_metric_a counter boo_foo_metric_a_total 1 # HELP boo_foo_metric_b metric_b. -# TYPE boo_foo_metric_b counter -boo_foo_metric_b_total 0 +# TYPE boo_foo_metric_b gauge +boo_foo_metric_b -42 # HELP boo_bar_count Bar Count. # TYPE boo_bar_count counter boo_bar_count_total 10 @@ -268,8 +274,8 @@ boo_bar_count_total 10 # TYPE boo_foo_metric_a counter boo_foo_metric_a_total 1 # HELP boo_foo_metric_b metric_b. -# TYPE boo_foo_metric_b counter -boo_foo_metric_b_total 0 +# TYPE boo_foo_metric_b gauge +boo_foo_metric_b -42 # HELP boo_bar_count Bar Count. # TYPE boo_bar_count counter boo_bar_count_total 10 @@ -277,8 +283,8 @@ boo_bar_count_total 10 # TYPE combined_foo_metric_a counter combined_foo_metric_a_total{x="y"} 1 # HELP combined_foo_metric_b metric_b. -# TYPE combined_foo_metric_b counter -combined_foo_metric_b_total{x="y"} 0 +# TYPE combined_foo_metric_b gauge +combined_foo_metric_b{x="y"} -42 # HELP combined_bar_count Bar Count. # TYPE combined_bar_count counter combined_bar_count_total{x="y"} 10 @@ -334,4 +340,17 @@ combined_bar_count_total{x="y"} 10 let mut values = metrics.iter(); assert!(values.next().is_none()); } + + #[test] + fn test_serde() { + let metrics = CombinedMetrics::default(); + metrics.foo.metric_a.inc(); + metrics.foo.metric_b.set(-42); + metrics.bar.count.inc_by(10); + let encoded = postcard::to_stdvec(&metrics).unwrap(); + let decoded: CombinedMetrics = postcard::from_bytes(&encoded).unwrap(); + assert_eq!(decoded.foo.metric_a.get(), 1); + assert_eq!(decoded.foo.metric_b.get(), -42); + assert_eq!(decoded.bar.count.get(), 10); + } } diff --git a/src/lib.rs b/src/lib.rs index a8fe7d1..a8f6cbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,7 @@ use std::collections::HashMap; /// Potential errors from this library. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { /// Indicates that the metrics have not been enabled. #[error("Metrics not enabled")]