diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 12dd9c12a2..ec88c2f998 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -137,6 +137,27 @@ OpenTelemetry supports multiple ways to configure the API, SDK and other compone - Environment variables - Compiling time configurations provided in the source code +### Experimental/Unstable features: + +Use `otel_unstable` feature flag for implementation of specification with [experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.27.0/specification/document-status.md) status. This approach ensures clear demarcation and safe integration of new or evolving features. Utilize the following structure: + +```rust +#[cfg(feature = "otel_unstable")] +{ + // Your feature implementation +} +``` +It's important to regularly review and remove the `otel_unstable` flag from the code once the feature becomes stable. This cleanup process is crucial to maintain the overall code quality and to ensure that stable features are accurately reflected in the main build. + +### Optional features: + +The potential features include: + +- Stable and non-experimental features that compliant to specification, and have a feature flag to minimize compilation size. Example: feature flags for signals (like `logs`, `traces`, `metrics`) and runtimes (`rt-tokio`, `rt-tokio-current-thread`, `rt-async-std`). +- Stable and non-experimental features, although not part of the specification, are crucial for enhancing the tracing/log crate's functionality or boosting performance. These features are also subject to discussion and approval by the OpenTelemetry Rust Maintainers. An example of such a feature is `logs_level_enabled`. + +All such features should adhere to naming convention `_` + ## Style Guide - Run `cargo clippy --all` - this will catch common mistakes and improve diff --git a/examples/metrics-basic/Cargo.toml b/examples/metrics-basic/Cargo.toml index 1e33cdfd7e..d505b8cbd5 100644 --- a/examples/metrics-basic/Cargo.toml +++ b/examples/metrics-basic/Cargo.toml @@ -6,8 +6,12 @@ license = "Apache-2.0" publish = false [dependencies] -opentelemetry = { path = "../../opentelemetry", features = ["metrics"] } +opentelemetry = { path = "../../opentelemetry", features = ["metrics", "otel_unstable"] } opentelemetry_sdk = { path = "../../opentelemetry-sdk", features = ["metrics", "rt-tokio"] } opentelemetry-stdout = { path = "../../opentelemetry-stdout", features = ["metrics"]} tokio = { version = "1.0", features = ["full"] } -serde_json = {version = "1.0"} \ No newline at end of file +serde_json = {version = "1.0"} + +[features] +default = ["otel_unstable"] +otel_unstable = ["opentelemetry/otel_unstable"] diff --git a/examples/metrics-basic/src/main.rs b/examples/metrics-basic/src/main.rs index c3307d1f3a..78dda47cad 100644 --- a/examples/metrics-basic/src/main.rs +++ b/examples/metrics-basic/src/main.rs @@ -110,17 +110,37 @@ async fn main() -> Result<(), Box> { // Note that there is no ObservableHistogram instrument. + // Create a Gauge Instrument. + // Note that the Gauge instrument is experimental, and can be changed/removed in the future releases. + #[cfg(feature = "otel_unstable")] + { + let gauge = meter + .f64_gauge("my_gauge") + .with_description("A gauge set to 1.0") + .with_unit(Unit::new("myunit")) + .init(); + + gauge.record( + 1.0, + [ + KeyValue::new("mykey1", "myvalue1"), + KeyValue::new("mykey2", "myvalue2"), + ] + .as_ref(), + ); + } + // Create a ObservableGauge instrument and register a callback that reports the measurement. - let gauge = meter - .f64_observable_gauge("my_gauge") - .with_description("A gauge set to 1.0") + let observable_gauge = meter + .f64_observable_gauge("my_observable_gauge") + .with_description("An observable gauge set to 1.0") .with_unit(Unit::new("myunit")) .init(); // Register a callback that reports the measurement. - meter.register_callback(&[gauge.as_any()], move |observer| { + meter.register_callback(&[observable_gauge.as_any()], move |observer| { observer.observe_f64( - &gauge, + &observable_gauge, 1.0, [ KeyValue::new("mykey1", "myvalue1"), @@ -130,8 +150,6 @@ async fn main() -> Result<(), Box> { ) })?; - // Note that Gauge only has a Observable version. - // Metrics are exported by default every 30 seconds when using stdout exporter, // however shutting down the MeterProvider here instantly flushes // the metrics, instead of waiting for the 30 sec interval. diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 1b5e0b76f9..ddb2315b70 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -2,6 +2,10 @@ ## vNext +### Added + +- [#1410](https://github.com/open-telemetry/opentelemetry-rust/pull/1410) Add experimental synchronous gauge + ### Changed - **Breaking** diff --git a/opentelemetry-sdk/src/metrics/instrument.rs b/opentelemetry-sdk/src/metrics/instrument.rs index 7971d47d3c..3ecae355b5 100644 --- a/opentelemetry-sdk/src/metrics/instrument.rs +++ b/opentelemetry-sdk/src/metrics/instrument.rs @@ -2,7 +2,8 @@ use std::{any::Any, borrow::Cow, collections::HashSet, hash::Hash, marker, sync: use opentelemetry::{ metrics::{ - AsyncInstrument, MetricsError, Result, SyncCounter, SyncHistogram, SyncUpDownCounter, Unit, + AsyncInstrument, MetricsError, Result, SyncCounter, SyncGauge, SyncHistogram, + SyncUpDownCounter, Unit, }, Key, KeyValue, }; @@ -33,6 +34,11 @@ pub enum InstrumentKind { /// A group of instruments that record increasing and decreasing values in an /// asynchronous callback. ObservableUpDownCounter, + + /// a group of instruments that record current value synchronously with + /// the code path they are measuring. + Gauge, + /// /// a group of instruments that record current values in an asynchronous callback. ObservableGauge, } @@ -268,6 +274,14 @@ impl SyncUpDownCounter for ResolvedMeasures { } } +impl SyncGauge for ResolvedMeasures { + fn record(&self, val: T, attrs: &[KeyValue]) { + for measure in &self.measures { + measure.call(val, AttributeSet::from(attrs)) + } + } +} + impl SyncHistogram for ResolvedMeasures { fn record(&self, val: T, attrs: &[KeyValue]) { for measure in &self.measures { diff --git a/opentelemetry-sdk/src/metrics/meter.rs b/opentelemetry-sdk/src/metrics/meter.rs index 5eaeba7745..c801adcb0a 100644 --- a/opentelemetry-sdk/src/metrics/meter.rs +++ b/opentelemetry-sdk/src/metrics/meter.rs @@ -5,9 +5,9 @@ use opentelemetry::{ global, metrics::{ noop::{NoopAsyncInstrument, NoopRegistration}, - AsyncInstrument, Callback, CallbackRegistration, Counter, Histogram, InstrumentProvider, - MetricsError, ObservableCounter, ObservableGauge, ObservableUpDownCounter, - Observer as ApiObserver, Result, Unit, UpDownCounter, + AsyncInstrument, Callback, CallbackRegistration, Counter, Gauge, Histogram, + InstrumentProvider, MetricsError, ObservableCounter, ObservableGauge, + ObservableUpDownCounter, Observer as ApiObserver, Result, Unit, UpDownCounter, }, KeyValue, }; @@ -299,6 +299,57 @@ impl InstrumentProvider for SdkMeter { Ok(ObservableUpDownCounter::new(observable)) } + fn u64_gauge( + &self, + name: Cow<'static, str>, + description: Option>, + unit: Option, + ) -> Result> { + validate_instrument_config(name.as_ref(), unit.as_ref(), self.validation_policy)?; + let p = InstrumentResolver::new(self, &self.u64_resolver); + p.lookup( + InstrumentKind::Gauge, + name, + description, + unit.unwrap_or_default(), + ) + .map(|i| Gauge::new(Arc::new(i))) + } + + fn f64_gauge( + &self, + name: Cow<'static, str>, + description: Option>, + unit: Option, + ) -> Result> { + validate_instrument_config(name.as_ref(), unit.as_ref(), self.validation_policy)?; + let p = InstrumentResolver::new(self, &self.f64_resolver); + p.lookup( + InstrumentKind::Gauge, + name, + description, + unit.unwrap_or_default(), + ) + .map(|i| Gauge::new(Arc::new(i))) + } + + fn i64_gauge( + &self, + name: Cow<'static, str>, + description: Option>, + unit: Option, + ) -> Result> { + validate_instrument_config(name.as_ref(), unit.as_ref(), self.validation_policy)?; + let p = InstrumentResolver::new(self, &self.i64_resolver); + p.lookup( + InstrumentKind::Gauge, + name, + description, + unit.unwrap_or_default(), + ) + .map(|i| Gauge::new(Arc::new(i))) + } + fn u64_observable_gauge( &self, name: Cow<'static, str>, @@ -784,6 +835,9 @@ mod tests { .f64_observable_up_down_counter(name.into(), None, None, Vec::new()) .map(|_| ()), ); + assert(meter.u64_gauge(name.into(), None, None).map(|_| ())); + assert(meter.f64_gauge(name.into(), None, None).map(|_| ())); + assert(meter.i64_gauge(name.into(), None, None).map(|_| ())); assert( meter .u64_observable_gauge(name.into(), None, None, Vec::new()) diff --git a/opentelemetry-sdk/src/metrics/pipeline.rs b/opentelemetry-sdk/src/metrics/pipeline.rs index 6ebd7c9e05..be0dde9736 100644 --- a/opentelemetry-sdk/src/metrics/pipeline.rs +++ b/opentelemetry-sdk/src/metrics/pipeline.rs @@ -536,6 +536,7 @@ fn aggregate_fn>( /// | Histogram | ✓ | | ✓ | ✓ | ✓ | /// | Observable Counter | ✓ | | ✓ | ✓ | ✓ | /// | Observable UpDownCounter | ✓ | | ✓ | ✓ | ✓ | +/// | Gauge | ✓ | ✓ | | ✓ | ✓ | /// | Observable Gauge | ✓ | ✓ | | ✓ | ✓ | fn is_aggregator_compatible(kind: &InstrumentKind, agg: &aggregation::Aggregation) -> Result<()> { use aggregation::Aggregation; @@ -547,6 +548,7 @@ fn is_aggregator_compatible(kind: &InstrumentKind, agg: &aggregation::Aggregatio kind, InstrumentKind::Counter | InstrumentKind::UpDownCounter + | InstrumentKind::Gauge | InstrumentKind::Histogram | InstrumentKind::ObservableCounter | InstrumentKind::ObservableUpDownCounter @@ -571,12 +573,14 @@ fn is_aggregator_compatible(kind: &InstrumentKind, agg: &aggregation::Aggregatio } } Aggregation::LastValue => { - if kind == &InstrumentKind::ObservableGauge { - return Ok(()); + match kind { + InstrumentKind::Gauge | InstrumentKind::ObservableGauge => Ok(()), + _ => { + // TODO: review need for aggregation check after + // https://github.com/open-telemetry/opentelemetry-specification/issues/2710 + Err(MetricsError::Other("incompatible aggregation".into())) + } } - // TODO: review need for aggregation check after - // https://github.com/open-telemetry/opentelemetry-specification/issues/2710 - Err(MetricsError::Other("incompatible aggregation".into())) } Aggregation::Drop => Ok(()), } diff --git a/opentelemetry-sdk/src/metrics/reader.rs b/opentelemetry-sdk/src/metrics/reader.rs index 11cd9ae060..f53e507dc1 100644 --- a/opentelemetry-sdk/src/metrics/reader.rs +++ b/opentelemetry-sdk/src/metrics/reader.rs @@ -121,6 +121,7 @@ where /// * Observable Counter ⇨ Sum /// * UpDownCounter ⇨ Sum /// * Observable UpDownCounter ⇨ Sum +/// * Gauge ⇨ LastValue /// * Observable Gauge ⇨ LastValue /// * Histogram ⇨ ExplicitBucketHistogram /// @@ -144,6 +145,7 @@ impl AggregationSelector for DefaultAggregationSelector { | InstrumentKind::UpDownCounter | InstrumentKind::ObservableCounter | InstrumentKind::ObservableUpDownCounter => Aggregation::Sum, + InstrumentKind::Gauge => Aggregation::LastValue, InstrumentKind::ObservableGauge => Aggregation::LastValue, InstrumentKind::Histogram => Aggregation::ExplicitBucketHistogram { boundaries: vec![ diff --git a/opentelemetry/CHANGELOG.md b/opentelemetry/CHANGELOG.md index 77c5b52a93..b57647e5ab 100644 --- a/opentelemetry/CHANGELOG.md +++ b/opentelemetry/CHANGELOG.md @@ -2,6 +2,12 @@ ## vNext +### Added + +- [#1410](https://github.com/open-telemetry/opentelemetry-rust/pull/1410) Add experimental synchronous gauge. This is behind the feature flag, and can be enabled by enabling the feature `otel_unstable` for opentelemetry crate. + +- [#1410](https://github.com/open-telemetry/opentelemetry-rust/pull/1410) Guidelines to add new unstable/experimental features. + ### Changed - Modified `AnyValue.Map` to be backed by `HashMap` instead of custom `OrderMap`, diff --git a/opentelemetry/Cargo.toml b/opentelemetry/Cargo.toml index 957d2d6a49..2c6726db4c 100644 --- a/opentelemetry/Cargo.toml +++ b/opentelemetry/Cargo.toml @@ -38,6 +38,7 @@ metrics = [] testing = ["trace", "metrics"] logs = [] logs_level_enabled = ["logs"] +otel_unstable = [] [dev-dependencies] opentelemetry_sdk = { path = "../opentelemetry-sdk" } # for documentation tests diff --git a/opentelemetry/src/metrics/instruments/gauge.rs b/opentelemetry/src/metrics/instruments/gauge.rs index b9f082d83a..ab9fb2e05d 100644 --- a/opentelemetry/src/metrics/instruments/gauge.rs +++ b/opentelemetry/src/metrics/instruments/gauge.rs @@ -1,12 +1,76 @@ use crate::{ - metrics::{AsyncInstrument, AsyncInstrumentBuilder, MetricsError}, + metrics::{AsyncInstrument, AsyncInstrumentBuilder, InstrumentBuilder, MetricsError}, KeyValue, }; use core::fmt; use std::sync::Arc; use std::{any::Any, convert::TryFrom}; -/// An instrument that records independent readings. +/// An SDK implemented instrument that records independent values +pub trait SyncGauge { + /// Records an independent value. + fn record(&self, value: T, attributes: &[KeyValue]); +} + +/// An instrument that records independent values +#[derive(Clone)] +pub struct Gauge(Arc + Send + Sync>); + +impl fmt::Debug for Gauge +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("Gauge<{}>", std::any::type_name::())) + } +} + +impl Gauge { + /// Create a new gauge. + pub fn new(inner: Arc + Send + Sync>) -> Self { + Gauge(inner) + } + + /// Records an independent value. + pub fn record(&self, value: T, attributes: &[KeyValue]) { + self.0.record(value, attributes) + } +} + +impl TryFrom>> for Gauge { + type Error = MetricsError; + + fn try_from(builder: InstrumentBuilder<'_, Gauge>) -> Result { + builder + .meter + .instrument_provider + .u64_gauge(builder.name, builder.description, builder.unit) + } +} + +impl TryFrom>> for Gauge { + type Error = MetricsError; + + fn try_from(builder: InstrumentBuilder<'_, Gauge>) -> Result { + builder + .meter + .instrument_provider + .f64_gauge(builder.name, builder.description, builder.unit) + } +} + +impl TryFrom>> for Gauge { + type Error = MetricsError; + + fn try_from(builder: InstrumentBuilder<'_, Gauge>) -> Result { + builder + .meter + .instrument_provider + .i64_gauge(builder.name, builder.description, builder.unit) + } +} + +/// An async instrument that records independent readings. #[derive(Clone)] pub struct ObservableGauge(Arc>); diff --git a/opentelemetry/src/metrics/meter.rs b/opentelemetry/src/metrics/meter.rs index a66a77e55c..f64fae6976 100644 --- a/opentelemetry/src/metrics/meter.rs +++ b/opentelemetry/src/metrics/meter.rs @@ -3,6 +3,8 @@ use std::any::Any; use std::borrow::Cow; use std::sync::Arc; +#[cfg(feature = "otel_unstable")] +use crate::metrics::Gauge; use crate::metrics::{ AsyncInstrumentBuilder, Counter, Histogram, InstrumentBuilder, InstrumentProvider, ObservableCounter, ObservableGauge, ObservableUpDownCounter, Result, UpDownCounter, @@ -333,6 +335,39 @@ impl Meter { AsyncInstrumentBuilder::new(self, name.into()) } + /// # Experimental + /// This method is experimental and can be changed/removed in future releases. + /// creates an instrument builder for recording independent values. + #[cfg(feature = "otel_unstable")] + pub fn u64_gauge( + &self, + name: impl Into>, + ) -> InstrumentBuilder<'_, Gauge> { + InstrumentBuilder::new(self, name.into()) + } + + /// # Experimental + /// This method is experimental and can be changed/removed in future releases. + /// creates an instrument builder for recording independent values. + #[cfg(feature = "otel_unstable")] + pub fn f64_gauge( + &self, + name: impl Into>, + ) -> InstrumentBuilder<'_, Gauge> { + InstrumentBuilder::new(self, name.into()) + } + + /// # Experimental + /// This method is experimental and can be changed/removed in future releases. + /// creates an instrument builder for recording indenpendent values. + #[cfg(feature = "otel_unstable")] + pub fn i64_gauge( + &self, + name: impl Into>, + ) -> InstrumentBuilder<'_, Gauge> { + InstrumentBuilder::new(self, name.into()) + } + /// creates an instrument builder for recording the current value via callback. pub fn u64_observable_gauge( &self, diff --git a/opentelemetry/src/metrics/mod.rs b/opentelemetry/src/metrics/mod.rs index 4241363411..34ed16fa39 100644 --- a/opentelemetry/src/metrics/mod.rs +++ b/opentelemetry/src/metrics/mod.rs @@ -13,7 +13,7 @@ pub mod noop; use crate::ExportError; pub use instruments::{ counter::{Counter, ObservableCounter, SyncCounter}, - gauge::ObservableGauge, + gauge::{Gauge, ObservableGauge, SyncGauge}, histogram::{Histogram, SyncHistogram}, up_down_counter::{ObservableUpDownCounter, SyncUpDownCounter, UpDownCounter}, AsyncInstrument, AsyncInstrumentBuilder, Callback, InstrumentBuilder, @@ -179,6 +179,36 @@ pub trait InstrumentProvider { ))) } + /// creates an instrument for recording independent values. + fn u64_gauge( + &self, + _name: Cow<'static, str>, + _description: Option>, + _unit: Option, + ) -> Result> { + Ok(Gauge::new(Arc::new(noop::NoopSyncInstrument::new()))) + } + + /// creates an instrument for recording independent values. + fn f64_gauge( + &self, + _name: Cow<'static, str>, + _description: Option>, + _unit: Option, + ) -> Result> { + Ok(Gauge::new(Arc::new(noop::NoopSyncInstrument::new()))) + } + + /// creates an instrument for recording independent values. + fn i64_gauge( + &self, + _name: Cow<'static, str>, + _description: Option>, + _unit: Option, + ) -> Result> { + Ok(Gauge::new(Arc::new(noop::NoopSyncInstrument::new()))) + } + /// creates an instrument for recording the current value via callback. fn u64_observable_gauge( &self, diff --git a/opentelemetry/src/metrics/noop.rs b/opentelemetry/src/metrics/noop.rs index 361bed29e7..adf4b03da3 100644 --- a/opentelemetry/src/metrics/noop.rs +++ b/opentelemetry/src/metrics/noop.rs @@ -6,7 +6,7 @@ use crate::{ metrics::{ AsyncInstrument, CallbackRegistration, InstrumentProvider, Meter, MeterProvider, Observer, - Result, SyncCounter, SyncHistogram, SyncUpDownCounter, + Result, SyncCounter, SyncGauge, SyncHistogram, SyncUpDownCounter, }, KeyValue, }; @@ -110,6 +110,12 @@ impl SyncHistogram for NoopSyncInstrument { } } +impl SyncGauge for NoopSyncInstrument { + fn record(&self, _value: T, _attributes: &[KeyValue]) { + // Ignored + } +} + /// A no-op async instrument. #[derive(Debug, Default)] pub struct NoopAsyncInstrument {