opentelemetry_sdk/metrics/
view.rs1use super::instrument::{Instrument, Stream};
2#[cfg(feature = "spec_unstable_metrics_views")]
3use crate::metrics::{MetricError, MetricResult};
4#[cfg(feature = "spec_unstable_metrics_views")]
5use glob::Pattern;
6
7#[cfg(feature = "spec_unstable_metrics_views")]
8fn empty_view(_inst: &Instrument) -> Option<Stream> {
9 None
10}
11
12#[allow(unreachable_pub)]
49pub trait View: Send + Sync + 'static {
50 fn match_inst(&self, inst: &Instrument) -> Option<Stream>;
55}
56
57impl<T> View for T
58where
59 T: Fn(&Instrument) -> Option<Stream> + Send + Sync + 'static,
60{
61 fn match_inst(&self, inst: &Instrument) -> Option<Stream> {
62 self(inst)
63 }
64}
65
66impl View for Box<dyn View> {
67 fn match_inst(&self, inst: &Instrument) -> Option<Stream> {
68 (**self).match_inst(inst)
69 }
70}
71
72#[cfg(feature = "spec_unstable_metrics_views")]
73pub fn new_view(criteria: Instrument, mask: Stream) -> MetricResult<Box<dyn View>> {
106 if criteria.is_empty() {
107 return Ok(Box::new(empty_view));
109 }
110 let contains_wildcard = criteria.name.contains(['*', '?']);
111
112 let match_fn: Box<dyn Fn(&Instrument) -> bool + Send + Sync> = if contains_wildcard {
113 if mask.name != "" {
114 return Ok(Box::new(empty_view));
116 }
117
118 let pattern = criteria.name.clone();
119 let glob_pattern =
120 Pattern::new(&pattern).map_err(|e| MetricError::Config(e.to_string()))?;
121
122 Box::new(move |i| {
123 glob_pattern.matches(&i.name)
124 && criteria.matches_description(i)
125 && criteria.matches_kind(i)
126 && criteria.matches_unit(i)
127 && criteria.matches_scope(i)
128 })
129 } else {
130 Box::new(move |i| criteria.matches(i))
131 };
132
133 let mut agg = None;
134 if let Some(ma) = &mask.aggregation {
135 match ma.validate() {
136 Ok(_) => agg = Some(ma.clone()),
137 Err(_) => {
138 return Ok(Box::new(empty_view));
140 }
141 }
142 }
143
144 Ok(Box::new(move |i: &Instrument| -> Option<Stream> {
145 if match_fn(i) {
146 Some(Stream {
147 name: if !mask.name.is_empty() {
148 mask.name.clone()
149 } else {
150 i.name.clone()
151 },
152 description: if !mask.description.is_empty() {
153 mask.description.clone()
154 } else {
155 i.description.clone()
156 },
157 unit: if !mask.unit.is_empty() {
158 mask.unit.clone()
159 } else {
160 i.unit.clone()
161 },
162 aggregation: agg.clone(),
163 allowed_attribute_keys: mask.allowed_attribute_keys.clone(),
164 })
165 } else {
166 None
167 }
168 }))
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 #[test]
175 fn test_new_view_matching_all() {
176 let criteria = Instrument::new().name("*");
177 let mask = Stream::new();
178
179 let view = new_view(criteria, mask).expect("Expected to create a new view");
180
181 let test_instrument = Instrument::new().name("test_instrument");
182 assert!(
183 view.match_inst(&test_instrument).is_some(),
184 "Expected to match all instruments with * pattern"
185 );
186 }
187
188 #[test]
189 fn test_new_view_exact_match() {
190 let criteria = Instrument::new().name("counter_exact_match");
191 let mask = Stream::new();
192
193 let view = new_view(criteria, mask).expect("Expected to create a new view");
194
195 let matching_instrument = Instrument::new().name("counter_exact_match");
196 assert!(
197 view.match_inst(&matching_instrument).is_some(),
198 "Expected to match instrument with exact name"
199 );
200
201 let non_matching_instrument = Instrument::new().name("counter_non_exact_match");
202 assert!(
203 view.match_inst(&non_matching_instrument).is_none(),
204 "Expected not to match instrument with different name"
205 );
206 }
207
208 #[test]
209 fn test_new_view_with_wildcard_pattern() {
210 let criteria = Instrument::new().name("prefix_*");
211 let mask = Stream::new();
212
213 let view = new_view(criteria, mask).expect("Expected to create a new view");
214
215 let matching_instrument = Instrument::new().name("prefix_counter");
216 assert!(
217 view.match_inst(&matching_instrument).is_some(),
218 "Expected to match instrument with matching prefix"
219 );
220
221 let non_matching_instrument = Instrument::new().name("nonprefix_counter");
222 assert!(
223 view.match_inst(&non_matching_instrument).is_none(),
224 "Expected not to match instrument with different prefix"
225 );
226 }
227
228 #[test]
229 fn test_new_view_wildcard_question_mark() {
230 let criteria = Instrument::new().name("test_?");
231 let mask = Stream::new();
232
233 let view = new_view(criteria, mask).expect("Expected to create a new view");
234
235 let matching_instrument = Instrument::new().name("test_1");
237 assert!(
238 view.match_inst(&matching_instrument).is_some(),
239 "Expected to match instrument with test_? pattern"
240 );
241
242 let non_matching_instrument = Instrument::new().name("test_12");
244 assert!(
245 view.match_inst(&non_matching_instrument).is_none(),
246 "Expected not to match instrument with test_? pattern"
247 );
248 }
249}