-
Notifications
You must be signed in to change notification settings - Fork 598
fix: Metrics Views - fix a bug that causes unit, description to be lost when applying views that influence other aspects #2981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
97c47ab
e1caf26
e56c09a
51bb6d5
0ac6d72
c0644f8
27eeade
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1463,6 +1463,144 @@ | |
| ); | ||
| } | ||
|
|
||
| #[tokio::test(flavor = "multi_thread", worker_threads = 1)] | ||
| async fn view_test_rename() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These tests and the helper method need not be async. |
||
| test_view_customization( | ||
| |i| { | ||
| if i.name == "my_counter" { | ||
| Some(Stream::new().name("my_counter_renamed")) | ||
| } else { | ||
| None | ||
| } | ||
| }, | ||
| "my_counter_renamed", | ||
| "my_unit", | ||
| "my_description", | ||
| ) | ||
| .await; | ||
| } | ||
|
|
||
| #[tokio::test(flavor = "multi_thread", worker_threads = 1)] | ||
| async fn view_test_change_unit() { | ||
| test_view_customization( | ||
| |i| { | ||
| if i.name == "my_counter" { | ||
| Some(Stream::new().unit("my_unit_new")) | ||
| } else { | ||
| None | ||
| } | ||
| }, | ||
| "my_counter", | ||
| "my_unit_new", | ||
| "my_description", | ||
| ) | ||
| .await; | ||
| } | ||
|
|
||
| #[tokio::test(flavor = "multi_thread", worker_threads = 1)] | ||
| async fn view_test_change_description() { | ||
| test_view_customization( | ||
| |i| { | ||
| if i.name == "my_counter" { | ||
| Some(Stream::new().description("my_description_new")) | ||
| } else { | ||
| None | ||
| } | ||
| }, | ||
| "my_counter", | ||
| "my_unit", | ||
| "my_description_new", | ||
| ) | ||
| .await; | ||
| } | ||
|
|
||
| #[tokio::test(flavor = "multi_thread", worker_threads = 1)] | ||
| async fn view_test_match_unit() { | ||
| test_view_customization( | ||
| |i| { | ||
| if i.unit == "my_unit" { | ||
| Some(Stream::new().unit("my_unit_new")) | ||
| } else { | ||
| None | ||
| } | ||
| }, | ||
| "my_counter", | ||
| "my_unit_new", | ||
| "my_description", | ||
| ) | ||
| .await; | ||
| } | ||
|
|
||
| #[tokio::test(flavor = "multi_thread", worker_threads = 1)] | ||
| async fn view_test_match_none() { | ||
cijothomas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| test_view_customization( | ||
| |i| { | ||
| if i.name == "not_expected_to_match" { | ||
| Some(Stream::new()) | ||
| } else { | ||
| None | ||
| } | ||
| }, | ||
| "my_counter", | ||
| "my_unit", | ||
| "my_description", | ||
| ) | ||
| .await; | ||
| } | ||
|
|
||
| /// Helper function to test view customizations | ||
| async fn test_view_customization<F>( | ||
| view_function: F, | ||
| expected_name: &str, | ||
| expected_unit: &str, | ||
| expected_description: &str, | ||
| ) where | ||
| F: Fn(&Instrument) -> Option<Stream> + Send + Sync + 'static, | ||
| { | ||
| // Run this test with stdout enabled to see output. | ||
| // cargo test view_test_* --all-features -- --nocapture | ||
|
|
||
| // Arrange | ||
| let exporter = InMemoryMetricExporter::default(); | ||
| let meter_provider = SdkMeterProvider::builder() | ||
| .with_periodic_exporter(exporter.clone()) | ||
| .with_view(view_function) | ||
| .build(); | ||
|
|
||
| // Act | ||
| let meter = meter_provider.meter("test"); | ||
| let counter = meter | ||
| .f64_counter("my_counter") | ||
| .with_unit("my_unit") | ||
| .with_description("my_description") | ||
| .build(); | ||
|
|
||
| counter.add(1.5, &[KeyValue::new("key1", "value1")]); | ||
| meter_provider.force_flush().unwrap(); | ||
|
|
||
| // Assert | ||
| let resource_metrics = exporter | ||
| .get_finished_metrics() | ||
| .expect("metrics are expected to be exported."); | ||
| assert!(!resource_metrics.is_empty()); | ||
cijothomas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let metric = &resource_metrics[0].scope_metrics[0].metrics[0]; | ||
| assert_eq!( | ||
| metric.name, expected_name, | ||
| "Expected name: {}.", | ||
| expected_name | ||
| ); | ||
| assert_eq!( | ||
| metric.unit, expected_unit, | ||
| "Expected unit: {}.", | ||
| expected_unit | ||
| ); | ||
| assert_eq!( | ||
| metric.description, expected_description, | ||
| "Expected description: {}.", | ||
| expected_description | ||
| ); | ||
| } | ||
|
|
||
| fn asynchronous_instruments_cumulative_data_points_only_from_last_measurement_helper( | ||
| instrument_name: &'static str, | ||
| should_not_emit: bool, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -267,12 +267,22 @@ | |
| // The cache will return the same Aggregator instance. Use stream ids to de duplicate. | ||
| let mut seen = HashSet::new(); | ||
| for v in &self.pipeline.views { | ||
| let stream = match v.match_inst(&inst) { | ||
| let mut stream = match v.match_inst(&inst) { | ||
| Some(stream) => stream, | ||
| None => continue, | ||
| }; | ||
| matched = true; | ||
|
|
||
| if stream.name.is_none() { | ||
| stream.name = Some(inst.name.clone()); | ||
| } | ||
| if stream.description.is_none() { | ||
| stream.description = Some(inst.description.clone()); | ||
| } | ||
| if stream.unit.is_none() { | ||
| stream.unit = Some(inst.unit.clone()); | ||
| } | ||
|
|
||
| let id = self.inst_id(kind, &stream); | ||
| if seen.contains(&id) { | ||
| continue; // This aggregator has already been added | ||
|
|
@@ -300,9 +310,9 @@ | |
|
|
||
| // Apply implicit default view if no explicit matched. | ||
| let mut stream = Stream { | ||
| name: inst.name, | ||
| description: inst.description, | ||
| unit: inst.unit, | ||
| name: Some(inst.name), | ||
| description: Some(inst.description), | ||
| unit: Some(inst.unit), | ||
| aggregation: None, | ||
| allowed_attribute_keys: None, | ||
| cardinality_limit, | ||
|
|
@@ -403,16 +413,16 @@ | |
|
|
||
| otel_debug!( | ||
| name : "Metrics.InstrumentCreated", | ||
| instrument_name = stream.name.as_ref(), | ||
| instrument_name = stream.name.clone().unwrap_or_default().as_ref(), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are being forced to introduce a lot of We should ideally create another internal struct that would have the same fields as
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point! we can modify this separately, as we use the unwrap_default for buckets, attribute_keys too. |
||
| cardinality_limit = cardinality_limit, | ||
| ); | ||
|
|
||
| self.pipeline.add_sync( | ||
| scope.clone(), | ||
| InstrumentSync { | ||
| name: stream.name, | ||
| description: stream.description, | ||
| unit: stream.unit, | ||
| name: stream.name.unwrap_or_default(), | ||
| description: stream.description.unwrap_or_default(), | ||
| unit: stream.unit.unwrap_or_default(), | ||
| comp_agg: collect, | ||
| }, | ||
| ); | ||
|
|
@@ -453,10 +463,10 @@ | |
|
|
||
| fn inst_id(&self, kind: InstrumentKind, stream: &Stream) -> InstrumentId { | ||
| InstrumentId { | ||
| name: stream.name.clone(), | ||
| description: stream.description.clone(), | ||
| name: stream.name.clone().unwrap_or_default(), | ||
| description: stream.description.clone().unwrap_or_default(), | ||
| kind, | ||
| unit: stream.unit.clone(), | ||
| unit: stream.unit.clone().unwrap_or_default(), | ||
| number: Cow::Borrowed(std::any::type_name::<T>()), | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -110,7 +110,7 @@ pub fn new_view(criteria: Instrument, mask: Stream) -> MetricResult<Box<dyn View | |
| let contains_wildcard = criteria.name.contains(['*', '?']); | ||
|
|
||
| let match_fn: Box<dyn Fn(&Instrument) -> bool + Send + Sync> = if contains_wildcard { | ||
| if !mask.name.is_empty() { | ||
| if mask.name.is_some() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still might have to check for empty string. A user could still provide an empty string as |
||
| // TODO - The error is getting lost here. Need to return or log. | ||
| return Ok(Box::new(empty_view)); | ||
| } | ||
|
|
@@ -144,20 +144,20 @@ pub fn new_view(criteria: Instrument, mask: Stream) -> MetricResult<Box<dyn View | |
| Ok(Box::new(move |i: &Instrument| -> Option<Stream> { | ||
| if match_fn(i) { | ||
| Some(Stream { | ||
| name: if !mask.name.is_empty() { | ||
| name: if mask.name.is_some() { | ||
| mask.name.clone() | ||
| } else { | ||
| i.name.clone() | ||
| Some(i.name.clone()) | ||
| }, | ||
| description: if !mask.description.is_empty() { | ||
| description: if mask.description.is_some() { | ||
| mask.description.clone() | ||
| } else { | ||
| i.description.clone() | ||
| Some(i.description.clone()) | ||
| }, | ||
| unit: if !mask.unit.is_empty() { | ||
| unit: if mask.unit.is_some() { | ||
| mask.unit.clone() | ||
| } else { | ||
| i.unit.clone() | ||
| Some(i.unit.clone()) | ||
| }, | ||
| aggregation: agg.clone(), | ||
| allowed_attribute_keys: mask.allowed_attribute_keys.clone(), | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.