-
Notifications
You must be signed in to change notification settings - Fork 460
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
373 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Key, Value>, | ||
} | ||
|
||
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<T: IntoIterator<Item = KeyValue>>(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<Box<dyn ResourceDetector>>) -> 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<Key, Value>); | ||
|
||
impl Iterator for IntoIter { | ||
type Item = (Key, Value); | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
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::Item> { | ||
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") | ||
]) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()) | ||
); | ||
} | ||
} |
Oops, something went wrong.