1use std::time::Duration;
2
3use metrics::Key;
4use regex::Regex;
5
6use super::recency::KeyMatcher;
7use crate::config::metrics_expiration::{
8 MetricLabelMatcher, MetricLabelMatcherConfig, MetricNameMatcherConfig, PerMetricSetExpiration,
9};
10
11pub(super) struct MetricKeyMatcher {
12 name: Option<MetricNameMatcher>,
13 labels: Option<LabelsMatcher>,
14}
15
16impl KeyMatcher<Key> for MetricKeyMatcher {
17 fn matches(&self, key: &Key) -> bool {
18 let name_match = self.name.as_ref().is_none_or(|m| m.matches(key));
19 let labels_match = self.labels.as_ref().is_none_or(|l| l.matches(key));
20 name_match && labels_match
21 }
22}
23
24impl TryFrom<PerMetricSetExpiration> for MetricKeyMatcher {
25 type Error = super::Error;
26
27 fn try_from(value: PerMetricSetExpiration) -> Result<Self, Self::Error> {
28 Ok(Self {
29 name: value.name.map(TryInto::try_into).transpose()?,
30 labels: value.labels.map(TryInto::try_into).transpose()?,
31 })
32 }
33}
34
35impl TryFrom<PerMetricSetExpiration> for (MetricKeyMatcher, Duration) {
36 type Error = super::Error;
37
38 fn try_from(value: PerMetricSetExpiration) -> Result<Self, Self::Error> {
39 if value.expire_secs <= 0.0 {
40 return Err(super::Error::TimeoutMustBePositive {
41 timeout: value.expire_secs,
42 });
43 }
44 let duration = Duration::from_secs_f64(value.expire_secs);
45 Ok((value.try_into()?, duration))
46 }
47}
48
49enum MetricNameMatcher {
50 Exact(String),
51 Regex(Regex),
52}
53
54impl KeyMatcher<Key> for MetricNameMatcher {
55 fn matches(&self, key: &Key) -> bool {
56 match self {
57 MetricNameMatcher::Exact(name) => key.name() == name,
58 MetricNameMatcher::Regex(regex) => regex.is_match(key.name()),
59 }
60 }
61}
62
63impl TryFrom<MetricNameMatcherConfig> for MetricNameMatcher {
64 type Error = super::Error;
65
66 fn try_from(value: MetricNameMatcherConfig) -> Result<Self, Self::Error> {
67 Ok(match value {
68 MetricNameMatcherConfig::Exact { value } => MetricNameMatcher::Exact(value),
69 MetricNameMatcherConfig::Regex { pattern } => MetricNameMatcher::Regex(
70 Regex::new(&pattern).map_err(|_| super::Error::InvalidRegexPattern { pattern })?,
71 ),
72 })
73 }
74}
75
76enum LabelsMatcher {
77 Any(Vec<LabelsMatcher>),
78 All(Vec<LabelsMatcher>),
79 Exact(String, String),
80 Regex(String, Regex),
81}
82
83impl KeyMatcher<Key> for LabelsMatcher {
84 fn matches(&self, key: &Key) -> bool {
85 match self {
86 LabelsMatcher::Any(vec) => vec.iter().any(|m| m.matches(key)),
87 LabelsMatcher::All(vec) => vec.iter().all(|m| m.matches(key)),
88 LabelsMatcher::Exact(label_key, label_value) => key
89 .labels()
90 .any(|l| l.key() == label_key && l.value() == label_value),
91 LabelsMatcher::Regex(label_key, regex) => key
92 .labels()
93 .any(|l| l.key() == label_key && regex.is_match(l.value())),
94 }
95 }
96}
97
98impl TryFrom<MetricLabelMatcher> for LabelsMatcher {
99 type Error = super::Error;
100
101 fn try_from(value: MetricLabelMatcher) -> Result<Self, Self::Error> {
102 Ok(match value {
103 MetricLabelMatcher::Exact { key, value } => Self::Exact(key, value),
104 MetricLabelMatcher::Regex { key, value_pattern } => Self::Regex(
105 key,
106 Regex::new(&value_pattern).map_err(|_| super::Error::InvalidRegexPattern {
107 pattern: value_pattern,
108 })?,
109 ),
110 })
111 }
112}
113
114impl TryFrom<MetricLabelMatcherConfig> for LabelsMatcher {
115 type Error = super::Error;
116
117 fn try_from(value: MetricLabelMatcherConfig) -> Result<Self, Self::Error> {
118 Ok(match value {
119 MetricLabelMatcherConfig::Any { matchers } => Self::Any(
120 matchers
121 .into_iter()
122 .map(TryInto::<LabelsMatcher>::try_into)
123 .collect::<Result<Vec<_>, _>>()?,
124 ),
125 MetricLabelMatcherConfig::All { matchers } => Self::All(
126 matchers
127 .into_iter()
128 .map(TryInto::<LabelsMatcher>::try_into)
129 .collect::<Result<Vec<_>, _>>()?,
130 ),
131 })
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use metrics::Label;
138 use vrl::prelude::indoc;
139
140 use super::*;
141
142 const EMPTY: MetricKeyMatcher = MetricKeyMatcher {
143 name: None,
144 labels: None,
145 };
146
147 #[test]
148 fn empty_matcher_should_match_all() {
149 assert!(EMPTY.matches(&Key::from_name("test_name")));
150 assert!(EMPTY.matches(&Key::from_parts(
151 "another",
152 [Label::new("test_key", "test_value")].iter()
153 )));
154 }
155
156 #[test]
157 fn name_matcher_should_ignore_labels() {
158 let matcher = MetricKeyMatcher {
159 name: Some(MetricNameMatcher::Exact("test_metric".to_string())),
160 labels: None,
161 };
162
163 assert!(matcher.matches(&Key::from_name("test_metric")));
164 assert!(matcher.matches(&Key::from_parts(
165 "test_metric",
166 [Label::new("test_key", "test_value")].iter()
167 )));
168 assert!(!matcher.matches(&Key::from_name("different_name")));
169 assert!(!matcher.matches(&Key::from_parts(
170 "different_name",
171 [Label::new("test_key", "test_value")].iter()
172 )));
173 }
174
175 #[test]
176 fn exact_name_matcher_should_check_name() {
177 let matcher = MetricKeyMatcher {
178 name: Some(MetricNameMatcher::Exact("test_metric".to_string())),
179 labels: None,
180 };
181
182 assert!(matcher.matches(&Key::from_name("test_metric")));
183 assert!(!matcher.matches(&Key::from_name("different_name")));
184 assert!(!matcher.matches(&Key::from_name("_test_metric")));
185 assert!(!matcher.matches(&Key::from_name("test_metric123")));
186 }
187
188 #[test]
189 fn regex_name_matcher_should_try_matching_the_name() {
190 let matcher = MetricKeyMatcher {
191 name: Some(MetricNameMatcher::Regex(
192 Regex::new(r".*test_?metric.*").unwrap(),
193 )),
194 labels: None,
195 };
196
197 assert!(matcher.matches(&Key::from_name("test_metric")));
198 assert!(!matcher.matches(&Key::from_name("different_name")));
199 assert!(matcher.matches(&Key::from_name("_test_metric")));
200 assert!(matcher.matches(&Key::from_name("test_metric123")));
201 assert!(matcher.matches(&Key::from_name("__testmetric123")));
202 }
203
204 #[test]
205 fn exact_label_matcher_should_look_for_exact_label_match() {
206 let matcher = MetricKeyMatcher {
207 name: None,
208 labels: Some(LabelsMatcher::Exact(
209 "test_key".to_string(),
210 "test_value".to_string(),
211 )),
212 };
213
214 assert!(!matcher.matches(&Key::from_name("test_metric")));
215 assert!(matcher.matches(&Key::from_parts(
216 "test_metric",
217 [Label::new("test_key", "test_value")].iter()
218 )));
219 assert!(!matcher.matches(&Key::from_name("different_name")));
220 assert!(matcher.matches(&Key::from_parts(
221 "different_name",
222 [Label::new("test_key", "test_value")].iter()
223 )));
224 }
225
226 #[test]
227 fn regex_label_matcher_should_look_for_exact_label_match() {
228 let matcher = MetricKeyMatcher {
229 name: None,
230 labels: Some(LabelsMatcher::Regex(
231 "test_key".to_string(),
232 Regex::new(r"metric_val.*").unwrap(),
233 )),
234 };
235
236 assert!(!matcher.matches(&Key::from_name("test_metric")));
237 assert!(matcher.matches(&Key::from_parts(
238 "test_metric",
239 [Label::new("test_key", "metric_value123")].iter()
240 )));
241 assert!(!matcher.matches(&Key::from_parts(
242 "test_metric",
243 [Label::new("test_key", "test_value123")].iter()
244 )));
245 assert!(matcher.matches(&Key::from_parts(
246 "different_name",
247 [Label::new("test_key", "metric_val0")].iter()
248 )));
249 }
250
251 #[test]
252 fn any_label_matcher_should_look_for_at_least_one_match() {
253 let matcher = MetricKeyMatcher {
254 name: None,
255 labels: Some(LabelsMatcher::Any(vec![
256 LabelsMatcher::Regex("test_key".to_string(), Regex::new(r"metric_val.*").unwrap()),
257 LabelsMatcher::Exact("test_key".to_string(), "test_value".to_string()),
258 ])),
259 };
260
261 assert!(!matcher.matches(&Key::from_name("test_metric")));
262 assert!(matcher.matches(&Key::from_parts(
263 "test_metric",
264 [Label::new("test_key", "metric_value123")].iter()
265 )));
266 assert!(matcher.matches(&Key::from_parts(
267 "test_metric",
268 [Label::new("test_key", "test_value")].iter()
269 )));
270 assert!(matcher.matches(&Key::from_parts(
271 "different_name",
272 [Label::new("test_key", "metric_val0")].iter()
273 )));
274 assert!(!matcher.matches(&Key::from_parts(
275 "different_name",
276 [Label::new("test_key", "different_value")].iter()
277 )));
278 }
279
280 #[test]
281 fn all_label_matcher_should_expect_all_matches() {
282 let matcher = MetricKeyMatcher {
283 name: None,
284 labels: Some(LabelsMatcher::All(vec![
285 LabelsMatcher::Regex("key_one".to_string(), Regex::new(r"metric_val.*").unwrap()),
286 LabelsMatcher::Exact("key_two".to_string(), "test_value".to_string()),
287 ])),
288 };
289
290 assert!(!matcher.matches(&Key::from_name("test_metric")));
291 assert!(!matcher.matches(&Key::from_parts(
292 "test_metric",
293 [Label::new("key_one", "metric_value123")].iter()
294 )));
295 assert!(!matcher.matches(&Key::from_parts(
296 "test_metric",
297 [Label::new("key_two", "test_value")].iter()
298 )));
299 assert!(
300 matcher.matches(&Key::from_parts(
301 "different_name",
302 [
303 Label::new("key_one", "metric_value_1234"),
304 Label::new("key_two", "test_value")
305 ]
306 .iter()
307 ))
308 );
309 }
310
311 #[test]
312 fn matcher_with_both_name_and_label_should_expect_both_to_match() {
313 let matcher = MetricKeyMatcher {
314 name: Some(MetricNameMatcher::Exact("test_metric".to_string())),
315 labels: Some(LabelsMatcher::Exact(
316 "test_key".to_string(),
317 "test_value".to_string(),
318 )),
319 };
320
321 assert!(!matcher.matches(&Key::from_name("test_metric")));
322 assert!(!matcher.matches(&Key::from_name("different_name")));
323 assert!(!matcher.matches(&Key::from_parts(
324 "different_name",
325 [Label::new("test_key", "test_value")].iter()
326 )));
327 assert!(matcher.matches(&Key::from_parts(
328 "test_metric",
329 [Label::new("test_key", "test_value")].iter()
330 )));
331 }
332
333 #[test]
334 fn complex_matcher_rules() {
335 let matcher = MetricKeyMatcher {
336 name: Some(MetricNameMatcher::Regex(Regex::new(r"custom_.*").unwrap())),
337 labels: Some(LabelsMatcher::All(vec![
338 LabelsMatcher::Exact("component_kind".to_string(), "sink".to_string()),
340 LabelsMatcher::Regex("component_type".to_string(), Regex::new(r"aws_.*").unwrap()),
342 LabelsMatcher::Any(vec![
344 LabelsMatcher::Exact("region".to_string(), "some_aws_region_name".to_string()),
345 LabelsMatcher::Regex(
346 "endpoint".to_string(),
347 Regex::new(r"test.com.*").unwrap(),
348 ),
349 ]),
350 ])),
351 };
352
353 assert!(!matcher.matches(&Key::from_name("test_metric")));
354 assert!(!matcher.matches(&Key::from_name("custom_metric_a")));
355 assert!(!matcher.matches(&Key::from_parts(
356 "custom_metric_with_missing_component_type",
357 [Label::new("component_kind", "sink")].iter()
358 )));
359 assert!(
360 !matcher.matches(&Key::from_parts(
361 "custom_metric_with_missing_extra_labels",
362 [
363 Label::new("component_kind", "sink"),
364 Label::new("component_type", "aws_cloudwatch_metrics")
365 ]
366 .iter()
367 ))
368 );
369 assert!(
370 !matcher.matches(&Key::from_parts(
371 "custom_metric_with_wrong_region",
372 [
373 Label::new("component_kind", "sink"),
374 Label::new("component_type", "aws_cloudwatch_metrics"),
375 Label::new("region", "some_other_region")
376 ]
377 .iter()
378 ))
379 );
380 assert!(
381 !matcher.matches(&Key::from_parts(
382 "custom_metric_with_wrong_region_and_endpoint",
383 [
384 Label::new("component_kind", "sink"),
385 Label::new("component_type", "aws_cloudwatch_metrics"),
386 Label::new("region", "some_other_region"),
387 Label::new("endpoint", "wrong_endpoint.com/metrics")
388 ]
389 .iter()
390 ))
391 );
392 assert!(
393 matcher.matches(&Key::from_parts(
394 "custom_metric_with_wrong_endpoint_but_correct_region",
395 [
396 Label::new("component_kind", "sink"),
397 Label::new("component_type", "aws_cloudwatch_metrics"),
398 Label::new("region", "some_aws_region_name"),
399 Label::new("endpoint", "wrong_endpoint.com/metrics")
400 ]
401 .iter()
402 ))
403 );
404 assert!(
405 matcher.matches(&Key::from_parts(
406 "custom_metric_with_wrong_region_but_correct_endpoint",
407 [
408 Label::new("component_kind", "sink"),
409 Label::new("component_type", "aws_cloudwatch_metrics"),
410 Label::new("region", "some_other_region"),
411 Label::new("endpoint", "test.com/metrics")
412 ]
413 .iter()
414 ))
415 );
416 assert!(
417 !matcher.matches(&Key::from_parts(
418 "custom_metric_with_wrong_component_kind",
419 [
420 Label::new("component_kind", "source"),
421 Label::new("component_type", "aws_cloudwatch_metrics"),
422 Label::new("region", "some_other_region"),
423 Label::new("endpoint", "test.com/metrics")
424 ]
425 .iter()
426 ))
427 );
428 }
429
430 #[test]
431 fn parse_simple_config_into_matcher() {
432 let config = serde_yaml::from_str::<PerMetricSetExpiration>(indoc! {r#"
433 name:
434 type: "exact"
435 value: "test_metric"
436 labels:
437 type: "all"
438 matchers:
439 - type: "exact"
440 key: "component_kind"
441 value: "sink"
442 - type: "regex"
443 key: "component_type"
444 value_pattern: "aws_.*"
445 expire_secs: 1.0
446 "#})
447 .unwrap();
448
449 let matcher: MetricKeyMatcher = config.try_into().unwrap();
450
451 if let Some(MetricNameMatcher::Exact(value)) = matcher.name {
452 assert_eq!("test_metric", value);
453 } else {
454 panic!("Expected exact name matcher");
455 }
456
457 let Some(LabelsMatcher::All(all_matchers)) = matcher.labels else {
458 panic!("Expected main label matcher to be an all matcher");
459 };
460
461 assert_eq!(2, all_matchers.len());
462 if let LabelsMatcher::Exact(key, value) = &all_matchers[0] {
463 assert_eq!("component_kind", key);
464 assert_eq!("sink", value);
465 } else {
466 panic!("Expected first label matcher to be an exact matcher");
467 }
468 if let LabelsMatcher::Regex(key, regex) = &all_matchers[1] {
469 assert_eq!("component_type", key);
470 assert_eq!("aws_.*", regex.as_str());
471 } else {
472 panic!("Expected second label matcher to be a regex matcher");
473 }
474 }
475}