From 92c541be4650c7d6a6aa8a5aa256398d9c876bc6 Mon Sep 17 00:00:00 2001 From: "zhongyang.wu" Date: Tue, 15 Jun 2021 23:24:08 -0400 Subject: [PATCH 1/2] feat: add more resource detectors. --- .../src/proto/grpcio/resource.rs | 4 +- opentelemetry/src/sdk/mod.rs | 2 - opentelemetry/src/sdk/{ => resource}/env.rs | 7 +- opentelemetry/src/sdk/resource/mod.rs | 271 ++++++++++++++++++ opentelemetry/src/sdk/resource/os.rs | 46 +++ opentelemetry/src/sdk/resource/process.rs | 51 ++++ 6 files changed, 373 insertions(+), 8 deletions(-) rename opentelemetry/src/sdk/{ => resource}/env.rs (93%) create mode 100644 opentelemetry/src/sdk/resource/mod.rs create mode 100644 opentelemetry/src/sdk/resource/os.rs create mode 100644 opentelemetry/src/sdk/resource/process.rs diff --git a/opentelemetry-otlp/src/proto/grpcio/resource.rs b/opentelemetry-otlp/src/proto/grpcio/resource.rs index 4bf7b9f430..c1a1f265ad 100644 --- a/opentelemetry-otlp/src/proto/grpcio/resource.rs +++ b/opentelemetry-otlp/src/proto/grpcio/resource.rs @@ -1,4 +1,4 @@ -// This file is generated by rust-protobuf 2.23.0. Do not edit +// This file is generated by rust-protobuf 2.24.1. Do not edit // @generated // https://github.com/rust-lang/rust-clippy/issues/702 @@ -21,7 +21,7 @@ /// Generated files are compatible only with the same version /// of protobuf runtime. -// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_23_0; +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_24_1; #[derive(PartialEq,Clone,Default)] #[cfg_attr(feature = "with-serde", derive(::serde::Serialize, ::serde::Deserialize))] diff --git a/opentelemetry/src/sdk/mod.rs b/opentelemetry/src/sdk/mod.rs index 24fa9a0764..83ea3f2148 100644 --- a/opentelemetry/src/sdk/mod.rs +++ b/opentelemetry/src/sdk/mod.rs @@ -6,7 +6,6 @@ //! facilitates the delivery of telemetry data to storage systems //! through `Exporter`s. These can be configured on `Tracer` and //! `Meter` creation. -pub mod env; pub mod export; pub mod instrumentation; #[cfg(feature = "metrics")] @@ -20,6 +19,5 @@ pub mod resource; #[cfg_attr(docsrs, doc(cfg(feature = "trace")))] pub mod trace; -pub use env::EnvResourceDetector; pub use instrumentation::InstrumentationLibrary; pub use resource::Resource; diff --git a/opentelemetry/src/sdk/env.rs b/opentelemetry/src/sdk/resource/env.rs similarity index 93% rename from opentelemetry/src/sdk/env.rs rename to opentelemetry/src/sdk/resource/env.rs index ccbef94044..a5abd2fa1e 100644 --- a/opentelemetry/src/sdk/env.rs +++ b/opentelemetry/src/sdk/resource/env.rs @@ -1,4 +1,4 @@ -//! EnvResourceDetector +//! Environment variables resource detector //! //! Implementation of `ResourceDetector` to extract a `Resource` from environment //! variables. @@ -59,9 +59,8 @@ fn construct_otel_resources(s: String) -> Resource { #[cfg(test)] mod tests { - use crate::sdk::env::OTEL_RESOURCE_ATTRIBUTES; - use crate::sdk::resource::{Resource, ResourceDetector}; - use crate::sdk::EnvResourceDetector; + use crate::sdk::resource::env::OTEL_RESOURCE_ATTRIBUTES; + use crate::sdk::resource::{EnvResourceDetector, Resource, ResourceDetector}; use crate::KeyValue; use std::{env, time}; diff --git a/opentelemetry/src/sdk/resource/mod.rs b/opentelemetry/src/sdk/resource/mod.rs new file mode 100644 index 0000000000..52b8538c08 --- /dev/null +++ b/opentelemetry/src/sdk/resource/mod.rs @@ -0,0 +1,271 @@ +//! # Resource +//! +//! A `Resource` is an immutable representation of the entity producing telemetry. For example, a +//! process producing telemetry that is running in a container on Kubernetes has a Pod name, it is +//! in a namespace, and possibly is part of a Deployment which also has a name. All three of these +//! attributes can be included in the `Resource`. +//! +//! The primary purpose of resources as a first-class concept in the SDK is decoupling of discovery +//! of resource information from exporters. This allows for independent development and easy +//! customization for users that need to integrate with closed source environments. When used with +//! distributed tracing, a resource can be associated with the [`TracerProvider`] when it is created. +//! That association cannot be changed later. When associated with a `TracerProvider`, all `Span`s +//! produced by any `Tracer` from the provider are associated with this `Resource`. +//! +//! [`TracerProvider`]: crate::trace::TracerProvider +//! +//! # Resource detectors +//! +//! `ResourceDetector`s are used to detect resource from runtime or environmental variables. The +//! following `ResourceDetector`s are provided along with this SDK. +//! +//! - EnvResourceDetector, detect resource from environmental variables. +//! - OsResourceDetector, detect OS from runtime. +//! - ProcessResourceDetector, detect process information +mod env; +mod os; +mod process; + +pub use env::EnvResourceDetector; +pub use os::OsResourceDetector; +pub use process::ProcessResourceDetector; + +#[cfg(feature = "metrics")] +use crate::labels; +use crate::{Key, KeyValue, Value}; +#[cfg(feature = "serialize")] +use serde::{Deserialize, Serialize}; +use std::collections::{btree_map, BTreeMap}; +use std::time::Duration; + +/// Describes an entity about which identifying information and metadata is exposed. +/// +/// Items are sorted by their key, and are only overwritten if the value is an empty string. +#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))] +#[derive(Clone, Debug, PartialEq)] +pub struct Resource { + attrs: BTreeMap, +} + +impl Default for Resource { + fn default() -> Self { + Self::from_detectors( + Duration::from_secs(0), + vec![Box::new(EnvResourceDetector::new())], + ) + } +} + +impl Resource { + /// Creates an empty resource. + pub fn empty() -> Self { + Self { + attrs: Default::default(), + } + } + + /// Create a new `Resource` from key value pairs. + /// + /// Values are de-duplicated by key, and the first key-value pair with a non-empty string value + /// will be retained + pub fn new>(kvs: T) -> Self { + let mut resource = Resource::empty(); + + for kv in kvs.into_iter() { + resource.attrs.insert(kv.key, kv.value); + } + + resource + } + + /// Create a new `Resource` from resource detectors. + /// + /// timeout will be applied to each detector. + pub fn from_detectors(timeout: Duration, detectors: Vec>) -> Self { + let mut resource = Resource::empty(); + for detector in detectors { + let detected_res = detector.detect(timeout); + for (key, value) in detected_res.into_iter() { + // using insert instead of merge to avoid clone. + resource.attrs.insert(key, value); + } + } + + resource + } + + /// Create a new `Resource` by combining two resources. + /// + /// Keys from the `other` resource have priority over keys from this resource, even if the + /// updated value is empty. + pub fn merge(&self, other: &Self) -> Self { + if self.attrs.is_empty() { + return other.clone(); + } + if other.attrs.is_empty() { + return self.clone(); + } + + let mut resource = Resource::empty(); + + // attrs from self must be added first so they have priority + for (k, v) in self.attrs.iter() { + resource.attrs.insert(k.clone(), v.clone()); + } + for (k, v) in other.attrs.iter() { + resource.attrs.insert(k.clone(), v.clone()); + } + + resource + } + + /// Returns the number of attributes for this resource + pub fn len(&self) -> usize { + self.attrs.len() + } + + /// Returns `true` if the resource contains no attributes. + pub fn is_empty(&self) -> bool { + self.attrs.is_empty() + } + + /// Gets an iterator over the attributes of this resource, sorted by key. + pub fn iter(&self) -> Iter<'_> { + self.into_iter() + } + + /// Encoded labels + #[cfg(feature = "metrics")] + #[cfg_attr(docsrs, doc(cfg(feature = "metrics")))] + pub fn encoded(&self, encoder: &dyn labels::Encoder) -> String { + encoder.encode(&mut self.into_iter()) + } +} + +/// An owned iterator over the entries of a `Resource`. +#[derive(Debug)] +pub struct IntoIter(btree_map::IntoIter); + +impl Iterator for IntoIter { + type Item = (Key, Value); + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl IntoIterator for Resource { + type Item = (Key, Value); + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter(self.attrs.into_iter()) + } +} + +/// An iterator over the entries of a `Resource`. +#[derive(Debug)] +pub struct Iter<'a>(btree_map::Iter<'a, Key, Value>); + +impl<'a> Iterator for Iter<'a> { + type Item = (&'a Key, &'a Value); + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl<'a> IntoIterator for &'a Resource { + type Item = (&'a Key, &'a Value); + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + Iter(self.attrs.iter()) + } +} + +/// ResourceDetector detects OpenTelemetry resource information +/// +/// Implementations of this trait can be passed to +/// the `Resource::from_detectors` function to generate a Resource from the merged information. +pub trait ResourceDetector { + /// detect returns an initialized Resource based on gathered information. + /// + /// timeout is used in case the detection operation takes too much time. + /// + /// If source information to construct a Resource is inaccessible, an empty Resource should be returned + /// + /// If source information to construct a Resource is invalid, for example, + /// missing required values. an empty Resource should be returned. + fn detect(&self, timeout: Duration) -> Resource; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sdk::resource::EnvResourceDetector; + use std::collections::BTreeMap; + use std::{env, time}; + + #[test] + fn new_resource() { + let args_with_dupe_keys = vec![KeyValue::new("a", ""), KeyValue::new("a", "final")]; + + let mut expected_attrs = BTreeMap::new(); + expected_attrs.insert(Key::new("a"), Value::from("final")); + + assert_eq!( + Resource::new(args_with_dupe_keys), + Resource { + attrs: expected_attrs + } + ); + } + + #[test] + fn merge_resource() { + let resource_a = Resource::new(vec![ + KeyValue::new("a", ""), + KeyValue::new("b", "b-value"), + KeyValue::new("d", "d-value"), + ]); + + let resource_b = Resource::new(vec![ + KeyValue::new("a", "a-value"), + KeyValue::new("c", "c-value"), + KeyValue::new("d", ""), + ]); + + let mut expected_attrs = BTreeMap::new(); + expected_attrs.insert(Key::new("a"), Value::from("a-value")); + expected_attrs.insert(Key::new("b"), Value::from("b-value")); + expected_attrs.insert(Key::new("c"), Value::from("c-value")); + expected_attrs.insert(Key::new("d"), Value::from("")); + + assert_eq!( + resource_a.merge(&resource_b), + Resource { + attrs: expected_attrs + } + ); + } + + #[test] + fn detect_resource() { + env::set_var("OTEL_RESOURCE_ATTRIBUTES", "key=value, k = v , a= x, a=z"); + env::set_var("irrelevant".to_uppercase(), "20200810"); + + let detector = EnvResourceDetector::new(); + let resource = + Resource::from_detectors(time::Duration::from_secs(5), vec![Box::new(detector)]); + assert_eq!( + resource, + Resource::new(vec![ + KeyValue::new("key", "value"), + KeyValue::new("k", "v"), + KeyValue::new("a", "x"), + KeyValue::new("a", "z") + ]) + ) + } +} diff --git a/opentelemetry/src/sdk/resource/os.rs b/opentelemetry/src/sdk/resource/os.rs new file mode 100644 index 0000000000..f47197060b --- /dev/null +++ b/opentelemetry/src/sdk/resource/os.rs @@ -0,0 +1,46 @@ +//! OS resource detector +//! +//! Detect the runtime operating system type. +use crate::sdk::resource::ResourceDetector; +use crate::sdk::Resource; +use crate::KeyValue; +use std::env::consts::OS; +use std::time::Duration; + +/// Detect runtime operating system information. +/// +/// This detector uses Rust's [`OS constant`] to detect the operating system type and +/// maps the result to the supported value defined in [`OpenTelemetry spec`]. +/// +/// [`OS constant`]: https://doc.rust-lang.org/std/env/consts/constant.OS.html +/// [`OpenTelemetry spec`]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/os.md +#[derive(Debug)] +pub struct OsResourceDetector; + +impl ResourceDetector for OsResourceDetector { + fn detect(&self, _timeout: Duration) -> Resource { + Resource::new(vec![KeyValue::new("os.type", OS)]) + } +} + +#[cfg(test)] +mod tests { + use crate::sdk::resource::os::OsResourceDetector; + use crate::sdk::resource::ResourceDetector; + use crate::Key; + use std::time::Duration; + + #[cfg(target_os = "linux")] + #[test] + fn test_os_resource_detector() { + let resource = OsResourceDetector.detect(Duration::from_secs(0)); + assert_eq!( + resource + .iter() + .0 + .find(|(k, _v)| **k == Key::from_static_str("os.type")) + .map(|(_k, v)| v.to_string()), + Some("linux".to_string()) + ); + } +} diff --git a/opentelemetry/src/sdk/resource/process.rs b/opentelemetry/src/sdk/resource/process.rs new file mode 100644 index 0000000000..e5329efe8b --- /dev/null +++ b/opentelemetry/src/sdk/resource/process.rs @@ -0,0 +1,51 @@ +//! Process resource detector +//! +//! Detect process related information like pid, executable name. + +use crate::sdk::resource::ResourceDetector; +use crate::sdk::Resource; +use crate::{Array, KeyValue, Value}; +use std::borrow::Cow; +use std::env::args_os; +use std::process::id; +use std::time::Duration; + +/// Detect process information. +/// +/// This resource detector returns the following information: +/// +/// - process command line arguments(`process.command_args`), the full command arguments of this +/// application. +/// - OS assigned process id(`process.pid`). +#[derive(Debug)] +pub struct ProcessResourceDetector; + +impl ResourceDetector for ProcessResourceDetector { + fn detect(&self, _timeout: Duration) -> Resource { + let arguments = args_os(); + let cmd_arg_val = arguments + .into_iter() + .map(|arg| Cow::from(arg.to_string_lossy().into_owned())) + .collect::>>(); + Resource::new(vec![ + KeyValue::new( + "process.command_args", + Value::Array(Array::String(cmd_arg_val)), + ), + KeyValue::new("process.pid", id() as i64), + ]) + } +} + +#[cfg(test)] +mod tests { + use crate::sdk::resource::{ProcessResourceDetector, ResourceDetector}; + use std::time::Duration; + + #[cfg(target_os = "linux")] + #[test] + fn test_processor_resource_detector() { + let resource = ProcessResourceDetector.detect(Duration::from_secs(0)); + assert_eq!(resource.len(), 2); // we cannot assert on the values because it changes along with runtime. + } +} From eefa20d2cfb43712bbd72c43a7a7ebb0d30df882 Mon Sep 17 00:00:00 2001 From: "zhongyang.wu" Date: Tue, 15 Jun 2021 23:24:39 -0400 Subject: [PATCH 2/2] feat: update grpc generated files. --- opentelemetry-otlp/src/proto/grpcio/common.rs | 4 +- .../src/proto/grpcio/metrics.rs | 4 +- .../src/proto/grpcio/metrics_service.rs | 4 +- opentelemetry-otlp/src/proto/grpcio/trace.rs | 4 +- .../src/proto/grpcio/trace_config.rs | 4 +- .../src/proto/grpcio/trace_service.rs | 4 +- opentelemetry/src/sdk/resource.rs | 255 ------------------ 7 files changed, 12 insertions(+), 267 deletions(-) delete mode 100644 opentelemetry/src/sdk/resource.rs diff --git a/opentelemetry-otlp/src/proto/grpcio/common.rs b/opentelemetry-otlp/src/proto/grpcio/common.rs index 0dde05ad18..575fef76ea 100644 --- a/opentelemetry-otlp/src/proto/grpcio/common.rs +++ b/opentelemetry-otlp/src/proto/grpcio/common.rs @@ -1,4 +1,4 @@ -// This file is generated by rust-protobuf 2.23.0. Do not edit +// This file is generated by rust-protobuf 2.24.1. Do not edit // @generated // https://github.com/rust-lang/rust-clippy/issues/702 @@ -21,7 +21,7 @@ /// Generated files are compatible only with the same version /// of protobuf runtime. -// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_23_0; +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_24_1; #[derive(PartialEq,Clone,Default)] #[cfg_attr(feature = "with-serde", derive(::serde::Serialize, ::serde::Deserialize))] diff --git a/opentelemetry-otlp/src/proto/grpcio/metrics.rs b/opentelemetry-otlp/src/proto/grpcio/metrics.rs index 1e93bd17fd..202b34b7b9 100644 --- a/opentelemetry-otlp/src/proto/grpcio/metrics.rs +++ b/opentelemetry-otlp/src/proto/grpcio/metrics.rs @@ -1,4 +1,4 @@ -// This file is generated by rust-protobuf 2.23.0. Do not edit +// This file is generated by rust-protobuf 2.24.1. Do not edit // @generated // https://github.com/rust-lang/rust-clippy/issues/702 @@ -21,7 +21,7 @@ /// Generated files are compatible only with the same version /// of protobuf runtime. -// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_23_0; +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_24_1; #[derive(PartialEq,Clone,Default)] #[cfg_attr(feature = "with-serde", derive(::serde::Serialize, ::serde::Deserialize))] diff --git a/opentelemetry-otlp/src/proto/grpcio/metrics_service.rs b/opentelemetry-otlp/src/proto/grpcio/metrics_service.rs index a188fdd508..811b7eece8 100644 --- a/opentelemetry-otlp/src/proto/grpcio/metrics_service.rs +++ b/opentelemetry-otlp/src/proto/grpcio/metrics_service.rs @@ -1,4 +1,4 @@ -// This file is generated by rust-protobuf 2.23.0. Do not edit +// This file is generated by rust-protobuf 2.24.1. Do not edit // @generated // https://github.com/rust-lang/rust-clippy/issues/702 @@ -21,7 +21,7 @@ /// Generated files are compatible only with the same version /// of protobuf runtime. -// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_23_0; +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_24_1; #[derive(PartialEq,Clone,Default)] #[cfg_attr(feature = "with-serde", derive(::serde::Serialize, ::serde::Deserialize))] diff --git a/opentelemetry-otlp/src/proto/grpcio/trace.rs b/opentelemetry-otlp/src/proto/grpcio/trace.rs index f1f943266b..8bd5a12d5a 100644 --- a/opentelemetry-otlp/src/proto/grpcio/trace.rs +++ b/opentelemetry-otlp/src/proto/grpcio/trace.rs @@ -1,4 +1,4 @@ -// This file is generated by rust-protobuf 2.23.0. Do not edit +// This file is generated by rust-protobuf 2.24.1. Do not edit // @generated // https://github.com/rust-lang/rust-clippy/issues/702 @@ -21,7 +21,7 @@ /// Generated files are compatible only with the same version /// of protobuf runtime. -// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_23_0; +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_24_1; #[derive(PartialEq,Clone,Default)] #[cfg_attr(feature = "with-serde", derive(::serde::Serialize, ::serde::Deserialize))] diff --git a/opentelemetry-otlp/src/proto/grpcio/trace_config.rs b/opentelemetry-otlp/src/proto/grpcio/trace_config.rs index 26f8c66ab4..9987eadada 100644 --- a/opentelemetry-otlp/src/proto/grpcio/trace_config.rs +++ b/opentelemetry-otlp/src/proto/grpcio/trace_config.rs @@ -1,4 +1,4 @@ -// This file is generated by rust-protobuf 2.23.0. Do not edit +// This file is generated by rust-protobuf 2.24.1. Do not edit // @generated // https://github.com/rust-lang/rust-clippy/issues/702 @@ -21,7 +21,7 @@ /// Generated files are compatible only with the same version /// of protobuf runtime. -// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_23_0; +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_24_1; #[derive(PartialEq,Clone,Default)] #[cfg_attr(feature = "with-serde", derive(::serde::Serialize, ::serde::Deserialize))] diff --git a/opentelemetry-otlp/src/proto/grpcio/trace_service.rs b/opentelemetry-otlp/src/proto/grpcio/trace_service.rs index 6ce4e359c8..8a154361bb 100644 --- a/opentelemetry-otlp/src/proto/grpcio/trace_service.rs +++ b/opentelemetry-otlp/src/proto/grpcio/trace_service.rs @@ -1,4 +1,4 @@ -// This file is generated by rust-protobuf 2.23.0. Do not edit +// This file is generated by rust-protobuf 2.24.1. Do not edit // @generated // https://github.com/rust-lang/rust-clippy/issues/702 @@ -21,7 +21,7 @@ /// Generated files are compatible only with the same version /// of protobuf runtime. -// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_23_0; +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_24_1; #[derive(PartialEq,Clone,Default)] #[cfg_attr(feature = "with-serde", derive(::serde::Serialize, ::serde::Deserialize))] diff --git a/opentelemetry/src/sdk/resource.rs b/opentelemetry/src/sdk/resource.rs deleted file mode 100644 index a04d921777..0000000000 --- a/opentelemetry/src/sdk/resource.rs +++ /dev/null @@ -1,255 +0,0 @@ -//! # Resource -//! -//! A `Resource` is an immutable representation of the entity producing telemetry. For example, a -//! process producing telemetry that is running in a container on Kubernetes has a Pod name, it is -//! in a namespace, and possibly is part of a Deployment which also has a name. All three of these -//! attributes can be included in the `Resource`. -//! -//! The primary purpose of resources as a first-class concept in the SDK is decoupling of discovery -//! of resource information from exporters. This allows for independent development and easy -//! customization for users that need to integrate with closed source environments. When used with -//! distributed tracing, a resource can be associated with the [`TracerProvider`] when it is created. -//! That association cannot be changed later. When associated with a `TracerProvider`, all `Span`s -//! produced by any `Tracer` from the provider are associated with this `Resource`. -//! -//! [`TracerProvider`]: crate::trace::TracerProvider -#[cfg(feature = "metrics")] -use crate::labels; -use crate::sdk::EnvResourceDetector; -use crate::{Key, KeyValue, Value}; -#[cfg(feature = "serialize")] -use serde::{Deserialize, Serialize}; -use std::collections::{btree_map, BTreeMap}; -use std::time::Duration; - -/// Describes an entity about which identifying information and metadata is exposed. -/// -/// Items are sorted by their key, and are only overwritten if the value is an empty string. -#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))] -#[derive(Clone, Debug, PartialEq)] -pub struct Resource { - attrs: BTreeMap, -} - -impl Default for Resource { - fn default() -> Self { - Self::from_detectors( - Duration::from_secs(0), - vec![Box::new(EnvResourceDetector::new())], - ) - } -} - -impl Resource { - /// Creates an empty resource. - pub fn empty() -> Self { - Self { - attrs: Default::default(), - } - } - - /// Create a new `Resource` from key value pairs. - /// - /// Values are de-duplicated by key, and the first key-value pair with a non-empty string value - /// will be retained - pub fn new>(kvs: T) -> Self { - let mut resource = Resource::empty(); - - for kv in kvs.into_iter() { - resource.attrs.insert(kv.key, kv.value); - } - - resource - } - - /// Create a new `Resource` from resource detectors. - /// - /// timeout will be applied to each detector. - pub fn from_detectors(timeout: Duration, detectors: Vec>) -> Self { - let mut resource = Resource::empty(); - for detector in detectors { - let detected_res = detector.detect(timeout); - for (key, value) in detected_res.into_iter() { - // using insert instead of merge to avoid clone. - resource.attrs.insert(key, value); - } - } - - resource - } - - /// Create a new `Resource` by combining two resources. - /// - /// Keys from the `other` resource have priority over keys from this resource, even if the - /// updated value is empty. - pub fn merge(&self, other: &Self) -> Self { - if self.attrs.is_empty() { - return other.clone(); - } - if other.attrs.is_empty() { - return self.clone(); - } - - let mut resource = Resource::empty(); - - // attrs from self must be added first so they have priority - for (k, v) in self.attrs.iter() { - resource.attrs.insert(k.clone(), v.clone()); - } - for (k, v) in other.attrs.iter() { - resource.attrs.insert(k.clone(), v.clone()); - } - - resource - } - - /// Returns the number of attributes for this resource - pub fn len(&self) -> usize { - self.attrs.len() - } - - /// Returns `true` if the resource contains no attributes. - pub fn is_empty(&self) -> bool { - self.attrs.is_empty() - } - - /// Gets an iterator over the attributes of this resource, sorted by key. - pub fn iter(&self) -> Iter<'_> { - self.into_iter() - } - - /// Encoded labels - #[cfg(feature = "metrics")] - #[cfg_attr(docsrs, doc(cfg(feature = "metrics")))] - pub fn encoded(&self, encoder: &dyn labels::Encoder) -> String { - encoder.encode(&mut self.into_iter()) - } -} - -/// An owned iterator over the entries of a `Resource`. -#[derive(Debug)] -pub struct IntoIter(btree_map::IntoIter); - -impl Iterator for IntoIter { - type Item = (Key, Value); - - fn next(&mut self) -> Option { - self.0.next() - } -} - -impl IntoIterator for Resource { - type Item = (Key, Value); - type IntoIter = IntoIter; - - fn into_iter(self) -> Self::IntoIter { - IntoIter(self.attrs.into_iter()) - } -} - -/// An iterator over the entries of a `Resource`. -#[derive(Debug)] -pub struct Iter<'a>(btree_map::Iter<'a, Key, Value>); - -impl<'a> Iterator for Iter<'a> { - type Item = (&'a Key, &'a Value); - - fn next(&mut self) -> Option { - self.0.next() - } -} - -impl<'a> IntoIterator for &'a Resource { - type Item = (&'a Key, &'a Value); - type IntoIter = Iter<'a>; - - fn into_iter(self) -> Self::IntoIter { - Iter(self.attrs.iter()) - } -} - -/// ResourceDetector detects OpenTelemetry resource information -/// -/// Implementations of this trait can be passed to -/// the `Resource::from_detectors` function to generate a Resource from the merged information. -pub trait ResourceDetector { - /// detect returns an initialized Resource based on gathered information. - /// - /// timeout is used in case the detection operation takes too much time. - /// - /// If source information to construct a Resource is inaccessible, an empty Resource should be returned - /// - /// If source information to construct a Resource is invalid, for example, - /// missing required values. an empty Resource should be returned. - fn detect(&self, timeout: Duration) -> Resource; -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::sdk::EnvResourceDetector; - use std::collections::BTreeMap; - use std::{env, time}; - - #[test] - fn new_resource() { - let args_with_dupe_keys = vec![KeyValue::new("a", ""), KeyValue::new("a", "final")]; - - let mut expected_attrs = BTreeMap::new(); - expected_attrs.insert(Key::new("a"), Value::from("final")); - - assert_eq!( - Resource::new(args_with_dupe_keys), - Resource { - attrs: expected_attrs - } - ); - } - - #[test] - fn merge_resource() { - let resource_a = Resource::new(vec![ - KeyValue::new("a", ""), - KeyValue::new("b", "b-value"), - KeyValue::new("d", "d-value"), - ]); - - let resource_b = Resource::new(vec![ - KeyValue::new("a", "a-value"), - KeyValue::new("c", "c-value"), - KeyValue::new("d", ""), - ]); - - let mut expected_attrs = BTreeMap::new(); - expected_attrs.insert(Key::new("a"), Value::from("a-value")); - expected_attrs.insert(Key::new("b"), Value::from("b-value")); - expected_attrs.insert(Key::new("c"), Value::from("c-value")); - expected_attrs.insert(Key::new("d"), Value::from("")); - - assert_eq!( - resource_a.merge(&resource_b), - Resource { - attrs: expected_attrs - } - ); - } - - #[test] - fn detect_resource() { - env::set_var("OTEL_RESOURCE_ATTRIBUTES", "key=value, k = v , a= x, a=z"); - env::set_var("irrelevant".to_uppercase(), "20200810"); - - let detector = EnvResourceDetector::new(); - let resource = - Resource::from_detectors(time::Duration::from_secs(5), vec![Box::new(detector)]); - assert_eq!( - resource, - Resource::new(vec![ - KeyValue::new("key", "value"), - KeyValue::new("k", "v"), - KeyValue::new("a", "x"), - KeyValue::new("a", "z") - ]) - ) - } -}