Skip to content

Commit 94fe7be

Browse files
feat(metrics): add scope_attributes to MetricSelector for view filtering
Expand MetricSelector to support scope_attributes as a new selector dimension. When configured, the view only applies to instruments whose scope contains all specified attribute key-value pairs.
1 parent 5507512 commit 94fe7be

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

rust/otap-dataflow/crates/config/src/pipeline/telemetry/metrics/views.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
//! Metrics views level configurations.
55
6+
use std::collections::HashMap;
7+
68
use schemars::JsonSchema;
79
use serde::{Deserialize, Serialize};
810

@@ -24,6 +26,10 @@ pub struct MetricSelector {
2426
/// The instrumentation scope (meter) name to match.
2527
/// When set, the view only applies to instruments created under this scope.
2628
pub scope_name: Option<String>,
29+
/// The instrumentation scope attributes to match.
30+
/// When set, the view only applies to instruments whose scope contains all
31+
/// of the specified attribute key-value pairs.
32+
pub scope_attributes: Option<HashMap<String, String>>,
2733
}
2834

2935
/// OpenTelemetry Metric Stream configuration.
@@ -115,4 +121,27 @@ mod tests {
115121
Some("exporter_sent_log_records")
116122
);
117123
}
124+
125+
#[test]
126+
fn test_view_config_with_scope_attributes() {
127+
let yaml_str = r#"
128+
selector:
129+
scope_name: "my.library"
130+
scope_attributes:
131+
env: "production"
132+
region: "us-east-1"
133+
stream:
134+
description: "Production histograms"
135+
"#;
136+
let config: ViewConfig = serde_yaml::from_str(yaml_str).unwrap();
137+
assert_eq!(config.selector.scope_name.as_deref(), Some("my.library"));
138+
let attrs = config.selector.scope_attributes.unwrap();
139+
assert_eq!(attrs.len(), 2);
140+
assert_eq!(attrs.get("env").unwrap(), "production");
141+
assert_eq!(attrs.get("region").unwrap(), "us-east-1");
142+
assert_eq!(
143+
config.stream.description.as_deref(),
144+
Some("Production histograms")
145+
);
146+
}
118147
}

rust/otap-dataflow/crates/telemetry/src/otel_sdk/meter_provider/views_provider.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ impl DeclarativeView {
5454
return None;
5555
}
5656

57+
if let Some(scope_attributes) = &config.selector.scope_attributes {
58+
let scope_attrs: Vec<_> = instrument.scope().attributes().collect();
59+
for (key, value) in scope_attributes {
60+
let matched = scope_attrs
61+
.iter()
62+
.any(|kv| kv.key.as_str() == key && kv.value.to_string() == *value);
63+
if !matched {
64+
return None;
65+
}
66+
}
67+
}
68+
5769
let mut stream_builder = Stream::builder();
5870
if let Some(name) = &config.stream.name {
5971
stream_builder = stream_builder.with_name(name.clone());
@@ -69,6 +81,8 @@ impl DeclarativeView {
6981

7082
#[cfg(test)]
7183
mod tests {
84+
use std::collections::HashMap;
85+
7286
use super::*;
7387
use opentelemetry_sdk::metrics::SdkMeterProvider;
7488
use otap_df_config::pipeline::telemetry::metrics::views::{
@@ -81,6 +95,7 @@ mod tests {
8195
selector: MetricSelector {
8296
instrument_name: Some("requests.total".to_string()),
8397
scope_name: None,
98+
scope_attributes: None,
8499
},
85100
stream: MetricStream {
86101
name: Some("http.requests.total".to_string()),
@@ -99,6 +114,7 @@ mod tests {
99114
selector: MetricSelector {
100115
instrument_name: Some("requests.total".to_string()),
101116
scope_name: None,
117+
scope_attributes: None,
102118
},
103119
stream: MetricStream {
104120
name: Some("http.requests.total".to_string()),
@@ -123,6 +139,7 @@ mod tests {
123139
selector: MetricSelector {
124140
instrument_name: Some("requests.total".to_string()),
125141
scope_name: None,
142+
scope_attributes: None,
126143
},
127144
stream: MetricStream {
128145
name: Some("http.requests.total".to_string()),
@@ -139,6 +156,7 @@ mod tests {
139156
selector: MetricSelector {
140157
instrument_name: Some("requests.total".to_string()),
141158
scope_name: Some("my.scope".to_string()),
159+
scope_attributes: None,
142160
},
143161
stream: MetricStream {
144162
name: Some("http.requests.total".to_string()),
@@ -157,6 +175,7 @@ mod tests {
157175
selector: MetricSelector {
158176
instrument_name: None,
159177
scope_name: Some("azure_monitor_exporter.metrics".to_string()),
178+
scope_attributes: None,
160179
},
161180
stream: MetricStream {
162181
name: None,
@@ -168,4 +187,26 @@ mod tests {
168187
let result = ViewsProvider::configure(sdk_meter_builder, views_config);
169188
assert!(result.is_ok());
170189
}
190+
191+
#[test]
192+
fn test_views_provider_configure_with_scope_attributes() {
193+
let view_config = ViewConfig {
194+
selector: MetricSelector {
195+
instrument_name: None,
196+
scope_name: Some("my.library".to_string()),
197+
scope_attributes: Some(HashMap::from([
198+
("env".to_string(), "production".to_string()),
199+
("region".to_string(), "us-east-1".to_string()),
200+
])),
201+
},
202+
stream: MetricStream {
203+
name: None,
204+
description: Some("Production histograms".to_string()),
205+
},
206+
};
207+
let views_config = vec![view_config];
208+
let sdk_meter_builder = SdkMeterProvider::builder();
209+
let result = ViewsProvider::configure(sdk_meter_builder, views_config);
210+
assert!(result.is_ok());
211+
}
171212
}

0 commit comments

Comments
 (0)