Skip to content

Commit

Permalink
feat: add more resource detectors.
Browse files Browse the repository at this point in the history
  • Loading branch information
TommyCpp committed Jun 16, 2021
1 parent efbc842 commit 92c541b
Show file tree
Hide file tree
Showing 6 changed files with 373 additions and 8 deletions.
4 changes: 2 additions & 2 deletions opentelemetry-otlp/src/proto/grpcio/resource.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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))]
Expand Down
2 changes: 0 additions & 2 deletions opentelemetry/src/sdk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! EnvResourceDetector
//! Environment variables resource detector
//!
//! Implementation of `ResourceDetector` to extract a `Resource` from environment
//! variables.
Expand Down Expand Up @@ -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};

Expand Down
271 changes: 271 additions & 0 deletions opentelemetry/src/sdk/resource/mod.rs
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")
])
)
}
}
46 changes: 46 additions & 0 deletions opentelemetry/src/sdk/resource/os.rs
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())
);
}
}
Loading

0 comments on commit 92c541b

Please sign in to comment.