diff --git a/examples/metrics-basic/src/main.rs b/examples/metrics-basic/src/main.rs index 78dda47cad..a923234273 100644 --- a/examples/metrics-basic/src/main.rs +++ b/examples/metrics-basic/src/main.rs @@ -1,4 +1,5 @@ use opentelemetry::metrics::Unit; +use opentelemetry::AttributeSet; use opentelemetry::{metrics::MeterProvider as _, KeyValue}; use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider}; use opentelemetry_sdk::{runtime, Resource}; @@ -52,11 +53,10 @@ async fn main() -> Result<(), Box> { observer.observe_u64( &observable_counter, 100, - [ + AttributeSet::from(&[ KeyValue::new("mykey1", "myvalue1"), KeyValue::new("mykey2", "myvalue2"), - ] - .as_ref(), + ]), ) })?; @@ -84,11 +84,10 @@ async fn main() -> Result<(), Box> { observer.observe_i64( &observable_up_down_counter, 100, - [ + AttributeSet::from(&[ KeyValue::new("mykey1", "myvalue1"), KeyValue::new("mykey2", "myvalue2"), - ] - .as_ref(), + ]), ) })?; @@ -142,11 +141,10 @@ async fn main() -> Result<(), Box> { observer.observe_f64( &observable_gauge, 1.0, - [ + AttributeSet::from(&[ KeyValue::new("mykey1", "myvalue1"), KeyValue::new("mykey2", "myvalue2"), - ] - .as_ref(), + ]), ) })?; diff --git a/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs b/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs index 0f320dcb8d..aa028b920b 100644 --- a/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs +++ b/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs @@ -11,6 +11,7 @@ use opentelemetry_sdk::metrics as sdkmetrics; use opentelemetry_sdk::resource; use opentelemetry_sdk::trace as sdktrace; +use opentelemetry::AttributeSet; use std::error::Error; use tracing::info; use tracing_subscriber::prelude::*; @@ -62,13 +63,13 @@ fn init_metrics() -> metrics::Result { const LEMONS_KEY: Key = Key::from_static_str("ex.com/lemons"); const ANOTHER_KEY: Key = Key::from_static_str("ex.com/another"); -static COMMON_ATTRIBUTES: Lazy<[KeyValue; 4]> = Lazy::new(|| { - [ +static COMMON_ATTRIBUTES: Lazy = Lazy::new(|| { + AttributeSet::from(&[ LEMONS_KEY.i64(10), KeyValue::new("A", "1"), KeyValue::new("B", "2"), KeyValue::new("C", "3"), - ] + ]) }); #[tokio::main] @@ -104,7 +105,7 @@ async fn main() -> Result<(), Box> { info!(target: "my-target", "hello from {}. My price is {}", "apple", 1.99); let histogram = meter.f64_histogram("ex.com.two").init(); - histogram.record(5.5, COMMON_ATTRIBUTES.as_ref()); + histogram.record(5.5, COMMON_ATTRIBUTES.clone()); global::shutdown_tracer_provider(); global::shutdown_logger_provider(); diff --git a/opentelemetry-otlp/examples/basic-otlp/src/main.rs b/opentelemetry-otlp/examples/basic-otlp/src/main.rs index 1bf5f7e421..1c252906f9 100644 --- a/opentelemetry-otlp/examples/basic-otlp/src/main.rs +++ b/opentelemetry-otlp/examples/basic-otlp/src/main.rs @@ -4,6 +4,7 @@ use opentelemetry::global; use opentelemetry::global::{logger_provider, shutdown_logger_provider, shutdown_tracer_provider}; use opentelemetry::logs::LogError; use opentelemetry::trace::TraceError; +use opentelemetry::AttributeSet; use opentelemetry::{ metrics, trace::{TraceContextExt, Tracer}, @@ -72,13 +73,13 @@ fn init_logs() -> Result { const LEMONS_KEY: Key = Key::from_static_str("lemons"); const ANOTHER_KEY: Key = Key::from_static_str("ex.com/another"); -static COMMON_ATTRIBUTES: Lazy<[KeyValue; 4]> = Lazy::new(|| { - [ +static COMMON_ATTRIBUTES: Lazy = Lazy::new(|| { + AttributeSet::from(&[ LEMONS_KEY.i64(10), KeyValue::new("A", "1"), KeyValue::new("B", "2"), KeyValue::new("C", "3"), - ] + ]) }); #[tokio::main] @@ -109,11 +110,11 @@ async fn main() -> Result<(), Box> { .init(); meter.register_callback(&[gauge.as_any()], move |observer| { - observer.observe_f64(&gauge, 1.0, COMMON_ATTRIBUTES.as_ref()) + observer.observe_f64(&gauge, 1.0, COMMON_ATTRIBUTES.clone()) })?; let histogram = meter.f64_histogram("ex.com.two").init(); - histogram.record(5.5, COMMON_ATTRIBUTES.as_ref()); + histogram.record(5.5, COMMON_ATTRIBUTES.clone()); tracer.in_span("operation", |cx| { let span = cx.span(); diff --git a/opentelemetry-prometheus/examples/hyper.rs b/opentelemetry-prometheus/examples/hyper.rs index 943ba617b6..a97bb4320e 100644 --- a/opentelemetry-prometheus/examples/hyper.rs +++ b/opentelemetry-prometheus/examples/hyper.rs @@ -4,6 +4,7 @@ use hyper::{ Body, Method, Request, Response, Server, }; use once_cell::sync::Lazy; +use opentelemetry::AttributeSet; use opentelemetry::{ metrics::{Counter, Histogram, MeterProvider as _, Unit}, KeyValue, @@ -14,7 +15,8 @@ use std::convert::Infallible; use std::sync::Arc; use std::time::SystemTime; -static HANDLER_ALL: Lazy<[KeyValue; 1]> = Lazy::new(|| [KeyValue::new("handler", "all")]); +static HANDLER_ALL: Lazy = + Lazy::new(|| AttributeSet::from(&[KeyValue::new("handler", "all")])); async fn serve_req( req: Request, @@ -23,7 +25,7 @@ async fn serve_req( println!("Receiving request at path {}", req.uri()); let request_start = SystemTime::now(); - state.http_counter.add(1, HANDLER_ALL.as_ref()); + state.http_counter.add(1, HANDLER_ALL.clone()); let response = match (req.method(), req.uri().path()) { (&Method::GET, "/metrics") => { @@ -33,7 +35,7 @@ async fn serve_req( encoder.encode(&metric_families, &mut buffer).unwrap(); state .http_body_gauge - .record(buffer.len() as u64, HANDLER_ALL.as_ref()); + .record(buffer.len() as u64, HANDLER_ALL.clone()); Response::builder() .status(200) @@ -53,7 +55,7 @@ async fn serve_req( state.http_req_histogram.record( request_start.elapsed().map_or(0.0, |d| d.as_secs_f64()), - &[], + AttributeSet::default(), ); Ok(response) } diff --git a/opentelemetry-prometheus/src/lib.rs b/opentelemetry-prometheus/src/lib.rs index 6ff87d3ed0..a28061e513 100644 --- a/opentelemetry-prometheus/src/lib.rs +++ b/opentelemetry-prometheus/src/lib.rs @@ -3,13 +3,14 @@ //! [Prometheus]: https://prometheus.io //! //! ``` -//! use opentelemetry::{metrics::MeterProvider, KeyValue}; +//! use opentelemetry::{AttributeSet, metrics::MeterProvider, KeyValue}; //! use opentelemetry_sdk::metrics::SdkMeterProvider; //! use prometheus::{Encoder, TextEncoder}; //! //! # fn main() -> Result<(), Box> { //! //! // create a new prometheus registry +//! use opentelemetry::AttributeSet; //! let registry = prometheus::Registry::new(); //! //! // configure OpenTelemetry to use this registry @@ -31,8 +32,10 @@ //! .with_description("Records values") //! .init(); //! -//! counter.add(100, &[KeyValue::new("key", "value")]); -//! histogram.record(100, &[KeyValue::new("key", "value")]); +//! let attributes = AttributeSet::from(&[KeyValue::new("key", "value")]); +//! +//! counter.add(100, attributes.clone()); +//! histogram.record(100, attributes); //! //! // Encode data as text or protobuf //! let encoder = TextEncoder::new(); diff --git a/opentelemetry-prometheus/tests/integration_test.rs b/opentelemetry-prometheus/tests/integration_test.rs index 0786b1a4c0..b81e4ec241 100644 --- a/opentelemetry-prometheus/tests/integration_test.rs +++ b/opentelemetry-prometheus/tests/integration_test.rs @@ -3,6 +3,7 @@ use std::path::Path; use std::time::Duration; use opentelemetry::metrics::{Meter, MeterProvider as _, Unit}; +use opentelemetry::AttributeSet; use opentelemetry::Key; use opentelemetry::KeyValue; use opentelemetry_prometheus::ExporterBuilder; @@ -44,27 +45,29 @@ fn prometheus_exporter_integration() { name: "counter", expected_file: "counter.txt", record_metrics: Box::new(|meter| { - let attrs = vec![ + let attrs = AttributeSet::from(&[ Key::new("A").string("B"), Key::new("C").string("D"), Key::new("E").bool(true), Key::new("F").i64(42), - ]; + ]); + let counter = meter .f64_counter("foo") .with_description("a simple counter") .with_unit(Unit::new("ms")) .init(); - counter.add(5.0, &attrs); - counter.add(10.3, &attrs); - counter.add(9.0, &attrs); - let attrs2 = vec![ + counter.add(5.0, attrs.clone()); + counter.add(10.3, attrs.clone()); + counter.add(9.0, attrs); + + let attrs2 = AttributeSet::from(&[ Key::new("A").string("D"), Key::new("C").string("B"), Key::new("E").bool(true), Key::new("F").i64(42), - ]; - counter.add(5.0, &attrs2); + ]); + counter.add(5.0, attrs2); }), ..Default::default() }, @@ -73,27 +76,28 @@ fn prometheus_exporter_integration() { expected_file: "counter_disabled_suffix.txt", builder: ExporterBuilder::default().without_counter_suffixes(), record_metrics: Box::new(|meter| { - let attrs = vec![ + let attrs = AttributeSet::from(&[ Key::new("A").string("B"), Key::new("C").string("D"), Key::new("E").bool(true), Key::new("F").i64(42), - ]; + ]); + let counter = meter .f64_counter("foo") .with_description("a simple counter without a total suffix") .with_unit(Unit::new("ms")) .init(); - counter.add(5.0, &attrs); - counter.add(10.3, &attrs); - counter.add(9.0, &attrs); - let attrs2 = vec![ + counter.add(5.0, attrs.clone()); + counter.add(10.3, attrs.clone()); + counter.add(9.0, attrs); + let attrs2 = AttributeSet::from(&[ Key::new("A").string("D"), Key::new("C").string("B"), Key::new("E").bool(true), Key::new("F").i64(42), - ]; - counter.add(5.0, &attrs2); + ]); + counter.add(5.0, attrs2); }), ..Default::default() }, @@ -101,14 +105,15 @@ fn prometheus_exporter_integration() { name: "gauge", expected_file: "gauge.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = + AttributeSet::from(&[Key::new("A").string("B"), Key::new("C").string("D")]); let gauge = meter .f64_up_down_counter("bar") .with_description("a fun little gauge") .with_unit(Unit::new("1")) .init(); - gauge.add(1.0, &attrs); - gauge.add(-0.25, &attrs); + gauge.add(1.0, attrs.clone()); + gauge.add(-0.25, attrs); }), ..Default::default() }, @@ -116,16 +121,17 @@ fn prometheus_exporter_integration() { name: "histogram", expected_file: "histogram.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = + AttributeSet::from(&[Key::new("A").string("B"), Key::new("C").string("D")]); let histogram = meter .f64_histogram("histogram_baz") .with_description("a very nice histogram") .with_unit(Unit::new("By")) .init(); - histogram.record(23.0, &attrs); - histogram.record(7.0, &attrs); - histogram.record(101.0, &attrs); - histogram.record(105.0, &attrs); + histogram.record(23.0, attrs.clone()); + histogram.record(7.0, attrs.clone()); + histogram.record(101.0, attrs.clone()); + histogram.record(105.0, attrs); }), ..Default::default() }, @@ -134,23 +140,23 @@ fn prometheus_exporter_integration() { expected_file: "sanitized_labels.txt", builder: ExporterBuilder::default().without_units(), record_metrics: Box::new(|meter| { - let attrs = vec![ + let attrs = AttributeSet::from(&[ // exact match, value should be overwritten Key::new("A.B").string("X"), Key::new("A.B").string("Q"), // unintended match due to sanitization, values should be concatenated Key::new("C.D").string("Y"), Key::new("C/D").string("Z"), - ]; + ]); let counter = meter .f64_counter("foo") .with_description("a sanitary counter") // This unit is not added to .with_unit(Unit::new("By")) .init(); - counter.add(5.0, &attrs); - counter.add(10.3, &attrs); - counter.add(9.0, &attrs); + counter.add(5.0, attrs.clone()); + counter.add(10.3, attrs.clone()); + counter.add(9.0, attrs); }), ..Default::default() }, @@ -158,33 +164,34 @@ fn prometheus_exporter_integration() { name: "invalid instruments are renamed", expected_file: "sanitized_names.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = + AttributeSet::from(&[Key::new("A").string("B"), Key::new("C").string("D")]); // Valid. let mut gauge = meter .f64_up_down_counter("bar") .with_description("a fun little gauge") .init(); - gauge.add(100., &attrs); - gauge.add(-25.0, &attrs); + gauge.add(100., attrs.clone()); + gauge.add(-25.0, attrs.clone()); // Invalid, will be renamed. gauge = meter .f64_up_down_counter("invalid.gauge.name") .with_description("a gauge with an invalid name") .init(); - gauge.add(100.0, &attrs); + gauge.add(100.0, attrs.clone()); let counter = meter .f64_counter("0invalid.counter.name") .with_description("a counter with an invalid name") .init(); - counter.add(100.0, &attrs); + counter.add(100.0, attrs.clone()); let histogram = meter .f64_histogram("invalid.hist.name") .with_description("a histogram with an invalid name") .init(); - histogram.record(23.0, &attrs); + histogram.record(23.0, attrs); }), ..Default::default() }, @@ -193,19 +200,19 @@ fn prometheus_exporter_integration() { empty_resource: true, expected_file: "empty_resource.txt", record_metrics: Box::new(|meter| { - let attrs = vec![ + let attrs = AttributeSet::from(&[ Key::new("A").string("B"), Key::new("C").string("D"), Key::new("E").bool(true), Key::new("F").i64(42), - ]; + ]); let counter = meter .f64_counter("foo") .with_description("a simple counter") .init(); - counter.add(5.0, &attrs); - counter.add(10.3, &attrs); - counter.add(9.0, &attrs); + counter.add(5.0, attrs.clone()); + counter.add(10.3, attrs.clone()); + counter.add(9.0, attrs.clone()); }), ..Default::default() }, @@ -214,19 +221,19 @@ fn prometheus_exporter_integration() { custom_resource_attrs: vec![Key::new("A").string("B"), Key::new("C").string("D")], expected_file: "custom_resource.txt", record_metrics: Box::new(|meter| { - let attrs = vec![ + let attrs = AttributeSet::from(&[ Key::new("A").string("B"), Key::new("C").string("D"), Key::new("E").bool(true), Key::new("F").i64(42), - ]; + ]); let counter = meter .f64_counter("foo") .with_description("a simple counter") .init(); - counter.add(5., &attrs); - counter.add(10.3, &attrs); - counter.add(9.0, &attrs); + counter.add(5., attrs.clone()); + counter.add(10.3, attrs.clone()); + counter.add(9.0, attrs); }), ..Default::default() }, @@ -235,19 +242,19 @@ fn prometheus_exporter_integration() { builder: ExporterBuilder::default().without_target_info(), expected_file: "without_target_info.txt", record_metrics: Box::new(|meter| { - let attrs = vec![ + let attrs = AttributeSet::from(&[ Key::new("A").string("B"), Key::new("C").string("D"), Key::new("E").bool(true), Key::new("F").i64(42), - ]; + ]); let counter = meter .f64_counter("foo") .with_description("a simple counter") .init(); - counter.add(5.0, &attrs); - counter.add(10.3, &attrs); - counter.add(9.0, &attrs); + counter.add(5.0, attrs.clone()); + counter.add(10.3, attrs.clone()); + counter.add(9.0, attrs); }), ..Default::default() }, @@ -256,14 +263,15 @@ fn prometheus_exporter_integration() { builder: ExporterBuilder::default().without_scope_info(), expected_file: "without_scope_info.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = + AttributeSet::from(&[Key::new("A").string("B"), Key::new("C").string("D")]); let gauge = meter .i64_up_down_counter("bar") .with_description("a fun little gauge") .with_unit(Unit::new("1")) .init(); - gauge.add(2, &attrs); - gauge.add(-1, &attrs); + gauge.add(2, attrs.clone()); + gauge.add(-1, attrs); }), ..Default::default() }, @@ -274,14 +282,15 @@ fn prometheus_exporter_integration() { .without_target_info(), expected_file: "without_scope_and_target_info.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = + AttributeSet::from(&[Key::new("A").string("B"), Key::new("C").string("D")]); let counter = meter .u64_counter("bar") .with_description("a fun little counter") .with_unit(Unit::new("By")) .init(); - counter.add(2, &attrs); - counter.add(1, &attrs); + counter.add(2, attrs.clone()); + counter.add(1, attrs); }), ..Default::default() }, @@ -290,20 +299,20 @@ fn prometheus_exporter_integration() { builder: ExporterBuilder::default().with_namespace("test"), expected_file: "with_namespace.txt", record_metrics: Box::new(|meter| { - let attrs = vec![ + let attrs = AttributeSet::from(&[ Key::new("A").string("B"), Key::new("C").string("D"), Key::new("E").bool(true), Key::new("F").i64(42), - ]; + ]); let counter = meter .f64_counter("foo") .with_description("a simple counter") .init(); - counter.add(5.0, &attrs); - counter.add(10.3, &attrs); - counter.add(9.0, &attrs); + counter.add(5.0, attrs.clone()); + counter.add(10.3, attrs.clone()); + counter.add(9.0, attrs); }), ..Default::default() }, diff --git a/opentelemetry-sdk/benches/attribute_set.rs b/opentelemetry-sdk/benches/attribute_set.rs index 6f3360b9cf..1fb6dccc1e 100644 --- a/opentelemetry-sdk/benches/attribute_set.rs +++ b/opentelemetry-sdk/benches/attribute_set.rs @@ -1,6 +1,6 @@ use criterion::{criterion_group, criterion_main, Criterion}; +use opentelemetry::AttributeSet; use opentelemetry::KeyValue; -use opentelemetry_sdk::AttributeSet; // Run this benchmark with: // cargo bench --bench metric_counter @@ -12,7 +12,7 @@ fn criterion_benchmark(c: &mut Criterion) { fn attribute_set(c: &mut Criterion) { c.bench_function("AttributeSet_without_duplicates", |b| { b.iter(|| { - let attributes: &[KeyValue] = &[ + let attributes = &[ KeyValue::new("attribute1", "value1"), KeyValue::new("attribute2", "value2"), KeyValue::new("attribute3", "value3"), @@ -24,7 +24,7 @@ fn attribute_set(c: &mut Criterion) { c.bench_function("AttributeSet_with_duplicates", |b| { b.iter(|| { - let attributes: &[KeyValue] = &[ + let attributes = &[ KeyValue::new("attribute1", "value1"), KeyValue::new("attribute3", "value3"), KeyValue::new("attribute3", "value3"), diff --git a/opentelemetry-sdk/benches/metric.rs b/opentelemetry-sdk/benches/metric.rs index d018634e04..3c668d58a8 100644 --- a/opentelemetry-sdk/benches/metric.rs +++ b/opentelemetry-sdk/benches/metric.rs @@ -2,6 +2,7 @@ use rand::Rng; use std::sync::{Arc, Weak}; use criterion::{criterion_group, criterion_main, Bencher, Criterion}; +use opentelemetry::AttributeSet; use opentelemetry::{ metrics::{Counter, Histogram, MeterProvider as _, Result}, Key, KeyValue, @@ -166,8 +167,12 @@ fn counters(c: &mut Criterion) { let (_, cntr3) = bench_counter(None, "cumulative"); let mut group = c.benchmark_group("Counter"); - group.bench_function("AddNoAttrs", |b| b.iter(|| cntr.add(1, &[]))); - group.bench_function("AddNoAttrsDelta", |b| b.iter(|| cntr2.add(1, &[]))); + group.bench_function("AddNoAttrs", |b| { + b.iter(|| cntr.add(1, AttributeSet::default())) + }); + group.bench_function("AddNoAttrsDelta", |b| { + b.iter(|| cntr2.add(1, AttributeSet::default())) + }); group.bench_function("AddOneAttr", |b| { b.iter(|| cntr.add(1, &[KeyValue::new("K", "V")])) @@ -274,14 +279,16 @@ fn counters(c: &mut Criterion) { } group.bench_function("AddOneTillMaxAttr", |b| { - b.iter(|| cntr3.add(1, &max_attributes)) + b.iter(|| cntr3.add(1, max_attributes.as_slice())) }); for i in MAX_DATA_POINTS..MAX_DATA_POINTS * 2 { max_attributes.push(KeyValue::new(i.to_string(), i)) } - group.bench_function("AddMaxAttr", |b| b.iter(|| cntr3.add(1, &max_attributes))); + group.bench_function("AddMaxAttr", |b| { + b.iter(|| cntr3.add(1, max_attributes.as_slice())) + }); group.bench_function("AddInvalidAttr", |b| { b.iter(|| cntr.add(1, &[KeyValue::new("", "V"), KeyValue::new("K", "V")])) @@ -393,10 +400,12 @@ fn histograms(c: &mut Criterion) { format!("V,{},{},{}", bound_size, attr_size, i), )) } + + let attributes = AttributeSet::from(&attributes); let value: u64 = rng.gen_range(0..MAX_BOUND).try_into().unwrap(); group.bench_function( format!("Record{}Attrs{}bounds", attr_size, bound_size), - |b| b.iter(|| hist.record(value, &attributes)), + |b| b.iter(|| hist.record(value, attributes.clone())), ); } } @@ -415,7 +424,7 @@ fn benchmark_collect_histogram(b: &mut Bencher, n: usize) { for i in 0..n { let h = mtr.u64_histogram(format!("fake_data_{i}")).init(); - h.record(1, &[]); + h.record(1, AttributeSet::default()); } let mut rm = ResourceMetrics { diff --git a/opentelemetry-sdk/benches/metric_counter.rs b/opentelemetry-sdk/benches/metric_counter.rs index 4bb4c84e6a..9e12243fdf 100644 --- a/opentelemetry-sdk/benches/metric_counter.rs +++ b/opentelemetry-sdk/benches/metric_counter.rs @@ -1,4 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; +use opentelemetry::AttributeSet; use opentelemetry::{ metrics::{Counter, MeterProvider as _}, KeyValue, @@ -67,6 +68,19 @@ fn counter_add(c: &mut Criterion) { ); }); }); + + c.bench_function("Counter_Add_Cached_Attributes", |b| { + let attributes = AttributeSet::from(&[ + KeyValue::new("attribute2", attribute_values[0]), + KeyValue::new("attribute3", attribute_values[1]), + KeyValue::new("attribute1", attribute_values[2]), + KeyValue::new("attribute4", attribute_values[3]), + ]); + + b.iter(|| { + counter.add(1, attributes.clone()); + }); + }); } criterion_group!(benches, criterion_benchmark); diff --git a/opentelemetry-sdk/src/attributes/mod.rs b/opentelemetry-sdk/src/attributes/mod.rs deleted file mode 100644 index 1182e996fb..0000000000 --- a/opentelemetry-sdk/src/attributes/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod set; - -pub use set::AttributeSet; diff --git a/opentelemetry-sdk/src/attributes/set.rs b/opentelemetry-sdk/src/attributes/set.rs deleted file mode 100644 index ae5d5a4a73..0000000000 --- a/opentelemetry-sdk/src/attributes/set.rs +++ /dev/null @@ -1,181 +0,0 @@ -use std::collections::hash_map::DefaultHasher; -use std::collections::HashSet; -use std::{ - cmp::Ordering, - hash::{Hash, Hasher}, -}; - -use opentelemetry::{Array, Key, KeyValue, Value}; -use ordered_float::OrderedFloat; - -use crate::Resource; - -#[derive(Clone, Debug)] -struct HashKeyValue(KeyValue); - -impl Hash for HashKeyValue { - fn hash(&self, state: &mut H) { - self.0.key.hash(state); - match &self.0.value { - Value::F64(f) => OrderedFloat(*f).hash(state), - Value::Array(a) => match a { - Array::Bool(b) => b.hash(state), - Array::I64(i) => i.hash(state), - Array::F64(f) => f.iter().for_each(|f| OrderedFloat(*f).hash(state)), - Array::String(s) => s.hash(state), - }, - Value::Bool(b) => b.hash(state), - Value::I64(i) => i.hash(state), - Value::String(s) => s.hash(state), - }; - } -} - -impl PartialOrd for HashKeyValue { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for HashKeyValue { - fn cmp(&self, other: &Self) -> Ordering { - match self.0.key.cmp(&other.0.key) { - Ordering::Equal => match type_order(&self.0.value).cmp(&type_order(&other.0.value)) { - Ordering::Equal => match (&self.0.value, &other.0.value) { - (Value::F64(f), Value::F64(of)) => OrderedFloat(*f).cmp(&OrderedFloat(*of)), - (Value::Array(Array::Bool(b)), Value::Array(Array::Bool(ob))) => b.cmp(ob), - (Value::Array(Array::I64(i)), Value::Array(Array::I64(oi))) => i.cmp(oi), - (Value::Array(Array::String(s)), Value::Array(Array::String(os))) => s.cmp(os), - (Value::Array(Array::F64(f)), Value::Array(Array::F64(of))) => { - match f.len().cmp(&of.len()) { - Ordering::Equal => f - .iter() - .map(|x| OrderedFloat(*x)) - .collect::>() - .cmp(&of.iter().map(|x| OrderedFloat(*x)).collect()), - other => other, - } - } - (Value::Bool(b), Value::Bool(ob)) => b.cmp(ob), - (Value::I64(i), Value::I64(oi)) => i.cmp(oi), - (Value::String(s), Value::String(os)) => s.cmp(os), - _ => Ordering::Equal, - }, - other => other, // 2nd order by value types - }, - other => other, // 1st order by key - } - } -} - -fn type_order(v: &Value) -> u8 { - match v { - Value::Bool(_) => 1, - Value::I64(_) => 2, - Value::F64(_) => 3, - Value::String(_) => 4, - Value::Array(a) => match a { - Array::Bool(_) => 5, - Array::I64(_) => 6, - Array::F64(_) => 7, - Array::String(_) => 8, - }, - } -} - -impl PartialEq for HashKeyValue { - fn eq(&self, other: &Self) -> bool { - self.0.key == other.0.key - && match (&self.0.value, &other.0.value) { - (Value::F64(f), Value::F64(of)) => OrderedFloat(*f).eq(&OrderedFloat(*of)), - (Value::Array(Array::F64(f)), Value::Array(Array::F64(of))) => { - f.len() == of.len() - && f.iter() - .zip(of.iter()) - .all(|(f, of)| OrderedFloat(*f).eq(&OrderedFloat(*of))) - } - (non_float, other_non_float) => non_float.eq(other_non_float), - } - } -} - -impl Eq for HashKeyValue {} - -/// A unique set of attributes that can be used as instrument identifiers. -/// -/// This must implement [Hash], [PartialEq], and [Eq] so it may be used as -/// HashMap keys and other de-duplication methods. -#[derive(Clone, Default, Debug, PartialEq, Eq)] -pub struct AttributeSet(Vec, u64); - -impl From<&[KeyValue]> for AttributeSet { - fn from(values: &[KeyValue]) -> Self { - let mut seen_keys = HashSet::with_capacity(values.len()); - let vec = values - .iter() - .rev() - .filter_map(|kv| { - if seen_keys.insert(kv.key.clone()) { - Some(HashKeyValue(kv.clone())) - } else { - None - } - }) - .collect::>(); - - AttributeSet::new(vec) - } -} - -impl From<&Resource> for AttributeSet { - fn from(values: &Resource) -> Self { - let vec = values - .iter() - .map(|(key, value)| HashKeyValue(KeyValue::new(key.clone(), value.clone()))) - .collect::>(); - - AttributeSet::new(vec) - } -} - -impl AttributeSet { - fn new(mut values: Vec) -> Self { - values.sort_unstable(); - let mut hasher = DefaultHasher::new(); - values.iter().fold(&mut hasher, |mut hasher, item| { - item.hash(&mut hasher); - hasher - }); - - AttributeSet(values, hasher.finish()) - } - - /// Returns the number of elements in the set. - pub fn len(&self) -> usize { - self.0.len() - } - - /// Returns `true` if the set contains no elements. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Retains only the attributes specified by the predicate. - pub fn retain(&mut self, f: F) - where - F: Fn(&KeyValue) -> bool, - { - self.0.retain(|kv| f(&kv.0)) - } - - /// Iterate over key value pairs in the set - pub fn iter(&self) -> impl Iterator { - self.0.iter().map(|kv| (&kv.0.key, &kv.0.value)) - } -} - -impl Hash for AttributeSet { - fn hash(&self, state: &mut H) { - state.write_u64(self.1) - } -} diff --git a/opentelemetry-sdk/src/lib.rs b/opentelemetry-sdk/src/lib.rs index b021ea5bcd..aefcd6f93c 100644 --- a/opentelemetry-sdk/src/lib.rs +++ b/opentelemetry-sdk/src/lib.rs @@ -52,9 +52,10 @@ //! ### Creating instruments and recording measurements //! //! ``` -//! # #[cfg(feature = "metrics")] +//! # use opentelemetry::AttributeSet; +//! #[cfg(feature = "metrics")] //! # { -//! use opentelemetry::{global, KeyValue}; +//! use opentelemetry::{AttributeSet, global, KeyValue}; //! //! // get a meter from a provider //! let meter = global::meter("my_service"); @@ -63,7 +64,8 @@ //! let counter = meter.u64_counter("my_counter").init(); //! //! // record a measurement -//! counter.add(1, &[KeyValue::new("http.client_ip", "83.164.160.102")]); +//! let attributes = AttributeSet::from(&[KeyValue::new("http.client_ip", "83.164.160.102")]); +//! counter.add(1, attributes); //! # } //! ``` //! @@ -111,7 +113,6 @@ )] #![cfg_attr(test, deny(warnings))] -pub(crate) mod attributes; pub mod export; mod instrumentation; #[cfg(feature = "logs")] @@ -135,7 +136,6 @@ pub mod trace; #[doc(hidden)] pub mod util; -pub use attributes::*; pub use instrumentation::{InstrumentationLibrary, Scope}; #[doc(inline)] pub use resource::Resource; diff --git a/opentelemetry-sdk/src/metrics/data/mod.rs b/opentelemetry-sdk/src/metrics/data/mod.rs index e827006c95..ec429ba1f8 100644 --- a/opentelemetry-sdk/src/metrics/data/mod.rs +++ b/opentelemetry-sdk/src/metrics/data/mod.rs @@ -4,7 +4,8 @@ use std::{any, borrow::Cow, fmt, time::SystemTime}; use opentelemetry::{metrics::Unit, KeyValue}; -use crate::{attributes::AttributeSet, instrumentation::Scope, Resource}; +use crate::{instrumentation::Scope, Resource}; +use opentelemetry::AttributeSet; pub use self::temporality::Temporality; diff --git a/opentelemetry-sdk/src/metrics/instrument.rs b/opentelemetry-sdk/src/metrics/instrument.rs index 3ecae355b5..5b342fd3ff 100644 --- a/opentelemetry-sdk/src/metrics/instrument.rs +++ b/opentelemetry-sdk/src/metrics/instrument.rs @@ -5,11 +5,10 @@ use opentelemetry::{ AsyncInstrument, MetricsError, Result, SyncCounter, SyncGauge, SyncHistogram, SyncUpDownCounter, Unit, }, - Key, KeyValue, + AttributeSet, Key, }; use crate::{ - attributes::AttributeSet, instrumentation::Scope, metrics::{aggregation::Aggregation, internal::Measure}, }; @@ -259,33 +258,33 @@ pub(crate) struct ResolvedMeasures { } impl SyncCounter for ResolvedMeasures { - fn add(&self, val: T, attrs: &[KeyValue]) { + fn add(&self, val: T, attrs: AttributeSet) { for measure in &self.measures { - measure.call(val, AttributeSet::from(attrs)) + measure.call(val, attrs.clone()) } } } impl SyncUpDownCounter for ResolvedMeasures { - fn add(&self, val: T, attrs: &[KeyValue]) { + fn add(&self, val: T, attrs: AttributeSet) { for measure in &self.measures { - measure.call(val, AttributeSet::from(attrs)) + measure.call(val, attrs.clone()) } } } impl SyncGauge for ResolvedMeasures { - fn record(&self, val: T, attrs: &[KeyValue]) { + fn record(&self, val: T, attrs: AttributeSet) { for measure in &self.measures { - measure.call(val, AttributeSet::from(attrs)) + measure.call(val, attrs.clone()) } } } impl SyncHistogram for ResolvedMeasures { - fn record(&self, val: T, attrs: &[KeyValue]) { + fn record(&self, val: T, attrs: AttributeSet) { for measure in &self.measures { - measure.call(val, AttributeSet::from(attrs)) + measure.call(val, attrs.clone()) } } } @@ -377,9 +376,9 @@ impl Observable { } impl AsyncInstrument for Observable { - fn observe(&self, measurement: T, attrs: &[KeyValue]) { + fn observe(&self, measurement: T, attrs: AttributeSet) { for measure in &self.measures { - measure.call(measurement, AttributeSet::from(attrs)) + measure.call(measurement, attrs.clone()) } } diff --git a/opentelemetry-sdk/src/metrics/internal/aggregate.rs b/opentelemetry-sdk/src/metrics/internal/aggregate.rs index 08d6feec04..8486892701 100644 --- a/opentelemetry-sdk/src/metrics/internal/aggregate.rs +++ b/opentelemetry-sdk/src/metrics/internal/aggregate.rs @@ -1,12 +1,9 @@ use std::{marker, sync::Arc}; use once_cell::sync::Lazy; -use opentelemetry::KeyValue; +use opentelemetry::{AttributeSet, KeyValue}; -use crate::{ - metrics::data::{Aggregation, Gauge, Temporality}, - AttributeSet, -}; +use crate::metrics::data::{Aggregation, Gauge, Temporality}; use super::{ exponential_histogram::ExpoHistogram, @@ -17,10 +14,8 @@ use super::{ }; const STREAM_CARDINALITY_LIMIT: u32 = 2000; -pub(crate) static STREAM_OVERFLOW_ATTRIBUTE_SET: Lazy = Lazy::new(|| { - let key_values: [KeyValue; 1] = [KeyValue::new("otel.metric.overflow", "true")]; - AttributeSet::from(&key_values[..]) -}); +pub(crate) static STREAM_OVERFLOW_ATTRIBUTE_SET: Lazy = + Lazy::new(|| AttributeSet::from(&[KeyValue::new("otel.metric.overflow", "true")])); /// Checks whether aggregator has hit cardinality limit for metric streams pub(crate) fn is_under_cardinality_limit(size: usize) -> bool { @@ -96,7 +91,7 @@ impl> AggregateBuilder { let filter = self.filter.as_ref().map(Arc::clone); move |n, mut attrs: AttributeSet| { if let Some(filter) = &filter { - attrs.retain(filter.as_ref()); + attrs = attrs.clone_with(filter.as_ref()); } f.call(n, attrs) } @@ -226,7 +221,7 @@ mod tests { let (measure, agg) = AggregateBuilder::::new(None, None).last_value(); let mut a = Gauge { data_points: vec![DataPoint { - attributes: AttributeSet::from(&[KeyValue::new("a", 1)][..]), + attributes: AttributeSet::from(&[KeyValue::new("a", 1)]), start_time: Some(SystemTime::now()), time: Some(SystemTime::now()), value: 1u64, @@ -234,7 +229,7 @@ mod tests { }], }; let new_attributes = [KeyValue::new("b", 2)]; - measure.call(2, AttributeSet::from(&new_attributes[..])); + measure.call(2, AttributeSet::from(&new_attributes)); let (count, new_agg) = agg.call(Some(&mut a)); @@ -243,7 +238,7 @@ mod tests { assert_eq!(a.data_points.len(), 1); assert_eq!( a.data_points[0].attributes, - AttributeSet::from(&new_attributes[..]) + AttributeSet::from(&new_attributes) ); assert_eq!(a.data_points[0].value, 2); } @@ -256,14 +251,14 @@ mod tests { let mut a = Sum { data_points: vec![ DataPoint { - attributes: AttributeSet::from(&[KeyValue::new("a1", 1)][..]), + attributes: AttributeSet::from(&[KeyValue::new("a1", 1)]), start_time: Some(SystemTime::now()), time: Some(SystemTime::now()), value: 1u64, exemplars: vec![], }, DataPoint { - attributes: AttributeSet::from(&[KeyValue::new("a2", 2)][..]), + attributes: AttributeSet::from(&[KeyValue::new("a2", 2)]), start_time: Some(SystemTime::now()), time: Some(SystemTime::now()), value: 2u64, @@ -278,7 +273,7 @@ mod tests { is_monotonic: false, }; let new_attributes = [KeyValue::new("b", 2)]; - measure.call(3, AttributeSet::from(&new_attributes[..])); + measure.call(3, AttributeSet::from(&new_attributes)); let (count, new_agg) = agg.call(Some(&mut a)); @@ -289,7 +284,7 @@ mod tests { assert_eq!(a.data_points.len(), 1); assert_eq!( a.data_points[0].attributes, - AttributeSet::from(&new_attributes[..]) + AttributeSet::from(&new_attributes) ); assert_eq!(a.data_points[0].value, 3); } @@ -302,14 +297,14 @@ mod tests { let mut a = Sum { data_points: vec![ DataPoint { - attributes: AttributeSet::from(&[KeyValue::new("a1", 1)][..]), + attributes: AttributeSet::from(&[KeyValue::new("a1", 1)]), start_time: Some(SystemTime::now()), time: Some(SystemTime::now()), value: 1u64, exemplars: vec![], }, DataPoint { - attributes: AttributeSet::from(&[KeyValue::new("a2", 2)][..]), + attributes: AttributeSet::from(&[KeyValue::new("a2", 2)]), start_time: Some(SystemTime::now()), time: Some(SystemTime::now()), value: 2u64, @@ -324,7 +319,7 @@ mod tests { is_monotonic: false, }; let new_attributes = [KeyValue::new("b", 2)]; - measure.call(3, AttributeSet::from(&new_attributes[..])); + measure.call(3, AttributeSet::from(&new_attributes)); let (count, new_agg) = agg.call(Some(&mut a)); @@ -335,7 +330,7 @@ mod tests { assert_eq!(a.data_points.len(), 1); assert_eq!( a.data_points[0].attributes, - AttributeSet::from(&new_attributes[..]) + AttributeSet::from(&new_attributes) ); assert_eq!(a.data_points[0].value, 3); } @@ -348,7 +343,7 @@ mod tests { .explicit_bucket_histogram(vec![1.0], true, true); let mut a = Histogram { data_points: vec![HistogramDataPoint { - attributes: AttributeSet::from(&[KeyValue::new("a2", 2)][..]), + attributes: AttributeSet::from(&[KeyValue::new("a2", 2)]), start_time: SystemTime::now(), time: SystemTime::now(), count: 2, @@ -366,7 +361,7 @@ mod tests { }, }; let new_attributes = [KeyValue::new("b", 2)]; - measure.call(3, AttributeSet::from(&new_attributes[..])); + measure.call(3, AttributeSet::from(&new_attributes)); let (count, new_agg) = agg.call(Some(&mut a)); @@ -376,7 +371,7 @@ mod tests { assert_eq!(a.data_points.len(), 1); assert_eq!( a.data_points[0].attributes, - AttributeSet::from(&new_attributes[..]) + AttributeSet::from(&new_attributes) ); assert_eq!(a.data_points[0].count, 1); assert_eq!(a.data_points[0].bounds, vec![1.0]); @@ -394,7 +389,7 @@ mod tests { .exponential_bucket_histogram(4, 20, true, true); let mut a = ExponentialHistogram { data_points: vec![ExponentialHistogramDataPoint { - attributes: AttributeSet::from(&[KeyValue::new("a2", 2)][..]), + attributes: AttributeSet::from(&[KeyValue::new("a2", 2)]), start_time: SystemTime::now(), time: SystemTime::now(), count: 2, @@ -421,7 +416,7 @@ mod tests { }, }; let new_attributes = [KeyValue::new("b", 2)]; - measure.call(3, AttributeSet::from(&new_attributes[..])); + measure.call(3, AttributeSet::from(&new_attributes.clone())); let (count, new_agg) = agg.call(Some(&mut a)); @@ -431,7 +426,7 @@ mod tests { assert_eq!(a.data_points.len(), 1); assert_eq!( a.data_points[0].attributes, - AttributeSet::from(&new_attributes[..]) + AttributeSet::from(&new_attributes) ); assert_eq!(a.data_points[0].count, 1); assert_eq!(a.data_points[0].min, Some(3)); diff --git a/opentelemetry-sdk/src/metrics/internal/exponential_histogram.rs b/opentelemetry-sdk/src/metrics/internal/exponential_histogram.rs index 189b61c553..5411637e05 100644 --- a/opentelemetry-sdk/src/metrics/internal/exponential_histogram.rs +++ b/opentelemetry-sdk/src/metrics/internal/exponential_histogram.rs @@ -1,12 +1,9 @@ use std::{collections::HashMap, f64::consts::LOG2_E, sync::Mutex, time::SystemTime}; use once_cell::sync::Lazy; -use opentelemetry::metrics::MetricsError; +use opentelemetry::{metrics::MetricsError, AttributeSet}; -use crate::{ - metrics::data::{self, Aggregation, Temporality}, - AttributeSet, -}; +use crate::metrics::data::{self, Aggregation, Temporality}; use super::Number; @@ -627,7 +624,7 @@ mod tests { } fn run_min_max_sum_f64() { - let alice = AttributeSet::from(&[KeyValue::new("user", "alice")][..]); + let alice = AttributeSet::from(&[KeyValue::new("user", "alice")]); struct Expected { min: f64, max: f64, @@ -688,7 +685,7 @@ mod tests { } fn run_min_max_sum + From>() { - let alice = AttributeSet::from(&[KeyValue::new("user", "alice")][..]); + let alice = AttributeSet::from(&[KeyValue::new("user", "alice")]); struct Expected { min: T, max: T, diff --git a/opentelemetry-sdk/src/metrics/internal/histogram.rs b/opentelemetry-sdk/src/metrics/internal/histogram.rs index 45ac569e2b..75acfe3f9e 100644 --- a/opentelemetry-sdk/src/metrics/internal/histogram.rs +++ b/opentelemetry-sdk/src/metrics/internal/histogram.rs @@ -1,8 +1,8 @@ use std::{collections::HashMap, sync::Mutex, time::SystemTime}; +use crate::metrics::data::HistogramDataPoint; use crate::metrics::data::{self, Aggregation, Temporality}; -use crate::{attributes::AttributeSet, metrics::data::HistogramDataPoint}; -use opentelemetry::{global, metrics::MetricsError}; +use opentelemetry::{global, metrics::MetricsError, AttributeSet}; use super::{ aggregate::{is_under_cardinality_limit, STREAM_OVERFLOW_ATTRIBUTE_SET}, diff --git a/opentelemetry-sdk/src/metrics/internal/last_value.rs b/opentelemetry-sdk/src/metrics/internal/last_value.rs index e5b2364b5b..f5d9f8d997 100644 --- a/opentelemetry-sdk/src/metrics/internal/last_value.rs +++ b/opentelemetry-sdk/src/metrics/internal/last_value.rs @@ -4,8 +4,8 @@ use std::{ time::SystemTime, }; -use crate::{attributes::AttributeSet, metrics::data::DataPoint}; -use opentelemetry::{global, metrics::MetricsError}; +use crate::metrics::data::DataPoint; +use opentelemetry::{global, metrics::MetricsError, AttributeSet}; use super::{ aggregate::{is_under_cardinality_limit, STREAM_OVERFLOW_ATTRIBUTE_SET}, diff --git a/opentelemetry-sdk/src/metrics/internal/sum.rs b/opentelemetry-sdk/src/metrics/internal/sum.rs index 3fac77c459..b6d3233217 100644 --- a/opentelemetry-sdk/src/metrics/internal/sum.rs +++ b/opentelemetry-sdk/src/metrics/internal/sum.rs @@ -4,9 +4,8 @@ use std::{ time::SystemTime, }; -use crate::attributes::AttributeSet; use crate::metrics::data::{self, Aggregation, DataPoint, Temporality}; -use opentelemetry::{global, metrics::MetricsError}; +use opentelemetry::{global, metrics::MetricsError, AttributeSet}; use super::{ aggregate::{is_under_cardinality_limit, STREAM_OVERFLOW_ATTRIBUTE_SET}, diff --git a/opentelemetry-sdk/src/metrics/meter.rs b/opentelemetry-sdk/src/metrics/meter.rs index c801adcb0a..26501f85d7 100644 --- a/opentelemetry-sdk/src/metrics/meter.rs +++ b/opentelemetry-sdk/src/metrics/meter.rs @@ -1,6 +1,7 @@ use core::fmt; use std::{any::Any, borrow::Cow, collections::HashSet, sync::Arc}; +use opentelemetry::AttributeSet; use opentelemetry::{ global, metrics::{ @@ -9,7 +10,6 @@ use opentelemetry::{ InstrumentProvider, MetricsError, ObservableCounter, ObservableGauge, ObservableUpDownCounter, Observer as ApiObserver, Result, Unit, UpDownCounter, }, - KeyValue, }; use crate::instrumentation::Scope; @@ -647,7 +647,7 @@ impl Observer { } impl ApiObserver for Observer { - fn observe_f64(&self, inst: &dyn AsyncInstrument, measurement: f64, attrs: &[KeyValue]) { + fn observe_f64(&self, inst: &dyn AsyncInstrument, measurement: f64, attrs: AttributeSet) { if let Some(f64_obs) = inst.as_any().downcast_ref::>() { if self.f64s.contains(&f64_obs.id) { f64_obs.observe(measurement, attrs) @@ -666,7 +666,7 @@ impl ApiObserver for Observer { } } - fn observe_u64(&self, inst: &dyn AsyncInstrument, measurement: u64, attrs: &[KeyValue]) { + fn observe_u64(&self, inst: &dyn AsyncInstrument, measurement: u64, attrs: AttributeSet) { if let Some(u64_obs) = inst.as_any().downcast_ref::>() { if self.u64s.contains(&u64_obs.id) { u64_obs.observe(measurement, attrs) @@ -685,7 +685,7 @@ impl ApiObserver for Observer { } } - fn observe_i64(&self, inst: &dyn AsyncInstrument, measurement: i64, attrs: &[KeyValue]) { + fn observe_i64(&self, inst: &dyn AsyncInstrument, measurement: i64, attrs: AttributeSet) { if let Some(i64_obs) = inst.as_any().downcast_ref::>() { if self.i64s.contains(&i64_obs.id) { i64_obs.observe(measurement, attrs) diff --git a/opentelemetry-sdk/src/metrics/mod.rs b/opentelemetry-sdk/src/metrics/mod.rs index 021ee3d469..622133aa70 100644 --- a/opentelemetry-sdk/src/metrics/mod.rs +++ b/opentelemetry-sdk/src/metrics/mod.rs @@ -13,6 +13,7 @@ //! metrics::{MeterProvider, Unit}, //! KeyValue, //! }; +//! use opentelemetry::AttributeSet; //! use opentelemetry_sdk::{metrics::SdkMeterProvider, Resource}; //! //! // Generate SDK configuration, resource, views, etc @@ -31,7 +32,8 @@ //! .init(); //! //! // use instruments to record measurements -//! counter.add(10, &[KeyValue::new("rate", "standard")]); +//! let attributes = AttributeSet::from(&[KeyValue::new("rate", "standard")]); +//! counter.add(10, attributes); //! ``` //! //! [Resource]: crate::Resource @@ -62,6 +64,7 @@ pub use view::*; mod tests { use super::*; use crate::{runtime, testing::metrics::InMemoryMetricsExporter}; + use opentelemetry::AttributeSet; use opentelemetry::{ metrics::{MeterProvider as _, Unit}, KeyValue, @@ -180,9 +183,9 @@ mod tests { .with_description("my_description") .init(); - let attribute = vec![KeyValue::new("key1", "value1")]; - counter.add(10, &attribute); - counter_duplicated.add(5, &attribute); + let attribute = AttributeSet::from(&[KeyValue::new("key1", "value1")]); + counter.add(10, attribute.clone()); + counter_duplicated.add(5, attribute); meter_provider.force_flush().unwrap(); @@ -266,7 +269,6 @@ mod tests { // "multi_thread" tokio flavor must be used else flush won't // be able to make progress! #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - #[ignore = "Spatial aggregation is not yet implemented."] async fn spatial_aggregation_when_view_drops_attributes_observable_counter() { // cargo test spatial_aggregation_when_view_drops_attributes_observable_counter --features=metrics,testing @@ -299,7 +301,8 @@ mod tests { KeyValue::new("statusCode", "200"), KeyValue::new("verb", "get"), ] - .as_ref(), + .as_slice() + .into(), ); observer.observe_u64( @@ -309,7 +312,8 @@ mod tests { KeyValue::new("statusCode", "200"), KeyValue::new("verb", "post"), ] - .as_ref(), + .as_slice() + .into(), ); observer.observe_u64( @@ -319,7 +323,8 @@ mod tests { KeyValue::new("statusCode", "500"), KeyValue::new("verb", "get"), ] - .as_ref(), + .as_slice() + .into(), ); }) .expect("Expected to register callback"); diff --git a/opentelemetry-sdk/src/resource/mod.rs b/opentelemetry-sdk/src/resource/mod.rs index 79ce0122eb..cf6f18440a 100644 --- a/opentelemetry-sdk/src/resource/mod.rs +++ b/opentelemetry-sdk/src/resource/mod.rs @@ -30,6 +30,7 @@ pub use os::OsResourceDetector; pub use process::ProcessResourceDetector; pub use telemetry::TelemetryResourceDetector; +use opentelemetry::AttributeSet; use opentelemetry::{Key, KeyValue, Value}; use std::borrow::Cow; use std::collections::{hash_map, HashMap}; @@ -190,6 +191,16 @@ impl Resource { pub fn get(&self, key: Key) -> Option { self.attrs.get(&key).cloned() } + + /// Creates a new attribute set from the resource's current attributes + pub fn to_attribute_set(&self) -> AttributeSet { + let key_values = self + .iter() + .map(|(key, value)| KeyValue::new(key.clone(), value.clone())) + .collect::>(); + + AttributeSet::from(&key_values) + } } /// An owned iterator over the entries of a `Resource`. @@ -365,4 +376,24 @@ mod tests { }, ) } + + #[test] + fn can_create_attribute_set_from_resource() { + let resource = Resource::new([KeyValue::new("key1", "value1"), KeyValue::new("key2", 3)]); + + let set = resource.to_attribute_set(); + let mut kvs = set.iter().collect::>(); + + assert_eq!(kvs.len(), 2, "Incorrect number of attributes"); + + kvs.sort_by(|kv1, kv2| kv1.0.cmp(kv2.0)); + assert_eq!(kvs[0].0, &Key::from("key1"), "Unexpected first key"); + assert_eq!( + kvs[0].1, + &Value::String("value1".into()), + "Unexpected first value" + ); + assert_eq!(kvs[1].0, &Key::from("key2"), "Unexpected second key"); + assert_eq!(kvs[1].1, &Value::I64(3), "Unexpected second value"); + } } diff --git a/opentelemetry-sdk/src/testing/metrics/in_memory_exporter.rs b/opentelemetry-sdk/src/testing/metrics/in_memory_exporter.rs index d28cd4062f..3edff3d2dd 100644 --- a/opentelemetry-sdk/src/testing/metrics/in_memory_exporter.rs +++ b/opentelemetry-sdk/src/testing/metrics/in_memory_exporter.rs @@ -29,6 +29,7 @@ use std::sync::{Arc, Mutex}; /// ``` ///# use opentelemetry_sdk::{metrics, runtime}; ///# use opentelemetry::{KeyValue}; +///# use opentelemetry::AttributeSet; ///# use opentelemetry::metrics::MeterProvider; ///# use opentelemetry_sdk::testing::metrics::InMemoryMetricsExporter; ///# use opentelemetry_sdk::metrics::PeriodicReader; @@ -46,7 +47,8 @@ use std::sync::{Arc, Mutex}; /// // Create and record metrics using the MeterProvider /// let meter = meter_provider.meter(std::borrow::Cow::Borrowed("example")); /// let counter = meter.u64_counter("my_counter").init(); -/// counter.add(1, &[KeyValue::new("key", "value")]); +/// let attributes = AttributeSet::from(&[KeyValue::new("key", "value")]); +/// counter.add(1, attributes); /// /// meter_provider.force_flush().unwrap(); /// diff --git a/opentelemetry-stdout/src/common.rs b/opentelemetry-stdout/src/common.rs index 0098b1a6b3..0897fbb22a 100644 --- a/opentelemetry-stdout/src/common.rs +++ b/opentelemetry-stdout/src/common.rs @@ -12,8 +12,8 @@ use serde::{Serialize, Serializer}; #[derive(Debug, Serialize, Clone, Hash, Eq, PartialEq)] pub(crate) struct AttributeSet(pub BTreeMap); -impl From<&opentelemetry_sdk::AttributeSet> for AttributeSet { - fn from(value: &opentelemetry_sdk::AttributeSet) -> Self { +impl From<&opentelemetry::AttributeSet> for AttributeSet { + fn from(value: &opentelemetry::AttributeSet) -> Self { AttributeSet( value .iter() diff --git a/opentelemetry-stdout/src/logs/transform.rs b/opentelemetry-stdout/src/logs/transform.rs index 9612cf1ff6..dba3b830ca 100644 --- a/opentelemetry-stdout/src/logs/transform.rs +++ b/opentelemetry-stdout/src/logs/transform.rs @@ -4,7 +4,7 @@ use crate::common::{ as_human_readable, as_opt_human_readable, as_opt_unix_nano, as_unix_nano, KeyValue, Resource, Scope, Value, }; -use opentelemetry_sdk::AttributeSet; +use opentelemetry::AttributeSet; use serde::Serialize; /// Transformed logs data that can be serialized. @@ -26,7 +26,7 @@ impl From> for LogData { let resource: Resource = sdk_log.resource.as_ref().into(); let rl = resource_logs - .entry(sdk_log.resource.as_ref().into()) + .entry(sdk_log.resource.as_ref().to_attribute_set()) .or_insert_with(move || ResourceLogs { resource, scope_logs: Vec::with_capacity(1), diff --git a/opentelemetry-stdout/src/trace/transform.rs b/opentelemetry-stdout/src/trace/transform.rs index 07725c9483..9af26986c6 100644 --- a/opentelemetry-stdout/src/trace/transform.rs +++ b/opentelemetry-stdout/src/trace/transform.rs @@ -1,5 +1,5 @@ use crate::common::{as_human_readable, as_unix_nano, KeyValue, Resource, Scope}; -use opentelemetry_sdk::AttributeSet; +use opentelemetry::AttributeSet; use serde::{Serialize, Serializer}; use std::{borrow::Cow, collections::HashMap, time::SystemTime}; @@ -20,7 +20,7 @@ impl From> for SpanData { let resource = sdk_span.resource.as_ref().into(); let rs = resource_spans - .entry(sdk_span.resource.as_ref().into()) + .entry(sdk_span.resource.as_ref().to_attribute_set()) .or_insert_with(move || ResourceSpans { resource, scope_spans: Vec::with_capacity(1), diff --git a/opentelemetry/CHANGELOG.md b/opentelemetry/CHANGELOG.md index b57647e5ab..db83c515ae 100644 --- a/opentelemetry/CHANGELOG.md +++ b/opentelemetry/CHANGELOG.md @@ -17,6 +17,16 @@ gains, and avoids `IndexMap` dependency. This affects `body` and `attributes` of `LogRecord`. [#1353](https://github.com/open-telemetry/opentelemetry-rust/pull/1353) - Add `TextMapCompositePropagator` [#1373](https://github.com/open-telemetry/opentelemetry-rust/pull/1373) +- `Counters`, `UpDownCounters`, `Gauges`, and `Histograms` now take an + `Into` as the parameter type for recording metric values. This + allows passing in a precreated `AttributeSet` for better performance when the + same set of attributes are used across instruments. This is backward + compatible with previous calls passing in `&[KeyValue]`. + [#1421](https://github.com/open-telemetry/opentelemetry-rust/pull/1421) +- Observable instruments no longer accept `&[KeyValue]` parameters for + `observe()` calls, and only accept a precreated `AttributeSet` + value. + [#1421](https://github.com/open-telemetry/opentelemetry-rust/pull/1421) ### Removed diff --git a/opentelemetry/Cargo.toml b/opentelemetry/Cargo.toml index 388cc24cce..8a4ad4b6c0 100644 --- a/opentelemetry/Cargo.toml +++ b/opentelemetry/Cargo.toml @@ -24,6 +24,7 @@ rustdoc-args = ["--cfg", "docsrs"] futures-core = { workspace = true } futures-sink = "0.3" once_cell = "1.13.0" +ordered-float = "4.0" pin-project-lite = { workspace = true, optional = true } thiserror = { workspace = true } urlencoding = "2.1.2" diff --git a/opentelemetry/src/attributes/mod.rs b/opentelemetry/src/attributes/mod.rs new file mode 100644 index 0000000000..ca3029549b --- /dev/null +++ b/opentelemetry/src/attributes/mod.rs @@ -0,0 +1,5 @@ +//! Utilities for managing attributes for metrics + +mod set; + +pub use set::{AttributeSet, ToKeyValue}; diff --git a/opentelemetry/src/attributes/set.rs b/opentelemetry/src/attributes/set.rs new file mode 100644 index 0000000000..4f20c8fdcb --- /dev/null +++ b/opentelemetry/src/attributes/set.rs @@ -0,0 +1,406 @@ +use once_cell::sync::Lazy; +use std::collections::hash_map::DefaultHasher; +use std::collections::HashSet; +use std::sync::Arc; +use std::{ + cmp::Ordering, + hash::{Hash, Hasher}, +}; + +use crate::{Array, Key, KeyValue, Value}; +use ordered_float::OrderedFloat; + +#[derive(Clone, Debug)] +struct HashKeyValue(KeyValue); + +impl Hash for HashKeyValue { + fn hash(&self, state: &mut H) { + self.0.key.hash(state); + match &self.0.value { + Value::F64(f) => OrderedFloat(*f).hash(state), + Value::Array(a) => match a { + Array::Bool(b) => b.hash(state), + Array::I64(i) => i.hash(state), + Array::F64(f) => f.iter().for_each(|f| OrderedFloat(*f).hash(state)), + Array::String(s) => s.hash(state), + }, + Value::Bool(b) => b.hash(state), + Value::I64(i) => i.hash(state), + Value::String(s) => s.hash(state), + }; + } +} + +impl PartialOrd for HashKeyValue { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for HashKeyValue { + fn cmp(&self, other: &Self) -> Ordering { + match self.0.key.cmp(&other.0.key) { + Ordering::Equal => match type_order(&self.0.value).cmp(&type_order(&other.0.value)) { + Ordering::Equal => match (&self.0.value, &other.0.value) { + (Value::F64(f), Value::F64(of)) => OrderedFloat(*f).cmp(&OrderedFloat(*of)), + (Value::Array(Array::Bool(b)), Value::Array(Array::Bool(ob))) => b.cmp(ob), + (Value::Array(Array::I64(i)), Value::Array(Array::I64(oi))) => i.cmp(oi), + (Value::Array(Array::String(s)), Value::Array(Array::String(os))) => s.cmp(os), + (Value::Array(Array::F64(f)), Value::Array(Array::F64(of))) => { + match f.len().cmp(&of.len()) { + Ordering::Equal => f + .iter() + .map(|x| OrderedFloat(*x)) + .collect::>() + .cmp(&of.iter().map(|x| OrderedFloat(*x)).collect()), + other => other, + } + } + (Value::Bool(b), Value::Bool(ob)) => b.cmp(ob), + (Value::I64(i), Value::I64(oi)) => i.cmp(oi), + (Value::String(s), Value::String(os)) => s.cmp(os), + _ => Ordering::Equal, + }, + other => other, // 2nd order by value types + }, + other => other, // 1st order by key + } + } +} + +fn type_order(v: &Value) -> u8 { + match v { + Value::Bool(_) => 1, + Value::I64(_) => 2, + Value::F64(_) => 3, + Value::String(_) => 4, + Value::Array(a) => match a { + Array::Bool(_) => 5, + Array::I64(_) => 6, + Array::F64(_) => 7, + Array::String(_) => 8, + }, + } +} + +impl PartialEq for HashKeyValue { + fn eq(&self, other: &Self) -> bool { + self.0.key == other.0.key + && match (&self.0.value, &other.0.value) { + (Value::F64(f), Value::F64(of)) => OrderedFloat(*f).eq(&OrderedFloat(*of)), + (Value::Array(Array::F64(f)), Value::Array(Array::F64(of))) => { + f.len() == of.len() + && f.iter() + .zip(of.iter()) + .all(|(f, of)| OrderedFloat(*f).eq(&OrderedFloat(*of))) + } + (non_float, other_non_float) => non_float.eq(other_non_float), + } + } +} + +impl Eq for HashKeyValue {} + +static EMPTY_SET: Lazy> = + Lazy::new(|| Arc::new(InternalAttributeSet::new(Vec::with_capacity(0)))); + +#[derive(Eq, PartialEq, Debug)] +struct InternalAttributeSet { + key_values: Vec, + hash: u64, +} + +impl InternalAttributeSet { + fn new(mut values: Vec) -> Self { + values.sort_unstable(); + let mut hasher = DefaultHasher::new(); + values.iter().fold(&mut hasher, |mut hasher, item| { + item.hash(&mut hasher); + hasher + }); + + InternalAttributeSet { + key_values: values, + hash: hasher.finish(), + } + } +} + +impl> From for InternalAttributeSet +where + ::IntoIter: DoubleEndedIterator + ExactSizeIterator, +{ + fn from(value: I) -> Self { + let iter = value.into_iter(); + let mut seen_keys = HashSet::with_capacity(iter.len()); + let vec = iter + .into_iter() + .rev() + .filter_map(|kv| { + if seen_keys.contains(&kv.key) { + None + } else { + seen_keys.insert(kv.key.clone()); + Some(HashKeyValue(kv)) + } + }) + .collect::>(); + + InternalAttributeSet::new(vec) + } +} + +impl Hash for InternalAttributeSet { + fn hash(&self, state: &mut H) { + state.write_u64(self.hash) + } +} + +/// Trait declaring that a type can be converted into a `KeyValue` +pub trait ToKeyValue { + /// Create a `KeyValue` from the current instance. + fn to_key_value(self) -> KeyValue; +} + +impl ToKeyValue for KeyValue { + fn to_key_value(self) -> KeyValue { + self + } +} + +impl ToKeyValue for &KeyValue { + fn to_key_value(self) -> KeyValue { + self.clone() + } +} + +/// A unique set of attributes that can be used as instrument identifiers. +/// +/// Cloning of an attribute set is cheap, as all clones share a reference to the underlying +/// attribute data. +/// +/// This must implement [Hash], [PartialEq], and [Eq] so it may be used as +/// HashMap keys and other de-duplication methods. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct AttributeSet(Arc); + +impl From for AttributeSet +where + KV: ToKeyValue, + I: IntoIterator, + ::IntoIter: DoubleEndedIterator + ExactSizeIterator, +{ + fn from(values: I) -> Self { + AttributeSet(Arc::new(InternalAttributeSet::from( + values.into_iter().map(ToKeyValue::to_key_value), + ))) + } +} + +impl AttributeSet { + /// Returns the number of elements in the set. + pub fn len(&self) -> usize { + self.0.key_values.len() + } + + /// Returns `true` if the set contains no elements. + pub fn is_empty(&self) -> bool { + self.0.key_values.is_empty() + } + + /// Creates a new attribute set that retains only the attributes specified by the predicate. + pub fn clone_with(&self, f: F) -> AttributeSet + where + F: Fn(&KeyValue) -> bool, + { + let key_values = self + .0 + .key_values + .iter() + .filter(|kv| f(&kv.0)) + .cloned() + .collect::>(); + + AttributeSet(Arc::new(InternalAttributeSet::new(key_values))) + } + + /// Iterate over key value pairs in the set + pub fn iter(&self) -> impl Iterator { + self.0.key_values.iter().map(|kv| (&kv.0.key, &kv.0.value)) + } +} + +impl Default for AttributeSet { + fn default() -> Self { + AttributeSet(EMPTY_SET.clone()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::StringValue; + + #[test] + fn can_create_attribute_set_from_array() { + let array = [KeyValue::new("key1", "value1"), KeyValue::new("key2", 3)]; + + let set = AttributeSet::from(&array); + let mut kvs = set.iter().collect::>(); + + assert_eq!(kvs.len(), 2, "Incorrect number of attributes"); + + kvs.sort_by(|kv1, kv2| kv1.0.cmp(kv2.0)); + assert_eq!(kvs[0].0, &Key::from("key1"), "Unexpected first key"); + assert_eq!( + kvs[0].1, + &Value::String("value1".into()), + "Unexpected first value" + ); + assert_eq!(kvs[1].0, &Key::from("key2"), "Unexpected second key"); + assert_eq!(kvs[1].1, &Value::I64(3), "Unexpected second value"); + } + + #[test] + fn can_create_attribute_set_from_owned_vec() { + let vec = vec![KeyValue::new("key1", "value1"), KeyValue::new("key2", 3)]; + + let set = AttributeSet::from(vec); + let mut kvs = set.iter().collect::>(); + + assert_eq!(kvs.len(), 2, "Incorrect number of attributes"); + + kvs.sort_by(|kv1, kv2| kv1.0.cmp(kv2.0)); + assert_eq!(kvs[0].0, &Key::from("key1"), "Unexpected first key"); + assert_eq!( + kvs[0].1, + &Value::String("value1".into()), + "Unexpected first value" + ); + assert_eq!(kvs[1].0, &Key::from("key2"), "Unexpected second key"); + assert_eq!(kvs[1].1, &Value::I64(3), "Unexpected second value"); + } + + #[test] + fn two_sets_with_same_key_values_in_different_orders_are_equal() { + let array1 = [ + KeyValue::new("key1", "value1"), + KeyValue::new("key2", 3), + KeyValue::new("key3", Value::Array(Array::Bool(vec![true]))), + KeyValue::new("key4", Value::Array(Array::F64(vec![1.5]))), + KeyValue::new("key5", Value::Array(Array::I64(vec![15]))), + KeyValue::new( + "key6", + Value::Array(Array::String(vec![StringValue::from("test")])), + ), + ]; + + let array2 = [ + KeyValue::new( + "key6", + Value::Array(Array::String(vec![StringValue::from("test")])), + ), + KeyValue::new("key1", "value1"), + KeyValue::new("key3", Value::Array(Array::Bool(vec![true]))), + KeyValue::new("key4", Value::Array(Array::F64(vec![1.5]))), + KeyValue::new("key5", Value::Array(Array::I64(vec![15]))), + KeyValue::new("key2", 3), + ]; + + let set1 = AttributeSet::from(&array1); + let set2 = AttributeSet::from(&array2); + + assert_eq!(set1, set2); + } + + #[test] + fn two_sets_with_same_key_values_in_different_orders_have_same_hash() { + let array1 = [ + KeyValue::new("key1", "value1"), + KeyValue::new("key2", 3), + KeyValue::new("key3", Value::Array(Array::Bool(vec![true]))), + KeyValue::new("key4", Value::Array(Array::F64(vec![1.5]))), + KeyValue::new("key5", Value::Array(Array::I64(vec![15]))), + KeyValue::new( + "key6", + Value::Array(Array::String(vec![StringValue::from("test")])), + ), + ]; + + let array2 = [ + KeyValue::new( + "key6", + Value::Array(Array::String(vec![StringValue::from("test")])), + ), + KeyValue::new("key1", "value1"), + KeyValue::new("key3", Value::Array(Array::Bool(vec![true]))), + KeyValue::new("key4", Value::Array(Array::F64(vec![1.5]))), + KeyValue::new("key5", Value::Array(Array::I64(vec![15]))), + KeyValue::new("key2", 3), + ]; + + let set1 = AttributeSet::from(&array1); + let set2 = AttributeSet::from(&array2); + + let mut hasher1 = DefaultHasher::new(); + let mut hasher2 = DefaultHasher::new(); + set1.hash(&mut hasher1); + set2.hash(&mut hasher2); + + assert_eq!(hasher1.finish(), hasher2.finish()); + } + + #[test] + fn clone_with_removes_unspecified_key_values() { + let array = [ + KeyValue::new("key1", "value1"), + KeyValue::new("key2", 3), + KeyValue::new("key3", 4), + ]; + + let set = AttributeSet::from(&array); + let set2 = set.clone_with(|kv| kv.key == Key::new("key2")); + + assert_ne!(set, set2, "Both sets were unexpectedly equal"); + assert_eq!(set2.len(), 1, "Expected only one attribute in new set"); + + let kvs = set2.iter().collect::>(); + assert_eq!(kvs[0].0, &Key::from("key2"), "Unexpected key"); + assert_eq!(kvs[0].1, &Value::I64(3), "Unexpected value"); + } + + #[test] + fn len_returns_accurate_value() { + let array = [KeyValue::new("key1", "value1"), KeyValue::new("key2", 3)]; + + let set = AttributeSet::from(&array); + let kvs = set.iter().collect::>(); + + assert_eq!(set.len(), kvs.len()); + } + + #[test] + fn empty_when_no_attributes_provided() { + let set = AttributeSet::from(&[]); + assert!(set.is_empty()); + } + + #[test] + fn default_set_has_no_attributes() { + let set = AttributeSet::default(); + assert!(set.is_empty()); + assert_eq!(set.len(), 0); + } + + #[test] + fn last_key_wins_for_deduplication() { + let array = [KeyValue::new("key1", "value1"), KeyValue::new("key1", 3)]; + + let set = AttributeSet::from(&array); + let kvs = set.iter().collect::>(); + + assert_eq!(set.len(), 1, "Expected only a single key value pair"); + assert_eq!(kvs[0].0, &Key::new("key1"), "Unexpected key"); + assert_eq!(kvs[0].1, &Value::I64(3), "Unexpected value"); + } +} diff --git a/opentelemetry/src/global/mod.rs b/opentelemetry/src/global/mod.rs index 790343968c..3af9145dc5 100644 --- a/opentelemetry/src/global/mod.rs +++ b/opentelemetry/src/global/mod.rs @@ -92,7 +92,7 @@ //! # #[cfg(feature="metrics")] //! # { //! use opentelemetry::metrics::{Meter, noop::NoopMeterProvider}; -//! use opentelemetry::{global, KeyValue}; +//! use opentelemetry::{AttributeSet, global, KeyValue}; //! //! fn init_meter() { //! let provider = NoopMeterProvider::new(); @@ -108,7 +108,8 @@ //! let counter = meter.u64_counter("my_counter").init(); //! //! // record metrics -//! counter.add(1, &[KeyValue::new("mykey", "myvalue")]); +//! let attributes = AttributeSet::from(&[KeyValue::new("mykey", "myvalue")]); +//! counter.add(1, attributes); //! } //! //! // in main or other app start @@ -122,7 +123,7 @@ //! ``` //! # #[cfg(feature="metrics")] //! # { -//! use opentelemetry::{global, KeyValue}; +//! use opentelemetry::{AttributeSet, global, KeyValue}; //! //! pub fn my_traced_library_function() { //! // End users of your library will configure their global meter provider @@ -131,7 +132,8 @@ //! let counter = tracer.u64_counter("my_counter").init(); //! //! // record metrics -//! counter.add(1, &[KeyValue::new("mykey", "myvalue")]); +//! let attributes = AttributeSet::from(&[KeyValue::new("mykey", "myvalue")]); +//! counter.add(1, attributes); //! } //! # } //! ``` diff --git a/opentelemetry/src/lib.rs b/opentelemetry/src/lib.rs index 9d2088f2ac..c6753174fe 100644 --- a/opentelemetry/src/lib.rs +++ b/opentelemetry/src/lib.rs @@ -72,7 +72,7 @@ //! ``` //! # #[cfg(feature = "metrics")] //! # { -//! use opentelemetry::{global, KeyValue}; +//! use opentelemetry::{AttributeSet, global, KeyValue}; //! //! // get a meter from a provider //! let meter = global::meter("my_service"); @@ -80,8 +80,11 @@ //! // create an instrument //! let counter = meter.u64_counter("my_counter").init(); //! +//! // Form the attributes +//! let attributes = AttributeSet::from(&[KeyValue::new("http.client_ip", "83.164.160.102")]); +//! //! // record a measurement -//! counter.add(1, &[KeyValue::new("http.client_ip", "83.164.160.102")]); +//! counter.add(1, attributes); //! # } //! ``` //! @@ -206,6 +209,9 @@ pub mod global; pub mod baggage; +mod attributes; +pub use attributes::{AttributeSet, ToKeyValue}; + mod context; pub use context::{Context, ContextGuard}; diff --git a/opentelemetry/src/metrics/instruments/counter.rs b/opentelemetry/src/metrics/instruments/counter.rs index 171b6a49ea..f437e08099 100644 --- a/opentelemetry/src/metrics/instruments/counter.rs +++ b/opentelemetry/src/metrics/instruments/counter.rs @@ -1,7 +1,5 @@ -use crate::{ - metrics::{AsyncInstrument, AsyncInstrumentBuilder, InstrumentBuilder, MetricsError}, - KeyValue, -}; +use crate::attributes::AttributeSet; +use crate::metrics::{AsyncInstrument, AsyncInstrumentBuilder, InstrumentBuilder, MetricsError}; use core::fmt; use std::sync::Arc; use std::{any::Any, convert::TryFrom}; @@ -9,7 +7,7 @@ use std::{any::Any, convert::TryFrom}; /// An SDK implemented instrument that records increasing values. pub trait SyncCounter { /// Records an increment to the counter. - fn add(&self, value: T, attributes: &[KeyValue]); + fn add(&self, value: T, attributes: AttributeSet); } /// An instrument that records increasing values. @@ -32,8 +30,8 @@ impl Counter { } /// Records an increment to the counter. - pub fn add(&self, value: T, attributes: &[KeyValue]) { - self.0.add(value, attributes) + pub fn add(&self, value: T, attributes: impl Into) { + self.0.add(value, attributes.into()) } } @@ -87,7 +85,7 @@ impl ObservableCounter { /// It is only valid to call this within a callback. If called outside of the /// registered callback it should have no effect on the instrument, and an /// error will be reported via the error handler. - pub fn observe(&self, value: T, attributes: &[KeyValue]) { + pub fn observe(&self, value: T, attributes: AttributeSet) { self.0.observe(value, attributes) } @@ -98,7 +96,7 @@ impl ObservableCounter { } impl AsyncInstrument for ObservableCounter { - fn observe(&self, measurement: T, attributes: &[KeyValue]) { + fn observe(&self, measurement: T, attributes: AttributeSet) { self.0.observe(measurement, attributes) } diff --git a/opentelemetry/src/metrics/instruments/gauge.rs b/opentelemetry/src/metrics/instruments/gauge.rs index ab9fb2e05d..0f64c01018 100644 --- a/opentelemetry/src/metrics/instruments/gauge.rs +++ b/opentelemetry/src/metrics/instruments/gauge.rs @@ -1,6 +1,6 @@ use crate::{ + attributes::AttributeSet, metrics::{AsyncInstrument, AsyncInstrumentBuilder, InstrumentBuilder, MetricsError}, - KeyValue, }; use core::fmt; use std::sync::Arc; @@ -9,7 +9,7 @@ use std::{any::Any, convert::TryFrom}; /// An SDK implemented instrument that records independent values pub trait SyncGauge { /// Records an independent value. - fn record(&self, value: T, attributes: &[KeyValue]); + fn record(&self, value: T, attributes: AttributeSet); } /// An instrument that records independent values @@ -32,8 +32,8 @@ impl Gauge { } /// Records an independent value. - pub fn record(&self, value: T, attributes: &[KeyValue]) { - self.0.record(value, attributes) + pub fn record(&self, value: T, attributes: impl Into) { + self.0.record(value, attributes.into()) } } @@ -92,7 +92,7 @@ impl ObservableGauge { /// It is only valid to call this within a callback. If called outside of the /// registered callback it should have no effect on the instrument, and an /// error will be reported via the error handler. - pub fn observe(&self, measurement: T, attributes: &[KeyValue]) { + pub fn observe(&self, measurement: T, attributes: AttributeSet) { self.0.observe(measurement, attributes) } @@ -103,7 +103,7 @@ impl ObservableGauge { } impl AsyncInstrument for ObservableGauge { - fn observe(&self, measurement: M, attributes: &[KeyValue]) { + fn observe(&self, measurement: M, attributes: AttributeSet) { self.observe(measurement, attributes) } diff --git a/opentelemetry/src/metrics/instruments/histogram.rs b/opentelemetry/src/metrics/instruments/histogram.rs index c6246ebee2..d4ec5cad61 100644 --- a/opentelemetry/src/metrics/instruments/histogram.rs +++ b/opentelemetry/src/metrics/instruments/histogram.rs @@ -1,7 +1,5 @@ -use crate::{ - metrics::{InstrumentBuilder, MetricsError}, - KeyValue, -}; +use crate::attributes::AttributeSet; +use crate::metrics::{InstrumentBuilder, MetricsError}; use core::fmt; use std::convert::TryFrom; use std::sync::Arc; @@ -9,7 +7,7 @@ use std::sync::Arc; /// An SDK implemented instrument that records a distribution of values. pub trait SyncHistogram { /// Adds an additional value to the distribution. - fn record(&self, value: T, attributes: &[KeyValue]); + fn record(&self, value: T, attributes: AttributeSet); } /// An instrument that records a distribution of values. @@ -32,8 +30,8 @@ impl Histogram { } /// Adds an additional value to the distribution. - pub fn record(&self, value: T, attributes: &[KeyValue]) { - self.0.record(value, attributes) + pub fn record(&self, value: T, attributes: impl Into) { + self.0.record(value, attributes.into()) } } diff --git a/opentelemetry/src/metrics/instruments/mod.rs b/opentelemetry/src/metrics/instruments/mod.rs index 137712d735..446e60c705 100644 --- a/opentelemetry/src/metrics/instruments/mod.rs +++ b/opentelemetry/src/metrics/instruments/mod.rs @@ -1,5 +1,5 @@ +use crate::attributes::AttributeSet; use crate::metrics::{Meter, MetricsError, Result, Unit}; -use crate::KeyValue; use core::fmt; use std::any::Any; use std::borrow::Cow; @@ -17,7 +17,7 @@ pub trait AsyncInstrument: Send + Sync { /// Observes the state of the instrument. /// /// It is only valid to call this within a callback. - fn observe(&self, measurement: T, attributes: &[KeyValue]); + fn observe(&self, measurement: T, attributes: AttributeSet); /// Used for SDKs to downcast instruments in callbacks. fn as_any(&self) -> Arc; diff --git a/opentelemetry/src/metrics/instruments/up_down_counter.rs b/opentelemetry/src/metrics/instruments/up_down_counter.rs index 1134ecedad..0ac1833d55 100644 --- a/opentelemetry/src/metrics/instruments/up_down_counter.rs +++ b/opentelemetry/src/metrics/instruments/up_down_counter.rs @@ -1,7 +1,5 @@ -use crate::{ - metrics::{InstrumentBuilder, MetricsError}, - KeyValue, -}; +use crate::attributes::AttributeSet; +use crate::metrics::{InstrumentBuilder, MetricsError}; use core::fmt; use std::sync::Arc; use std::{any::Any, convert::TryFrom}; @@ -11,7 +9,7 @@ use super::{AsyncInstrument, AsyncInstrumentBuilder}; /// An SDK implemented instrument that records increasing or decreasing values. pub trait SyncUpDownCounter { /// Records an increment or decrement to the counter. - fn add(&self, value: T, attributes: &[KeyValue]); + fn add(&self, value: T, attributes: AttributeSet); } /// An instrument that records increasing or decreasing values. @@ -37,8 +35,8 @@ impl UpDownCounter { } /// Records an increment or decrement to the counter. - pub fn add(&self, value: T, attributes: &[KeyValue]) { - self.0.add(value, attributes) + pub fn add(&self, value: T, attributes: impl Into) { + self.0.add(value, attributes.into()) } } @@ -93,7 +91,7 @@ impl ObservableUpDownCounter { /// It is only valid to call this within a callback. If called outside of the /// registered callback it should have no effect on the instrument, and an /// error will be reported via the error handler. - pub fn observe(&self, value: T, attributes: &[KeyValue]) { + pub fn observe(&self, value: T, attributes: AttributeSet) { self.0.observe(value, attributes) } @@ -104,7 +102,7 @@ impl ObservableUpDownCounter { } impl AsyncInstrument for ObservableUpDownCounter { - fn observe(&self, measurement: T, attributes: &[KeyValue]) { + fn observe(&self, measurement: T, attributes: AttributeSet) { self.0.observe(measurement, attributes) } diff --git a/opentelemetry/src/metrics/meter.rs b/opentelemetry/src/metrics/meter.rs index f64fae6976..999c0e9b1f 100644 --- a/opentelemetry/src/metrics/meter.rs +++ b/opentelemetry/src/metrics/meter.rs @@ -1,3 +1,4 @@ +use crate::attributes::AttributeSet; use core::fmt; use std::any::Any; use std::borrow::Cow; @@ -71,7 +72,7 @@ pub trait MeterProvider { /// Provides access to instrument instances for recording measurements. /// /// ``` -/// use opentelemetry::{global, KeyValue}; +/// use opentelemetry::{AttributeSet, global, KeyValue}; /// /// let meter = global::meter("my-meter"); /// @@ -80,181 +81,107 @@ pub trait MeterProvider { /// // u64 Counter /// let u64_counter = meter.u64_counter("my_u64_counter").init(); /// -/// // Record measurements using the counter instrument add() -/// u64_counter.add( -/// 10, -/// [ +/// // Define the attributes the counters will use +/// let attributes = AttributeSet::from(&[ /// KeyValue::new("mykey1", "myvalue1"), /// KeyValue::new("mykey2", "myvalue2"), -/// ].as_ref() -/// ); +/// ]); +/// +/// // Record measurements using the counter instrument add() +/// u64_counter.add(10, attributes.clone()); /// /// // f64 Counter /// let f64_counter = meter.f64_counter("my_f64_counter").init(); /// /// // Record measurements using the counter instrument add() -/// f64_counter.add( -/// 3.15, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ].as_ref() -/// ); +/// f64_counter.add(3.15, attributes.clone()); /// /// // u6 observable counter /// let observable_u4_counter = meter.u64_observable_counter("my_observable_u64_counter").init(); /// /// // Register a callback to this meter for an asynchronous instrument to record measurements +/// let observer_attributes = attributes.clone(); /// meter.register_callback(&[observable_u4_counter.as_any()], move |observer| { -/// observer.observe_u64( -/// &observable_u4_counter, -/// 1, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ].as_ref(), -/// ) +/// observer.observe_u64(&observable_u4_counter, 1, observer_attributes.clone()) /// }); /// /// // f64 observable counter /// let observable_f64_counter = meter.f64_observable_counter("my_observable_f64_counter").init(); /// /// // Register a callback to this meter for an asynchronous instrument to record measurements +/// let observer_attributes = attributes.clone(); /// meter.register_callback(&[observable_f64_counter.as_any()], move |observer| { -/// observer.observe_f64( -/// &observable_f64_counter, -/// 1.55, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ].as_ref(), -/// ) +/// observer.observe_f64(&observable_f64_counter, 1.55, observer_attributes.clone()) /// }); /// /// // i64 updown counter /// let updown_i64_counter = meter.i64_up_down_counter("my_updown_i64_counter").init(); /// /// // Record measurements using the updown counter instrument add() -/// updown_i64_counter.add( -/// -10, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ].as_ref(), -/// ); +/// updown_i64_counter.add(-10, attributes.clone()); /// /// // f64 updown counter /// let updown_f64_counter = meter.f64_up_down_counter("my_updown_f64_counter").init(); /// /// // Record measurements using the updown counter instrument add() -/// updown_f64_counter.add( -/// -10.67, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ].as_ref(), -/// ); +/// updown_f64_counter.add(-10.67, attributes.clone()); /// /// // i64 observable updown counter /// let observable_i64_up_down_counter = meter.i64_observable_up_down_counter("my_observable_i64_updown_counter").init(); /// /// // Register a callback to this meter for an asynchronous instrument to record measurements +/// let observer_attributes = attributes.clone(); /// meter.register_callback(&[observable_i64_up_down_counter.as_any()], move |observer| { -/// observer.observe_i64( -/// &observable_i64_up_down_counter, -/// 1, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ].as_ref(), -/// ) +/// observer.observe_i64(&observable_i64_up_down_counter, 1, observer_attributes.clone()) /// }); /// /// // f64 observable updown counter /// let observable_f64_up_down_counter = meter.f64_observable_up_down_counter("my_observable_f64_updown_counter").init(); /// /// // Register a callback to this meter for an asynchronous instrument to record measurements +/// let observer_attributes = attributes.clone(); /// meter.register_callback(&[observable_f64_up_down_counter.as_any()], move |observer| { -/// observer.observe_f64( -/// &observable_f64_up_down_counter, -/// 1.16, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ].as_ref(), -/// ) +/// observer.observe_f64(&observable_f64_up_down_counter, 1.16, observer_attributes.clone()) /// }); /// /// // Observable f64 gauge /// let f64_gauge = meter.f64_observable_gauge("my_f64_gauge").init(); /// /// // Register a callback to this meter for an asynchronous instrument to record measurements +/// let observer_attributes = attributes.clone(); /// meter.register_callback(&[f64_gauge.as_any()], move |observer| { -/// observer.observe_f64( -/// &f64_gauge, -/// 2.32, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ].as_ref(), -/// ) +/// observer.observe_f64(&f64_gauge, 2.32, observer_attributes.clone()) /// }); /// /// // Observable i64 gauge /// let i64_gauge = meter.i64_observable_gauge("my_i64_gauge").init(); /// /// // Register a callback to this meter for an asynchronous instrument to record measurements +/// let observer_attributes = attributes.clone(); /// meter.register_callback(&[i64_gauge.as_any()], move |observer| { -/// observer.observe_i64( -/// &i64_gauge, -/// 12, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ].as_ref(), -/// ) +/// observer.observe_i64(&i64_gauge, 12, observer_attributes.clone()) /// }); /// /// // Observable u64 gauge /// let u64_gauge = meter.u64_observable_gauge("my_u64_gauge").init(); /// /// // Register a callback to this meter for an asynchronous instrument to record measurements +/// let observer_attributes = attributes.clone(); /// meter.register_callback(&[u64_gauge.as_any()], move |observer| { -/// observer.observe_u64( -/// &u64_gauge, -/// 1, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ].as_ref(), -/// ) +/// observer.observe_u64(&u64_gauge, 1, observer_attributes.clone()) /// }); /// /// // f64 histogram /// let f64_histogram = meter.f64_histogram("my_f64_histogram").init(); /// /// // Record measurements using the histogram instrument record() -/// f64_histogram.record( -/// 10.5, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ] -/// .as_ref(), -/// ); +/// f64_histogram.record(10.5, attributes.clone()); /// /// // u64 histogram /// let u64_histogram = meter.u64_histogram("my_u64_histogram").init(); /// /// // Record measurements using the histogram instrument record() -/// u64_histogram.record( -/// 12, -/// [ -/// KeyValue::new("mykey1", "myvalue1"), -/// KeyValue::new("mykey2", "myvalue2"), -/// ] -/// .as_ref(), -/// ); +/// u64_histogram.record(12, attributes); /// /// ``` #[derive(Clone)] @@ -438,13 +365,13 @@ pub trait CallbackRegistration: Send + Sync { /// Records measurements for multiple instruments in a callback. pub trait Observer { /// Records the f64 value with attributes for the observable. - fn observe_f64(&self, inst: &dyn AsyncInstrument, measurement: f64, attrs: &[KeyValue]); + fn observe_f64(&self, inst: &dyn AsyncInstrument, measurement: f64, attrs: AttributeSet); /// Records the u64 value with attributes for the observable. - fn observe_u64(&self, inst: &dyn AsyncInstrument, measurement: u64, attrs: &[KeyValue]); + fn observe_u64(&self, inst: &dyn AsyncInstrument, measurement: u64, attrs: AttributeSet); /// Records the i64 value with attributes for the observable. - fn observe_i64(&self, inst: &dyn AsyncInstrument, measurement: i64, attrs: &[KeyValue]); + fn observe_i64(&self, inst: &dyn AsyncInstrument, measurement: i64, attrs: AttributeSet); } impl fmt::Debug for Meter { diff --git a/opentelemetry/src/metrics/noop.rs b/opentelemetry/src/metrics/noop.rs index adf4b03da3..3f675ed462 100644 --- a/opentelemetry/src/metrics/noop.rs +++ b/opentelemetry/src/metrics/noop.rs @@ -3,6 +3,7 @@ //! This implementation is returned as the global Meter if no `Meter` //! has been set. It is also useful for testing purposes as it is intended //! to have minimal resource utilization and runtime impact. +use crate::attributes::AttributeSet; use crate::{ metrics::{ AsyncInstrument, CallbackRegistration, InstrumentProvider, Meter, MeterProvider, Observer, @@ -93,25 +94,25 @@ impl NoopSyncInstrument { } impl SyncCounter for NoopSyncInstrument { - fn add(&self, _value: T, _attributes: &[KeyValue]) { + fn add(&self, _value: T, _attributes: AttributeSet) { // Ignored } } impl SyncUpDownCounter for NoopSyncInstrument { - fn add(&self, _value: T, _attributes: &[KeyValue]) { + fn add(&self, _value: T, _attributes: AttributeSet) { // Ignored } } impl SyncHistogram for NoopSyncInstrument { - fn record(&self, _value: T, _attributes: &[KeyValue]) { + fn record(&self, _value: T, _attributes: AttributeSet) { // Ignored } } impl SyncGauge for NoopSyncInstrument { - fn record(&self, _value: T, _attributes: &[KeyValue]) { + fn record(&self, _value: T, _attributes: AttributeSet) { // Ignored } } @@ -130,7 +131,7 @@ impl NoopAsyncInstrument { } impl AsyncInstrument for NoopAsyncInstrument { - fn observe(&self, _value: T, _attributes: &[KeyValue]) { + fn observe(&self, _value: T, _attributes: AttributeSet) { // Ignored } diff --git a/stress/Cargo.toml b/stress/Cargo.toml index bd9c2911cd..a6383a7f6a 100644 --- a/stress/Cargo.toml +++ b/stress/Cargo.toml @@ -9,6 +9,11 @@ name = "metrics" path = "src/metrics.rs" doc = false +[[bin]] # Bin to run the metrics cached attributes stress tests +name = "metrics_cached_attrs" +path = "src/metrics_cached_attrs.rs" +doc = false + [[bin]] # Bin to run the logs stress tests name = "logs" path = "src/logs.rs" diff --git a/stress/src/metrics_cached_attrs.rs b/stress/src/metrics_cached_attrs.rs new file mode 100644 index 0000000000..867ffa2e3e --- /dev/null +++ b/stress/src/metrics_cached_attrs.rs @@ -0,0 +1,53 @@ +use lazy_static::lazy_static; +use opentelemetry::AttributeSet; +use opentelemetry::{ + metrics::{Counter, MeterProvider as _}, + KeyValue, +}; +use opentelemetry_sdk::metrics::{ManualReader, SdkMeterProvider}; +use rand::{rngs::SmallRng, Rng, SeedableRng}; +use std::borrow::Cow; + +mod throughput; + +lazy_static! { + static ref PROVIDER: SdkMeterProvider = SdkMeterProvider::builder() + .with_reader(ManualReader::builder().build()) + .build(); + static ref ATTRIBUTE_VALUES: [&'static str; 10] = [ + "value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8", "value9", + "value10" + ]; + static ref COUNTER: Counter = PROVIDER + .meter(<&str as Into>>::into("test")) + .u64_counter("hello") + .init(); + static ref ATTRIBUTE_SETS: Vec = { + let mut vec = Vec::new(); + for i0 in 0..ATTRIBUTE_VALUES.len() { + for i1 in 0..ATTRIBUTE_VALUES.len() { + for i2 in 0..ATTRIBUTE_VALUES.len() { + vec.push(AttributeSet::from(&[ + KeyValue::new("attribute1", ATTRIBUTE_VALUES[i0]), + KeyValue::new("attribute2", ATTRIBUTE_VALUES[i1]), + KeyValue::new("attribute3", ATTRIBUTE_VALUES[i2]), + ])) + } + } + } + + vec + }; +} + +fn main() { + throughput::test_throughput(test_counter); +} + +fn test_counter() { + let mut rng = SmallRng::from_entropy(); + let len = ATTRIBUTE_SETS.len(); + let index = rng.gen_range(0..len); + + COUNTER.add(1, ATTRIBUTE_SETS[index].clone()); +}