From d0123e441882a136e2a1a8b5f558c999fd7f6ff3 Mon Sep 17 00:00:00 2001 From: Maxim <59533214+biryukovmaxim@users.noreply.github.com> Date: Fri, 24 May 2024 12:19:51 +0300 Subject: [PATCH] Metrics/gauge (#129) The [opentelemetry](https://github.com/open-telemetry/opentelemetry-rust/blob/a7a47a745dfe9ded79ffb64722fc53847638d47a/examples/metrics-basic/src/main.rs#L112) crate actually supports Gauge metric behind otel_unstable feature. Co-authored-by: Sistemas --- .github/workflows/CI.yml | 6 +- Cargo.toml | 2 + src/metrics.rs | 75 ++++++++++++++++- tests/metrics_publishing.rs | 161 +++++++++++++++++++++++++++++++++++- 4 files changed, 239 insertions(+), 5 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 26ed4a3..d309bae 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -135,7 +135,7 @@ jobs: - name: install cargo-nextest uses: taiki-e/install-action@nextest - name: Run tests - run: cargo nextest run --profile ci --workspace + run: cargo nextest run --profile ci --workspace --all-features # TODO(eliza): punt on this for now because the generated JUnit report is # missing some fields that this action needs to give good output. # - name: Publish Test Report @@ -146,7 +146,7 @@ jobs: # check_name: "cargo test (Rust ${{ matrix.rust }} on ${{ matrix.os }})" # check_title_template: "{{SUITE_NAME}}::{{TEST_NAME}}" - name: Run doctests - run: cargo test --doc --workspace + run: cargo test --doc --workspace --all-features test-build-wasm: name: build tests (wasm) @@ -158,7 +158,7 @@ jobs: with: target: wasm32-unknown-unknown - name: build all tests - run: cargo test --no-run + run: cargo test --no-run --all-features # all required checks except for the main test run (which we only require # specific matrix combinations from) diff --git a/Cargo.toml b/Cargo.toml index e7372a8..7c87287 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ rust-version = "1.65.0" default = ["tracing-log", "metrics"] # Enables support for exporting OpenTelemetry metrics metrics = ["opentelemetry/metrics","opentelemetry_sdk/metrics", "smallvec"] +# Enables experimental support for OpenTelemetry gauge metrics +metrics_gauge_unstable = ["opentelemetry/otel_unstable"] [dependencies] opentelemetry = { version = "0.23.0", default-features = false, features = ["trace"] } diff --git a/src/metrics.rs b/src/metrics.rs index 5151720..33e1863 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -2,6 +2,8 @@ use std::{collections::HashMap, fmt, sync::RwLock}; use tracing::{field::Visit, Subscriber}; use tracing_core::{Field, Interest, Metadata}; +#[cfg(feature = "metrics_gauge_unstable")] +use opentelemetry::metrics::Gauge; use opentelemetry::{ metrics::{Counter, Histogram, Meter, MeterProvider, UpDownCounter}, KeyValue, Value, @@ -21,6 +23,9 @@ const INSTRUMENTATION_LIBRARY_NAME: &str = "tracing/tracing-opentelemetry"; const METRIC_PREFIX_MONOTONIC_COUNTER: &str = "monotonic_counter."; const METRIC_PREFIX_COUNTER: &str = "counter."; const METRIC_PREFIX_HISTOGRAM: &str = "histogram."; +#[cfg(feature = "metrics_gauge_unstable")] +const METRIC_PREFIX_GAUGE: &str = "gauge."; + const I64_MAX: u64 = i64::MAX as u64; #[derive(Default)] @@ -31,6 +36,12 @@ pub(crate) struct Instruments { f64_up_down_counter: MetricsMap>, u64_histogram: MetricsMap>, f64_histogram: MetricsMap>, + #[cfg(feature = "metrics_gauge_unstable")] + u64_gauge: MetricsMap>, + #[cfg(feature = "metrics_gauge_unstable")] + i64_gauge: MetricsMap>, + #[cfg(feature = "metrics_gauge_unstable")] + f64_gauge: MetricsMap>, } type MetricsMap = RwLock>; @@ -43,6 +54,12 @@ pub(crate) enum InstrumentType { UpDownCounterF64(f64), HistogramU64(u64), HistogramF64(f64), + #[cfg(feature = "metrics_gauge_unstable")] + GaugeU64(u64), + #[cfg(feature = "metrics_gauge_unstable")] + GaugeI64(i64), + #[cfg(feature = "metrics_gauge_unstable")] + GaugeF64(f64), } impl Instruments { @@ -125,6 +142,33 @@ impl Instruments { |rec| rec.record(value, attributes), ); } + #[cfg(feature = "metrics_gauge_unstable")] + InstrumentType::GaugeU64(value) => { + update_or_insert( + &self.u64_gauge, + metric_name, + || meter.u64_gauge(metric_name).init(), + |rec| rec.record(value, attributes), + ); + } + #[cfg(feature = "metrics_gauge_unstable")] + InstrumentType::GaugeI64(value) => { + update_or_insert( + &self.i64_gauge, + metric_name, + || meter.i64_gauge(metric_name).init(), + |rec| rec.record(value, attributes), + ); + } + #[cfg(feature = "metrics_gauge_unstable")] + InstrumentType::GaugeF64(value) => { + update_or_insert( + &self.f64_gauge, + metric_name, + || meter.f64_gauge(metric_name).init(), + |rec| rec.record(value, attributes), + ); + } }; } } @@ -141,6 +185,12 @@ impl<'a> Visit for MetricVisitor<'a> { } fn record_u64(&mut self, field: &Field, value: u64) { + #[cfg(feature = "metrics_gauge_unstable")] + if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_GAUGE) { + self.visited_metrics + .push((metric_name, InstrumentType::GaugeU64(value))); + return; + } if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) { self.visited_metrics .push((metric_name, InstrumentType::CounterU64(value))); @@ -166,6 +216,12 @@ impl<'a> Visit for MetricVisitor<'a> { } fn record_f64(&mut self, field: &Field, value: f64) { + #[cfg(feature = "metrics_gauge_unstable")] + if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_GAUGE) { + self.visited_metrics + .push((metric_name, InstrumentType::GaugeF64(value))); + return; + } if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) { self.visited_metrics .push((metric_name, InstrumentType::CounterF64(value))); @@ -182,6 +238,12 @@ impl<'a> Visit for MetricVisitor<'a> { } fn record_i64(&mut self, field: &Field, value: i64) { + #[cfg(feature = "metrics_gauge_unstable")] + if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_GAUGE) { + self.visited_metrics + .push((metric_name, InstrumentType::GaugeI64(value))); + return; + } if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) { self.visited_metrics .push((metric_name, InstrumentType::CounterU64(value as u64))); @@ -358,9 +420,20 @@ impl MetricsFilter { meta.is_event() && meta.fields().iter().any(|field| { let name = field.name(); - name.starts_with(METRIC_PREFIX_COUNTER) + + if name.starts_with(METRIC_PREFIX_COUNTER) || name.starts_with(METRIC_PREFIX_MONOTONIC_COUNTER) || name.starts_with(METRIC_PREFIX_HISTOGRAM) + { + return true; + } + + #[cfg(feature = "metrics_gauge_unstable")] + if name.starts_with(METRIC_PREFIX_GAUGE) { + return true; + } + + false }) } } diff --git a/tests/metrics_publishing.rs b/tests/metrics_publishing.rs index f847b58..5968c6c 100644 --- a/tests/metrics_publishing.rs +++ b/tests/metrics_publishing.rs @@ -1,7 +1,7 @@ use opentelemetry::{metrics::MetricsError, KeyValue}; use opentelemetry_sdk::{ metrics::{ - data::{self, Histogram, Sum}, + data::{self, Gauge, Histogram, Sum}, reader::{ AggregationSelector, DefaultAggregationSelector, DefaultTemporalitySelector, MetricReader, TemporalitySelector, @@ -115,6 +115,48 @@ async fn f64_up_down_counter_is_exported() { exporter.export().unwrap(); } +#[cfg(feature = "metrics_gauge_unstable")] +#[tokio::test] +async fn u64_gauge_is_exported() { + let (subscriber, exporter) = + init_subscriber("gygygy".to_string(), InstrumentKind::Gauge, 2_u64, None); + + tracing::subscriber::with_default(subscriber, || { + tracing::info!(gauge.gygygy = 1_u64); + tracing::info!(gauge.gygygy = 2_u64); + }); + + exporter.export().unwrap(); +} + +#[cfg(feature = "metrics_gauge_unstable")] +#[tokio::test] +async fn f64_gauge_is_exported() { + let (subscriber, exporter) = + init_subscriber("huitt".to_string(), InstrumentKind::Gauge, 2_f64, None); + + tracing::subscriber::with_default(subscriber, || { + tracing::info!(gauge.huitt = 1_f64); + tracing::info!(gauge.huitt = 2_f64); + }); + + exporter.export().unwrap(); +} + +#[cfg(feature = "metrics_gauge_unstable")] +#[tokio::test] +async fn i64_gauge_is_exported() { + let (subscriber, exporter) = + init_subscriber("samsagaz".to_string(), InstrumentKind::Gauge, 2_i64, None); + + tracing::subscriber::with_default(subscriber, || { + tracing::info!(gauge.samsagaz = 1_i64); + tracing::info!(gauge.samsagaz = 2_i64); + }); + + exporter.export().unwrap(); +} + #[tokio::test] async fn u64_histogram_is_exported() { let (subscriber, exporter) = init_subscriber( @@ -275,6 +317,105 @@ async fn f64_up_down_counter_with_attributes_is_exported() { exporter.export().unwrap(); } +#[cfg(feature = "metrics_gauge_unstable")] +#[tokio::test] +async fn f64_gauge_with_attributes_is_exported() { + let (subscriber, exporter) = init_subscriber( + "hello_world".to_string(), + InstrumentKind::Gauge, + 1_f64, + Some(AttributeSet::from( + [ + KeyValue::new("u64_key_1", 1_i64), + KeyValue::new("i64_key_1", 2_i64), + KeyValue::new("f64_key_1", 3_f64), + KeyValue::new("str_key_1", "foo"), + KeyValue::new("bool_key_1", true), + ] + .as_slice(), + )), + ); + + tracing::subscriber::with_default(subscriber, || { + tracing::info!( + gauge.hello_world = 1_f64, + u64_key_1 = 1_u64, + i64_key_1 = 2_i64, + f64_key_1 = 3_f64, + str_key_1 = "foo", + bool_key_1 = true, + ); + }); + + exporter.export().unwrap(); +} + +#[cfg(feature = "metrics_gauge_unstable")] +#[tokio::test] +async fn u64_gauge_with_attributes_is_exported() { + let (subscriber, exporter) = init_subscriber( + "hello_world".to_string(), + InstrumentKind::Gauge, + 1_u64, + Some(AttributeSet::from( + [ + KeyValue::new("u64_key_1", 1_i64), + KeyValue::new("i64_key_1", 2_i64), + KeyValue::new("f64_key_1", 3_f64), + KeyValue::new("str_key_1", "foo"), + KeyValue::new("bool_key_1", true), + ] + .as_slice(), + )), + ); + + tracing::subscriber::with_default(subscriber, || { + tracing::info!( + gauge.hello_world = 1_u64, + u64_key_1 = 1_u64, + i64_key_1 = 2_i64, + f64_key_1 = 3_f64, + str_key_1 = "foo", + bool_key_1 = true, + ); + }); + + exporter.export().unwrap(); +} + +#[cfg(feature = "metrics_gauge_unstable")] +#[tokio::test] +async fn i64_gauge_with_attributes_is_exported() { + let (subscriber, exporter) = init_subscriber( + "hello_world".to_string(), + InstrumentKind::Gauge, + 1_i64, + Some(AttributeSet::from( + [ + KeyValue::new("u64_key_1", 1_i64), + KeyValue::new("i64_key_1", 2_i64), + KeyValue::new("f64_key_1", 3_f64), + KeyValue::new("str_key_1", "foo"), + KeyValue::new("bool_key_1", true), + ] + .as_slice(), + )), + ); + + tracing::subscriber::with_default(subscriber, || { + tracing::info!( + gauge.hello_world = 1_i64, + u64_key_1 = 1_u64, + i64_key_1 = 2_i64, + f64_key_1 = 3_f64, + str_key_1 = "foo", + bool_key_1 = true, + ); + }); + + exporter.export().unwrap(); +} + #[tokio::test] async fn u64_histogram_with_attributes_is_exported() { let (subscriber, exporter) = init_subscriber( @@ -517,6 +658,24 @@ where }); } } + InstrumentKind::Gauge => { + let gauge = metric.data.as_any().downcast_ref::>().unwrap(); + assert_eq!( + self.expected_value, + gauge + .data_points + .iter() + .map(|data_point| data_point.value) + .last() + .unwrap() + ); + + if let Some(expected_attributes) = self.expected_attributes.as_ref() { + gauge.data_points.iter().for_each(|data_point| { + assert_eq!(expected_attributes, &data_point.attributes,) + }); + } + } InstrumentKind::Histogram => { let histogram = metric.data.as_any().downcast_ref::>().unwrap();