vector/sources/prometheus/
parser.rs

1use chrono::{DateTime, TimeZone, Utc};
2#[cfg(feature = "sources-prometheus-remote-write")]
3use vector_lib::prometheus::parser::proto;
4use vector_lib::prometheus::parser::{GroupKind, MetricGroup, ParserError};
5
6use crate::event::{
7    Event,
8    metric::{Bucket, Metric, MetricKind, MetricTags, MetricValue, Quantile},
9};
10
11fn utc_timestamp(timestamp: Option<i64>, default: DateTime<Utc>) -> DateTime<Utc> {
12    timestamp
13        .and_then(|timestamp| {
14            Utc.timestamp_opt(timestamp / 1000, (timestamp % 1000) as u32 * 1000000)
15                .latest()
16        })
17        .unwrap_or(default)
18}
19
20#[cfg(any(test, feature = "sources-prometheus-scrape"))]
21pub(super) fn parse_text(packet: &str) -> Result<Vec<Event>, ParserError> {
22    vector_lib::prometheus::parser::parse_text(packet)
23        .map(|group| reparse_groups(group, vec![], false, false))
24}
25
26#[cfg(any(test, feature = "sources-prometheus-pushgateway"))]
27pub(super) fn parse_text_with_overrides(
28    packet: &str,
29    tag_overrides: impl IntoIterator<Item = (String, String)> + Clone,
30    aggregate_metrics: bool,
31) -> Result<Vec<Event>, ParserError> {
32    vector_lib::prometheus::parser::parse_text(packet)
33        .map(|group| reparse_groups(group, tag_overrides, aggregate_metrics, false))
34}
35
36#[cfg(test)]
37fn parse_text_with_nan_filtering(packet: &str) -> Result<Vec<Event>, ParserError> {
38    vector_lib::prometheus::parser::parse_text(packet)
39        .map(|group| reparse_groups(group, vec![], false, true))
40}
41
42#[cfg(feature = "sources-prometheus-remote-write")]
43pub(super) fn parse_request(
44    request: proto::WriteRequest,
45    skip_nan_values: bool,
46) -> Result<Vec<Event>, ParserError> {
47    vector_lib::prometheus::parser::parse_request(request)
48        .map(|group| reparse_groups(group, vec![], false, skip_nan_values))
49}
50
51fn reparse_groups(
52    groups: Vec<MetricGroup>,
53    tag_overrides: impl IntoIterator<Item = (String, String)> + Clone,
54    aggregate_metrics: bool,
55    skip_nan_values: bool,
56) -> Vec<Event> {
57    let mut result = Vec::new();
58    let start = Utc::now();
59
60    let metric_kind = if aggregate_metrics {
61        MetricKind::Incremental
62    } else {
63        MetricKind::Absolute
64    };
65
66    for group in groups {
67        match group.metrics {
68            GroupKind::Counter(metrics) => {
69                for (key, metric) in metrics {
70                    if skip_nan_values && metric.value.is_nan() {
71                        continue;
72                    }
73
74                    let tags = combine_tags(key.labels, tag_overrides.clone());
75
76                    let counter = Metric::new(
77                        group.name.clone(),
78                        metric_kind,
79                        MetricValue::Counter {
80                            value: metric.value,
81                        },
82                    )
83                    .with_timestamp(Some(utc_timestamp(key.timestamp, start)))
84                    .with_tags(tags.as_option());
85
86                    result.push(counter.into());
87                }
88            }
89            GroupKind::Gauge(metrics) | GroupKind::Untyped(metrics) => {
90                for (key, metric) in metrics {
91                    if skip_nan_values && metric.value.is_nan() {
92                        continue;
93                    }
94
95                    let tags = combine_tags(key.labels, tag_overrides.clone());
96
97                    let gauge = Metric::new(
98                        group.name.clone(),
99                        // Gauges are always absolute: aggregating them makes no sense
100                        MetricKind::Absolute,
101                        MetricValue::Gauge {
102                            value: metric.value,
103                        },
104                    )
105                    .with_timestamp(Some(utc_timestamp(key.timestamp, start)))
106                    .with_tags(tags.as_option());
107
108                    result.push(gauge.into());
109                }
110            }
111            GroupKind::Histogram(metrics) => {
112                for (key, metric) in metrics {
113                    if skip_nan_values
114                        && (metric.sum.is_nan() || metric.buckets.iter().any(|b| b.bucket.is_nan()))
115                    {
116                        continue;
117                    }
118
119                    let tags = combine_tags(key.labels, tag_overrides.clone());
120
121                    let mut buckets = metric.buckets;
122                    buckets.sort_unstable_by(|a, b| {
123                        a.bucket.total_cmp(&b.bucket).then(a.count.cmp(&b.count))
124                    });
125                    for i in (1..buckets.len()).rev() {
126                        buckets[i].count = buckets[i].count.saturating_sub(buckets[i - 1].count);
127                    }
128                    let drop_last = buckets
129                        .last()
130                        .is_some_and(|bucket| bucket.bucket == f64::INFINITY);
131                    if drop_last {
132                        buckets.pop();
133                    }
134
135                    result.push(
136                        Metric::new(
137                            group.name.clone(),
138                            metric_kind,
139                            MetricValue::AggregatedHistogram {
140                                buckets: buckets
141                                    .into_iter()
142                                    .map(|b| Bucket {
143                                        upper_limit: b.bucket,
144                                        count: b.count,
145                                    })
146                                    .collect(),
147                                count: metric.count,
148                                sum: metric.sum,
149                            },
150                        )
151                        .with_timestamp(Some(utc_timestamp(key.timestamp, start)))
152                        .with_tags(tags.as_option())
153                        .into(),
154                    );
155                }
156            }
157            GroupKind::Summary(metrics) => {
158                for (key, metric) in metrics {
159                    if skip_nan_values
160                        && (metric.sum.is_nan()
161                            || metric
162                                .quantiles
163                                .iter()
164                                .any(|q| q.quantile.is_nan() || q.value.is_nan()))
165                    {
166                        continue;
167                    }
168
169                    let tags = combine_tags(key.labels, tag_overrides.clone());
170
171                    result.push(
172                        Metric::new(
173                            group.name.clone(),
174                            // Summaries are always absolute: aggregating them makes no sense
175                            MetricKind::Absolute,
176                            MetricValue::AggregatedSummary {
177                                quantiles: metric
178                                    .quantiles
179                                    .into_iter()
180                                    .map(|q| Quantile {
181                                        quantile: q.quantile,
182                                        value: q.value,
183                                    })
184                                    .collect(),
185                                count: metric.count,
186                                sum: metric.sum,
187                            },
188                        )
189                        .with_timestamp(Some(utc_timestamp(key.timestamp, start)))
190                        .with_tags(tags.as_option())
191                        .into(),
192                    );
193                }
194            }
195        }
196    }
197
198    result
199}
200
201fn combine_tags(
202    base_tags: impl Into<MetricTags>,
203    tag_overrides: impl IntoIterator<Item = (String, String)>,
204) -> MetricTags {
205    let mut tags = base_tags.into();
206    for (k, v) in tag_overrides.into_iter() {
207        tags.replace(k, v);
208    }
209
210    tags
211}
212
213#[cfg(test)]
214mod test {
215    use core::f64;
216    use std::sync::LazyLock;
217
218    use chrono::{TimeZone, Timelike, Utc};
219    use itertools::Itertools;
220    use similar_asserts::assert_eq;
221    use vector_lib::{assert_event_data_eq, metric_tags};
222
223    use super::*;
224    use crate::event::metric::{Metric, MetricKind, MetricValue};
225
226    static TIMESTAMP: LazyLock<DateTime<Utc>> = LazyLock::new(|| {
227        Utc.with_ymd_and_hms(2021, 2, 4, 4, 5, 6)
228            .single()
229            .and_then(|t| t.with_nanosecond(789 * 1_000_000))
230            .expect("invalid timestamp")
231    });
232
233    fn events_to_metrics(
234        events: Result<Vec<Event>, ParserError>,
235    ) -> Result<Vec<Metric>, ParserError> {
236        events.map(|events| events.into_iter().map(Event::into_metric).collect())
237    }
238
239    #[test]
240    fn adds_timestamp_if_missing() {
241        let now = Utc::now();
242        let exp = r"
243            # HELP counter Some counter
244            # TYPE count counter
245            http_requests_total 1027
246            ";
247        let result = events_to_metrics(parse_text(exp)).unwrap();
248        assert_eq!(result.len(), 1);
249        assert!(result[0].timestamp().unwrap() >= now);
250    }
251
252    #[test]
253    fn test_counter() {
254        let exp = r"
255            # HELP uptime A counter
256            # TYPE uptime counter
257            uptime 123.0 1612411506789
258            ";
259
260        assert_event_data_eq!(
261            events_to_metrics(parse_text(exp)),
262            Ok(vec![
263                Metric::new(
264                    "uptime",
265                    MetricKind::Absolute,
266                    MetricValue::Counter { value: 123.0 },
267                )
268                .with_timestamp(Some(*TIMESTAMP))
269            ]),
270        );
271    }
272
273    #[test]
274    fn test_counter_empty() {
275        let exp = r"
276            # HELP hidden A counter
277            # TYPE hidden counter
278            ";
279
280        assert_event_data_eq!(events_to_metrics(parse_text(exp)), Ok(vec![]));
281    }
282
283    #[test]
284    fn test_counter_nan() {
285        let exp = r#"
286            # TYPE name counter
287            name{labelname="val1",basename="basevalue"} NaN
288            "#;
289
290        match events_to_metrics(parse_text(exp)).unwrap()[0].value() {
291            MetricValue::Counter { value } => {
292                assert!(value.is_nan());
293            }
294            _ => unreachable!(),
295        }
296    }
297
298    #[test]
299    fn test_counter_weird() {
300        let exp = r#"
301            # A normal comment.
302            #
303            # TYPE name counter
304            name {labelname="val2",basename="base\"v\\al\nue"} 0.23 1612411506789
305            # HELP name two-line\n doc  str\\ing
306            # HELP  name2  	doc str"ing 2
307            #    TYPE    name2 counter
308            name2{labelname="val2"	,basename   =   "basevalue2"		} +Inf 1612411506789
309            name2{ labelname = "val1" , }-Inf 1612411506789
310            "#;
311
312        assert_event_data_eq!(
313            events_to_metrics(parse_text(exp)),
314            Ok(vec![
315                Metric::new(
316                    "name",
317                    MetricKind::Absolute,
318                    MetricValue::Counter { value: 0.23 },
319                )
320                .with_tags(Some(metric_tags!(
321                    "labelname" => "val2",
322                    "basename" => "base\"v\\al\nue",
323                )))
324                .with_timestamp(Some(*TIMESTAMP)),
325                Metric::new(
326                    "name2",
327                    MetricKind::Absolute,
328                    MetricValue::Counter {
329                        value: f64::INFINITY
330                    },
331                )
332                .with_tags(Some(metric_tags!(
333                    "labelname" => "val2",
334                    "basename" => "basevalue2",
335                )))
336                .with_timestamp(Some(*TIMESTAMP)),
337                Metric::new(
338                    "name2",
339                    MetricKind::Absolute,
340                    MetricValue::Counter {
341                        value: f64::NEG_INFINITY
342                    },
343                )
344                .with_tags(Some(metric_tags!("labelname" => "val1")))
345                .with_timestamp(Some(*TIMESTAMP)),
346            ]),
347        );
348    }
349
350    #[test]
351    fn test_counter_tags_and_timestamp() {
352        let exp = r#"
353            # HELP http_requests_total The total number of HTTP requests.
354            # TYPE http_requests_total counter
355            http_requests_total{method="post",code="200"} 1027 1395066363000
356            http_requests_total{method="post",code="400"}    3 1395066363000
357            "#;
358
359        assert_event_data_eq!(
360            events_to_metrics(parse_text(exp)),
361            Ok(vec![
362                Metric::new(
363                    "http_requests_total",
364                    MetricKind::Absolute,
365                    MetricValue::Counter { value: 1027.0 },
366                )
367                .with_timestamp(Utc.timestamp_opt(1395066363, 0).latest())
368                .with_tags(Some(metric_tags!(
369                    "method" => "post",
370                    "code" => "200",
371                ))),
372                Metric::new(
373                    "http_requests_total",
374                    MetricKind::Absolute,
375                    MetricValue::Counter { value: 3.0 },
376                )
377                .with_timestamp(Utc.timestamp_opt(1395066363, 0).latest())
378                .with_tags(Some(metric_tags!(
379                    "method" => "post",
380                    "code" => "400"
381                )))
382            ]),
383        );
384    }
385
386    #[test]
387    fn test_gauge() {
388        let exp = r"
389            # HELP latency A gauge
390            # TYPE latency gauge
391            latency 123.0 1612411506789
392            ";
393
394        assert_event_data_eq!(
395            events_to_metrics(parse_text(exp)),
396            Ok(vec![
397                Metric::new(
398                    "latency",
399                    MetricKind::Absolute,
400                    MetricValue::Gauge { value: 123.0 },
401                )
402                .with_timestamp(Some(*TIMESTAMP))
403            ]),
404        );
405    }
406
407    #[test]
408    fn test_gauge_minimalistic() {
409        let exp = r"
410            metric_without_timestamp_and_labels 12.47 1612411506789
411            ";
412
413        assert_event_data_eq!(
414            events_to_metrics(parse_text(exp)),
415            Ok(vec![
416                Metric::new(
417                    "metric_without_timestamp_and_labels",
418                    MetricKind::Absolute,
419                    MetricValue::Gauge { value: 12.47 },
420                )
421                .with_timestamp(Some(*TIMESTAMP))
422            ]),
423        );
424    }
425
426    #[test]
427    fn test_gauge_empty_labels() {
428        let exp = r"
429            no_labels{} 3 1612411506789
430            ";
431
432        assert_event_data_eq!(
433            events_to_metrics(parse_text(exp)),
434            Ok(vec![
435                Metric::new(
436                    "no_labels",
437                    MetricKind::Absolute,
438                    MetricValue::Gauge { value: 3.0 },
439                )
440                .with_timestamp(Some(*TIMESTAMP))
441            ]),
442        );
443    }
444
445    #[test]
446    fn test_gauge_minimalistic_escaped() {
447        let exp = r#"
448            msdos_file_access_time_seconds{path="C:\\DIR\\FILE.TXT",error="Cannot find file:\n\"FILE.TXT\""} 1.458255915e9 1612411506789
449            "#;
450
451        assert_event_data_eq!(
452            events_to_metrics(parse_text(exp)),
453            Ok(vec![
454                Metric::new(
455                    "msdos_file_access_time_seconds",
456                    MetricKind::Absolute,
457                    MetricValue::Gauge {
458                        value: 1458255915.0
459                    },
460                )
461                .with_tags(Some(metric_tags!(
462                    "path" => "C:\\DIR\\FILE.TXT",
463                    "error" => "Cannot find file:\n\"FILE.TXT\"",
464                )))
465                .with_timestamp(Some(*TIMESTAMP))
466            ]),
467        );
468    }
469
470    #[test]
471    fn test_tag_value_contain_bracket() {
472        let exp = r#"
473            # HELP name counter
474            # TYPE name counter
475            name{tag="}"} 0 1612411506789
476            "#;
477        assert_event_data_eq!(
478            events_to_metrics(parse_text(exp)),
479            Ok(vec![
480                Metric::new(
481                    "name",
482                    MetricKind::Absolute,
483                    MetricValue::Counter { value: 0.0 },
484                )
485                .with_tags(Some(metric_tags! { "tag" => "}" }))
486                .with_timestamp(Some(*TIMESTAMP))
487            ]),
488        );
489    }
490
491    #[test]
492    fn test_parse_tag_value_contain_comma() {
493        let exp = r#"
494            # HELP name counter
495            # TYPE name counter
496            name{tag="a,b"} 0 1612411506789
497            "#;
498        assert_event_data_eq!(
499            events_to_metrics(parse_text(exp)),
500            Ok(vec![
501                Metric::new(
502                    "name",
503                    MetricKind::Absolute,
504                    MetricValue::Counter { value: 0.0 },
505                )
506                .with_tags(Some(metric_tags! { "tag" => "a,b" }))
507                .with_timestamp(Some(*TIMESTAMP))
508            ]),
509        );
510    }
511
512    #[test]
513    fn test_parse_tag_escaping() {
514        let exp = r#"
515            # HELP name counter
516            # TYPE name counter
517            name{tag="\\n"} 0 1612411506789
518            "#;
519        assert_event_data_eq!(
520            events_to_metrics(parse_text(exp)),
521            Ok(vec![
522                Metric::new(
523                    "name",
524                    MetricKind::Absolute,
525                    MetricValue::Counter { value: 0.0 },
526                )
527                .with_tags(Some(metric_tags! { "tag" => "\\n" }))
528                .with_timestamp(Some(*TIMESTAMP))
529            ]),
530        );
531    }
532
533    #[test]
534    fn test_parse_tag_dont_trim_value() {
535        let exp = r#"
536            # HELP name counter
537            # TYPE name counter
538            name{tag=" * "} 0 1612411506789
539            "#;
540        assert_event_data_eq!(
541            events_to_metrics(parse_text(exp)),
542            Ok(vec![
543                Metric::new(
544                    "name",
545                    MetricKind::Absolute,
546                    MetricValue::Counter { value: 0.0 },
547                )
548                .with_tags(Some(metric_tags! { "tag" => " * " }))
549                .with_timestamp(Some(*TIMESTAMP))
550            ]),
551        );
552    }
553
554    #[test]
555    fn test_parse_tag_value_containing_equals() {
556        let exp = r#"
557            telemetry_scrape_size_bytes_count{registry="default",content_type="text/plain; version=0.0.4"} 1890 1612411506789
558            "#;
559
560        assert_event_data_eq!(
561            events_to_metrics(parse_text(exp)),
562            Ok(vec![
563                Metric::new(
564                    "telemetry_scrape_size_bytes_count",
565                    MetricKind::Absolute,
566                    MetricValue::Gauge { value: 1890.0 },
567                )
568                .with_tags(Some(metric_tags!( "registry" => "default",
569                    "content_type" => "text/plain; version=0.0.4" )))
570                .with_timestamp(Some(*TIMESTAMP))
571            ]),
572        );
573    }
574
575    #[test]
576    fn test_parse_tag_error_no_value() {
577        let exp = r#"
578            telemetry_scrape_size_bytes_count{registry="default",content_type} 1890 1612411506789
579            "#;
580
581        assert!(events_to_metrics(parse_text(exp)).is_err());
582    }
583
584    #[test]
585    fn test_parse_tag_error_equals_empty_value() {
586        let exp = r#"
587            telemetry_scrape_size_bytes_count{registry="default",content_type=} 1890 1612411506789
588            "#;
589
590        assert!(events_to_metrics(parse_text(exp)).is_err());
591    }
592
593    #[test]
594    fn test_gauge_weird_timestamp() {
595        let exp = r#"
596            something_weird{problem="division by zero"} +Inf -3982045000
597            "#;
598
599        assert_event_data_eq!(
600            events_to_metrics(parse_text(exp)),
601            Ok(vec![
602                Metric::new(
603                    "something_weird",
604                    MetricKind::Absolute,
605                    MetricValue::Gauge {
606                        value: f64::INFINITY
607                    },
608                )
609                .with_timestamp(Utc.timestamp_opt(-3982045, 0).latest())
610                .with_tags(Some(metric_tags!("problem" => "division by zero")))
611            ]),
612        );
613    }
614
615    #[test]
616    fn test_gauge_tabs() {
617        let exp = r#"
618            # TYPE	latency	gauge
619            latency{env="production"}	1.0		1395066363000
620            latency{env="testing"}		2.0		1395066363000
621            "#;
622
623        assert_event_data_eq!(
624            events_to_metrics(parse_text(exp)),
625            Ok(vec![
626                Metric::new(
627                    "latency",
628                    MetricKind::Absolute,
629                    MetricValue::Gauge { value: 1.0 },
630                )
631                .with_timestamp(Utc.timestamp_opt(1395066363, 0).latest())
632                .with_tags(Some(metric_tags!("env" => "production"))),
633                Metric::new(
634                    "latency",
635                    MetricKind::Absolute,
636                    MetricValue::Gauge { value: 2.0 },
637                )
638                .with_timestamp(Utc.timestamp_opt(1395066363, 0).latest())
639                .with_tags(Some(metric_tags!("env" => "testing")))
640            ]),
641        );
642    }
643
644    #[test]
645    fn test_mixed() {
646        let exp = r"
647            # TYPE uptime counter
648            uptime 123.0 1612411506789
649            # TYPE temperature gauge
650            temperature -1.5 1612411506789
651            # TYPE launch_count counter
652            launch_count 10.0 1612411506789
653            ";
654
655        assert_event_data_eq!(
656            events_to_metrics(parse_text(exp)),
657            Ok(vec![
658                Metric::new(
659                    "uptime",
660                    MetricKind::Absolute,
661                    MetricValue::Counter { value: 123.0 },
662                )
663                .with_timestamp(Some(*TIMESTAMP)),
664                Metric::new(
665                    "temperature",
666                    MetricKind::Absolute,
667                    MetricValue::Gauge { value: -1.5 },
668                )
669                .with_timestamp(Some(*TIMESTAMP)),
670                Metric::new(
671                    "launch_count",
672                    MetricKind::Absolute,
673                    MetricValue::Counter { value: 10.0 },
674                )
675                .with_timestamp(Some(*TIMESTAMP))
676            ]),
677        );
678    }
679
680    #[test]
681    fn test_no_value() {
682        let exp = r#"
683            # TYPE latency counter
684            latency{env="production"}
685            "#;
686
687        assert!(events_to_metrics(parse_text(exp)).is_err());
688    }
689
690    #[test]
691    fn test_no_name() {
692        let exp = r"
693            # TYPE uptime counter
694            123.0 1612411506789
695            ";
696
697        assert!(events_to_metrics(parse_text(exp)).is_err());
698    }
699
700    #[test]
701    fn test_mixed_and_loosely_typed() {
702        let exp = r"
703            # TYPE uptime counter
704            uptime 123.0 1612411506789
705            last_downtime 4.0 1612411506789
706            # TYPE temperature gauge
707            temperature -1.5 1612411506789
708            temperature_7_days_average 0.1 1612411506789
709            ";
710
711        assert_event_data_eq!(
712            events_to_metrics(parse_text(exp)),
713            Ok(vec![
714                Metric::new(
715                    "uptime",
716                    MetricKind::Absolute,
717                    MetricValue::Counter { value: 123.0 },
718                )
719                .with_timestamp(Some(*TIMESTAMP)),
720                Metric::new(
721                    "last_downtime",
722                    MetricKind::Absolute,
723                    MetricValue::Gauge { value: 4.0 },
724                )
725                .with_timestamp(Some(*TIMESTAMP)),
726                Metric::new(
727                    "temperature",
728                    MetricKind::Absolute,
729                    MetricValue::Gauge { value: -1.5 },
730                )
731                .with_timestamp(Some(*TIMESTAMP)),
732                Metric::new(
733                    "temperature_7_days_average",
734                    MetricKind::Absolute,
735                    MetricValue::Gauge { value: 0.1 },
736                )
737                .with_timestamp(Some(*TIMESTAMP))
738            ]),
739        );
740    }
741
742    #[test]
743    fn test_histogram() {
744        let exp = r#"
745            # HELP http_request_duration_seconds A histogram of the request duration.
746            # TYPE http_request_duration_seconds histogram
747            http_request_duration_seconds_bucket{le="0.05"} 24054 1612411506789
748            http_request_duration_seconds_bucket{le="0.1"} 33444 1612411506789
749            http_request_duration_seconds_bucket{le="0.2"} 100392 1612411506789
750            http_request_duration_seconds_bucket{le="0.5"} 129389 1612411506789
751            http_request_duration_seconds_bucket{le="1"} 133988 1612411506789
752            http_request_duration_seconds_bucket{le="+Inf"} 144320 1612411506789
753            http_request_duration_seconds_sum 53423 1612411506789
754            http_request_duration_seconds_count 144320 1612411506789
755            "#;
756
757        assert_event_data_eq!(
758            events_to_metrics(parse_text(exp)),
759            Ok(vec![
760                Metric::new(
761                    "http_request_duration_seconds",
762                    MetricKind::Absolute,
763                    MetricValue::AggregatedHistogram {
764                        buckets: vector_lib::buckets![
765                            0.05 => 24054, 0.1 => 9390, 0.2 => 66948, 0.5 => 28997, 1.0 => 4599
766                        ],
767                        count: 144320,
768                        sum: 53423.0,
769                    },
770                )
771                .with_timestamp(Some(*TIMESTAMP))
772            ]),
773        );
774    }
775
776    #[test]
777    fn test_histogram_doesnt_panic() {
778        let mut exp = r#"
779            # HELP http_request_duration_seconds A histogram of the request duration.
780            # TYPE http_request_duration_seconds histogram
781            "#
782        .to_string();
783
784        let to_float = |v: i32| -> f64 { v as f64 };
785        exp += &(0..=15)
786            .map(to_float)
787            .chain(std::iter::once(f64::NAN))
788            .chain((16..=20).map(to_float))
789            .rev()
790            .map(|f| format!("http_request_duration_seconds_bucket{{le=\"{f}\"}} 0 1612411506789"))
791            .join("\n");
792
793        assert_event_data_eq!(
794            events_to_metrics(parse_text(&exp)),
795            Ok(vec![
796                Metric::new(
797                    "http_request_duration_seconds",
798                    MetricKind::Absolute,
799                    MetricValue::AggregatedHistogram {
800                        // These bucket values don't mean/test anything, they just test that the
801                        // sort works without panicking
802                        buckets: (0..=20)
803                            .map(to_float)
804                            .chain(std::iter::once(f64::NAN))
805                            .map(|upper_limit| Bucket {
806                                upper_limit,
807                                count: 0
808                            })
809                            .collect(),
810                        count: 0,
811                        sum: 0.0,
812                    },
813                )
814                .with_timestamp(Some(*TIMESTAMP))
815            ]),
816        );
817    }
818
819    #[test]
820    fn test_histogram_out_of_order() {
821        let exp = r#"
822            # HELP duration A histogram of the request duration.
823            # TYPE duration histogram
824            duration_bucket{le="+Inf"} 144320 1612411506789
825            duration_bucket{le="1"} 133988 1612411506789
826            duration_sum 53423 1612411506789
827            duration_count 144320 1612411506789
828            "#;
829
830        assert_event_data_eq!(
831            events_to_metrics(parse_text(exp)),
832            Ok(vec![
833                Metric::new(
834                    "duration",
835                    MetricKind::Absolute,
836                    MetricValue::AggregatedHistogram {
837                        buckets: vector_lib::buckets![1.0 => 133988],
838                        count: 144320,
839                        sum: 53423.0,
840                    },
841                )
842                .with_timestamp(Some(*TIMESTAMP))
843            ]),
844        );
845    }
846
847    #[test]
848    fn test_histogram_backward_values() {
849        let exp = r#"
850            # HELP duration A histogram of the request duration.
851            # TYPE duration histogram
852            duration_bucket{le="1"} 2000 1612411506789
853            duration_bucket{le="10"} 1000 1612411506789
854            duration_bucket{le="+Inf"} 2000 1612411506789
855            duration_sum 2000 1612411506789
856            duration_count 2000 1612411506789
857            "#;
858
859        assert_event_data_eq!(
860            events_to_metrics(parse_text(exp)),
861            Ok(vec![
862                Metric::new(
863                    "duration",
864                    MetricKind::Absolute,
865                    MetricValue::AggregatedHistogram {
866                        buckets: vector_lib::buckets![1.0 => 2000, 10.0 => 0],
867                        count: 2000,
868                        sum: 2000.0,
869                    },
870                )
871                .with_timestamp(Some(*TIMESTAMP))
872            ]),
873        );
874    }
875
876    #[test]
877    fn test_histogram_with_labels() {
878        let exp = r#"
879            # HELP gitlab_runner_job_duration_seconds Histogram of job durations
880            # TYPE gitlab_runner_job_duration_seconds histogram
881            gitlab_runner_job_duration_seconds_bucket{runner="z",le="30"} 327 1612411506789
882            gitlab_runner_job_duration_seconds_bucket{runner="z",le="60"} 474 1612411506789
883            gitlab_runner_job_duration_seconds_bucket{runner="z",le="300"} 535 1612411506789
884            gitlab_runner_job_duration_seconds_bucket{runner="z",le="600"} 536 1612411506789
885            gitlab_runner_job_duration_seconds_bucket{runner="z",le="1800"} 536 1612411506789
886            gitlab_runner_job_duration_seconds_bucket{runner="z",le="3600"} 536 1612411506789
887            gitlab_runner_job_duration_seconds_bucket{runner="z",le="7200"} 536 1612411506789
888            gitlab_runner_job_duration_seconds_bucket{runner="z",le="10800"} 536 1612411506789
889            gitlab_runner_job_duration_seconds_bucket{runner="z",le="18000"} 536 1612411506789
890            gitlab_runner_job_duration_seconds_bucket{runner="z",le="36000"} 536 1612411506789
891            gitlab_runner_job_duration_seconds_bucket{runner="z",le="+Inf"} 536 1612411506789
892            gitlab_runner_job_duration_seconds_sum{runner="z"} 19690.129384881966 1612411506789
893            gitlab_runner_job_duration_seconds_count{runner="z"} 536 1612411506789
894            gitlab_runner_job_duration_seconds_bucket{runner="x",le="30"} 1 1612411506789
895            gitlab_runner_job_duration_seconds_bucket{runner="x",le="60"} 1 1612411506789
896            gitlab_runner_job_duration_seconds_bucket{runner="x",le="300"} 1 1612411506789
897            gitlab_runner_job_duration_seconds_bucket{runner="x",le="600"} 1 1612411506789
898            gitlab_runner_job_duration_seconds_bucket{runner="x",le="1800"} 1 1612411506789
899            gitlab_runner_job_duration_seconds_bucket{runner="x",le="3600"} 1 1612411506789
900            gitlab_runner_job_duration_seconds_bucket{runner="x",le="7200"} 1 1612411506789
901            gitlab_runner_job_duration_seconds_bucket{runner="x",le="10800"} 1 1612411506789
902            gitlab_runner_job_duration_seconds_bucket{runner="x",le="18000"} 1 1612411506789
903            gitlab_runner_job_duration_seconds_bucket{runner="x",le="36000"} 1 1612411506789
904            gitlab_runner_job_duration_seconds_bucket{runner="x",le="+Inf"} 1 1612411506789
905            gitlab_runner_job_duration_seconds_sum{runner="x"} 28.975436316 1612411506789
906            gitlab_runner_job_duration_seconds_count{runner="x"} 1 1612411506789
907            gitlab_runner_job_duration_seconds_bucket{runner="y",le="30"} 285 1612411506789
908            gitlab_runner_job_duration_seconds_bucket{runner="y",le="60"} 1165 1612411506789
909            gitlab_runner_job_duration_seconds_bucket{runner="y",le="300"} 3071 1612411506789
910            gitlab_runner_job_duration_seconds_bucket{runner="y",le="600"} 3151 1612411506789
911            gitlab_runner_job_duration_seconds_bucket{runner="y",le="1800"} 3252 1612411506789
912            gitlab_runner_job_duration_seconds_bucket{runner="y",le="3600"} 3255 1612411506789
913            gitlab_runner_job_duration_seconds_bucket{runner="y",le="7200"} 3255 1612411506789
914            gitlab_runner_job_duration_seconds_bucket{runner="y",le="10800"} 3255 1612411506789
915            gitlab_runner_job_duration_seconds_bucket{runner="y",le="18000"} 3255 1612411506789
916            gitlab_runner_job_duration_seconds_bucket{runner="y",le="36000"} 3255 1612411506789
917            gitlab_runner_job_duration_seconds_bucket{runner="y",le="+Inf"} 3255 1612411506789
918            gitlab_runner_job_duration_seconds_sum{runner="y"} 381111.7498891335 1612411506789
919            gitlab_runner_job_duration_seconds_count{runner="y"} 3255 1612411506789
920        "#;
921
922        assert_event_data_eq!(
923            events_to_metrics(parse_text(exp)),
924            Ok(vec![
925                Metric::new(
926                    "gitlab_runner_job_duration_seconds", MetricKind::Absolute, MetricValue::AggregatedHistogram {
927                        buckets: vector_lib::buckets![
928                            30.0 => 327,
929                            60.0 => 147,
930                            300.0 => 61,
931                            600.0 => 1,
932                            1800.0 => 0,
933                            3600.0 => 0,
934                            7200.0 => 0,
935                            10800.0 => 0,
936                            18000.0 => 0,
937                            36000.0 => 0
938                        ],
939                        count: 536,
940                        sum: 19690.129384881966,
941                    },
942                )
943                    .with_tags(Some(metric_tags!("runner" => "z")))
944                    .with_timestamp(Some(*TIMESTAMP)),
945                Metric::new(
946                    "gitlab_runner_job_duration_seconds", MetricKind::Absolute, MetricValue::AggregatedHistogram {
947                        buckets: vector_lib::buckets![
948                            30.0 => 1,
949                            60.0 => 0,
950                            300.0 => 0,
951                            600.0 => 0,
952                            1800.0 => 0,
953                            3600.0 => 0,
954                            7200.0 => 0,
955                            10800.0 => 0,
956                            18000.0 => 0,
957                            36000.0 => 0
958                        ],
959                        count: 1,
960                        sum: 28.975436316,
961                    },
962                )
963                    .with_tags(Some(metric_tags!("runner" => "x")))
964                    .with_timestamp(Some(*TIMESTAMP)),
965                Metric::new(
966                    "gitlab_runner_job_duration_seconds", MetricKind::Absolute, MetricValue::AggregatedHistogram {
967                        buckets: vector_lib::buckets![
968                            30.0 => 285, 60.0 => 880, 300.0 => 1906, 600.0 => 80, 1800.0 => 101, 3600.0 => 3,
969                            7200.0 => 0, 10800.0 => 0, 18000.0 => 0, 36000.0 => 0
970                        ],
971                        count: 3255,
972                        sum: 381111.7498891335,
973                    },
974                )
975                    .with_tags(Some(metric_tags!("runner" => "y")))
976                    .with_timestamp(Some(*TIMESTAMP))
977            ]),
978        );
979    }
980
981    #[test]
982    fn test_summary() {
983        let exp = r#"
984            # HELP rpc_duration_seconds A summary of the RPC duration in seconds.
985            # TYPE rpc_duration_seconds summary
986            rpc_duration_seconds{service="a",quantile="0.01"} 3102 1612411506789
987            rpc_duration_seconds{service="a",quantile="0.05"} 3272 1612411506789
988            rpc_duration_seconds{service="a",quantile="0.5"} 4773 1612411506789
989            rpc_duration_seconds{service="a",quantile="0.9"} 9001 1612411506789
990            rpc_duration_seconds{service="a",quantile="0.99"} 76656 1612411506789
991            rpc_duration_seconds_sum{service="a"} 1.7560473e+07 1612411506789
992            rpc_duration_seconds_count{service="a"} 2693 1612411506789
993            # HELP go_gc_duration_seconds A summary of the GC invocation durations.
994            # TYPE go_gc_duration_seconds summary
995            go_gc_duration_seconds{quantile="0"} 0.009460965 1612411506789
996            go_gc_duration_seconds{quantile="0.25"} 0.009793382 1612411506789
997            go_gc_duration_seconds{quantile="0.5"} 0.009870205 1612411506789
998            go_gc_duration_seconds{quantile="0.75"} 0.01001838 1612411506789
999            go_gc_duration_seconds{quantile="1"} 0.018827136 1612411506789
1000            go_gc_duration_seconds_sum 4668.551713715 1612411506789
1001            go_gc_duration_seconds_count 602767 1612411506789
1002            "#;
1003
1004        assert_event_data_eq!(
1005            events_to_metrics(parse_text(exp)),
1006            Ok(vec![
1007                Metric::new(
1008                    "rpc_duration_seconds",
1009                    MetricKind::Absolute,
1010                    MetricValue::AggregatedSummary {
1011                        quantiles: vector_lib::quantiles![
1012                            0.01 => 3102.0,
1013                            0.05 => 3272.0,
1014                            0.5 => 4773.0,
1015                            0.9 => 9001.0,
1016                            0.99 => 76656.0
1017                        ],
1018                        count: 2693,
1019                        sum: 1.7560473e+07,
1020                    },
1021                )
1022                .with_tags(Some(metric_tags!("service" => "a")))
1023                .with_timestamp(Some(*TIMESTAMP)),
1024                Metric::new(
1025                    "go_gc_duration_seconds",
1026                    MetricKind::Absolute,
1027                    MetricValue::AggregatedSummary {
1028                        quantiles: vector_lib::quantiles![
1029                            0.0 => 0.009460965,
1030                            0.25 => 0.009793382,
1031                            0.5 => 0.009870205,
1032                            0.75 => 0.01001838,
1033                            1.0 => 0.018827136
1034                        ],
1035                        count: 602767,
1036                        sum: 4668.551713715,
1037                    },
1038                )
1039                .with_timestamp(Some(*TIMESTAMP)),
1040            ]),
1041        );
1042    }
1043
1044    // https://github.com/vectordotdev/vector/issues/3276
1045    #[test]
1046    fn test_nginx() {
1047        let exp = r#"
1048            # HELP nginx_server_bytes request/response bytes
1049            # TYPE nginx_server_bytes counter
1050            nginx_server_bytes{direction="in",host="*"} 263719
1051            nginx_server_bytes{direction="in",host="_"} 255061
1052            nginx_server_bytes{direction="in",host="nginx-vts-status"} 8658
1053            nginx_server_bytes{direction="out",host="*"} 944199
1054            nginx_server_bytes{direction="out",host="_"} 360775
1055            nginx_server_bytes{direction="out",host="nginx-vts-status"} 583424
1056            # HELP nginx_server_cache cache counter
1057            # TYPE nginx_server_cache counter
1058            nginx_server_cache{host="*",status="bypass"} 0
1059            nginx_server_cache{host="*",status="expired"} 0
1060            nginx_server_cache{host="*",status="hit"} 0
1061            nginx_server_cache{host="*",status="miss"} 0
1062            nginx_server_cache{host="*",status="revalidated"} 0
1063            nginx_server_cache{host="*",status="scarce"} 0
1064            "#;
1065
1066        let now = Utc::now();
1067        let result = events_to_metrics(parse_text(exp)).expect("Parsing failed");
1068        // Reset all the timestamps for comparison
1069        let result: Vec<_> = result
1070            .into_iter()
1071            .map(|metric| {
1072                assert!(metric.timestamp().expect("Missing timestamp") >= now);
1073                metric.with_timestamp(Some(*TIMESTAMP))
1074            })
1075            .collect();
1076
1077        assert_event_data_eq!(
1078            result,
1079            vec![
1080                Metric::new(
1081                    "nginx_server_bytes",
1082                    MetricKind::Absolute,
1083                    MetricValue::Counter { value: 263719.0 },
1084                )
1085                .with_tags(Some(metric_tags! { "direction" => "in", "host" => "*" }))
1086                .with_timestamp(Some(*TIMESTAMP)),
1087                Metric::new(
1088                    "nginx_server_bytes",
1089                    MetricKind::Absolute,
1090                    MetricValue::Counter { value: 255061.0 },
1091                )
1092                .with_tags(Some(metric_tags! { "direction" => "in", "host" => "_" }))
1093                .with_timestamp(Some(*TIMESTAMP)),
1094                Metric::new(
1095                    "nginx_server_bytes",
1096                    MetricKind::Absolute,
1097                    MetricValue::Counter { value: 8658.0 },
1098                )
1099                .with_tags(Some(
1100                    metric_tags! { "direction" => "in", "host" => "nginx-vts-status" }
1101                ))
1102                .with_timestamp(Some(*TIMESTAMP)),
1103                Metric::new(
1104                    "nginx_server_bytes",
1105                    MetricKind::Absolute,
1106                    MetricValue::Counter { value: 944199.0 },
1107                )
1108                .with_tags(Some(metric_tags! { "direction" => "out", "host" => "*" }))
1109                .with_timestamp(Some(*TIMESTAMP)),
1110                Metric::new(
1111                    "nginx_server_bytes",
1112                    MetricKind::Absolute,
1113                    MetricValue::Counter { value: 360775.0 },
1114                )
1115                .with_tags(Some(metric_tags! { "direction" => "out", "host" => "_" }))
1116                .with_timestamp(Some(*TIMESTAMP)),
1117                Metric::new(
1118                    "nginx_server_bytes",
1119                    MetricKind::Absolute,
1120                    MetricValue::Counter { value: 583424.0 },
1121                )
1122                .with_tags(Some(
1123                    metric_tags! { "direction" => "out", "host" => "nginx-vts-status" }
1124                ))
1125                .with_timestamp(Some(*TIMESTAMP)),
1126                Metric::new(
1127                    "nginx_server_cache",
1128                    MetricKind::Absolute,
1129                    MetricValue::Counter { value: 0.0 },
1130                )
1131                .with_tags(Some(metric_tags! { "host" => "*", "status" => "bypass" }))
1132                .with_timestamp(Some(*TIMESTAMP)),
1133                Metric::new(
1134                    "nginx_server_cache",
1135                    MetricKind::Absolute,
1136                    MetricValue::Counter { value: 0.0 },
1137                )
1138                .with_tags(Some(metric_tags! { "host" => "*", "status" => "expired" }))
1139                .with_timestamp(Some(*TIMESTAMP)),
1140                Metric::new(
1141                    "nginx_server_cache",
1142                    MetricKind::Absolute,
1143                    MetricValue::Counter { value: 0.0 },
1144                )
1145                .with_tags(Some(metric_tags! { "host" => "*", "status" => "hit" }))
1146                .with_timestamp(Some(*TIMESTAMP)),
1147                Metric::new(
1148                    "nginx_server_cache",
1149                    MetricKind::Absolute,
1150                    MetricValue::Counter { value: 0.0 },
1151                )
1152                .with_tags(Some(metric_tags! { "host" => "*", "status" => "miss" }))
1153                .with_timestamp(Some(*TIMESTAMP)),
1154                Metric::new(
1155                    "nginx_server_cache",
1156                    MetricKind::Absolute,
1157                    MetricValue::Counter { value: 0.0 },
1158                )
1159                .with_tags(Some(
1160                    metric_tags! { "host" => "*", "status" => "revalidated" }
1161                ))
1162                .with_timestamp(Some(*TIMESTAMP)),
1163                Metric::new(
1164                    "nginx_server_cache",
1165                    MetricKind::Absolute,
1166                    MetricValue::Counter { value: 0.0 },
1167                )
1168                .with_tags(Some(metric_tags! { "host" => "*", "status" => "scarce" }))
1169                .with_timestamp(Some(*TIMESTAMP))
1170            ]
1171        );
1172    }
1173
1174    #[test]
1175    fn test_overrides_nothing_overwritten() {
1176        let exp = r#"
1177            # TYPE jobs_total counter
1178            # HELP jobs_total Total number of jobs
1179            jobs_total{type="a"} 1.0 1612411506789
1180            "#;
1181
1182        assert_event_data_eq!(
1183            events_to_metrics(parse_text_with_overrides(exp, vec![], false)),
1184            Ok(vec![
1185                Metric::new(
1186                    "jobs_total",
1187                    MetricKind::Absolute,
1188                    MetricValue::Counter { value: 1.0 },
1189                )
1190                .with_tags(Some(metric_tags! { "type" => "a" }))
1191                .with_timestamp(Some(*TIMESTAMP))
1192            ]),
1193        );
1194    }
1195
1196    #[test]
1197    fn test_overrides_label_overwritten() {
1198        let exp = r#"
1199            # TYPE jobs_total counter
1200            # HELP jobs_total Total number of jobs
1201            jobs_total{type="a"} 1.0 1612411506789
1202            "#;
1203
1204        assert_event_data_eq!(
1205            events_to_metrics(parse_text_with_overrides(
1206                exp,
1207                vec![("type".to_owned(), "b".to_owned())],
1208                false
1209            )),
1210            Ok(vec![
1211                Metric::new(
1212                    "jobs_total",
1213                    MetricKind::Absolute,
1214                    MetricValue::Counter { value: 1.0 },
1215                )
1216                .with_tags(Some(metric_tags! { "type" => "b" }))
1217                .with_timestamp(Some(*TIMESTAMP))
1218            ]),
1219        );
1220    }
1221
1222    // This matches the behaviour of the real Prometheus Pushgateway, which I
1223    // tested manually.
1224    #[test]
1225    fn test_overrides_last_value_preferred() {
1226        let exp = r#"
1227            # TYPE jobs_total counter
1228            # HELP jobs_total Total number of jobs
1229            jobs_total{type="a"} 1.0 1612411506789
1230            "#;
1231
1232        assert_event_data_eq!(
1233            events_to_metrics(parse_text_with_overrides(
1234                exp,
1235                vec![
1236                    ("type".to_owned(), "b".to_owned()),
1237                    ("type".to_owned(), "c".to_owned())
1238                ],
1239                false
1240            )),
1241            Ok(vec![
1242                Metric::new(
1243                    "jobs_total",
1244                    MetricKind::Absolute,
1245                    MetricValue::Counter { value: 1.0 },
1246                )
1247                .with_tags(Some(metric_tags! { "type" => "c" }))
1248                .with_timestamp(Some(*TIMESTAMP))
1249            ]),
1250        );
1251    }
1252
1253    #[test]
1254    fn test_aggregation_enabled_only_aggregates_counter_and_histogram() {
1255        let exp = r#"
1256            # TYPE jobs_total counter
1257            # HELP jobs_total Total number of jobs
1258            jobs_total{type="a"} 1.0 1612411506789
1259            # TYPE jobs_current gauge
1260            # HELP jobs_current Current number of jobs
1261            jobs_current{type="a"} 5.0 1612411506789
1262            # TYPE jobs_distribution histogram
1263            # HELP jobs_distribution Distribution of jobs
1264            jobs_distribution_bucket{type="a",le="1"} 0.0 1612411506789
1265            jobs_distribution_bucket{type="a",le="2.5"} 0.0 1612411506789
1266            jobs_distribution_bucket{type="a",le="5"} 0.0 1612411506789
1267            jobs_distribution_bucket{type="a",le="10"} 1.0 1612411506789
1268            jobs_distribution_bucket{type="a",le="+Inf"} 1.0 1612411506789
1269            jobs_distribution_sum{type="a"} 8.0 1612411506789
1270            jobs_distribution_count{type="a"} 1.0 1612411506789
1271            # TYPE jobs_summary summary
1272            # HELP jobs_summary Summary of jobs
1273            jobs_summary_sum{type="a"} 8.0 1612411506789
1274            jobs_summary_count{type="a"} 1.0 1612411506789
1275            "#;
1276
1277        assert_event_data_eq!(
1278            events_to_metrics(parse_text_with_overrides(exp, vec![], true)),
1279            Ok(vec![
1280                Metric::new(
1281                    "jobs_total",
1282                    MetricKind::Incremental,
1283                    MetricValue::Counter { value: 1.0 },
1284                )
1285                .with_tags(Some(metric_tags! { "type" => "a" }))
1286                .with_timestamp(Some(*TIMESTAMP)),
1287                Metric::new(
1288                    "jobs_current",
1289                    MetricKind::Absolute,
1290                    MetricValue::Gauge { value: 5.0 },
1291                )
1292                .with_tags(Some(metric_tags! { "type" => "a" }))
1293                .with_timestamp(Some(*TIMESTAMP)),
1294                Metric::new(
1295                    "jobs_distribution",
1296                    MetricKind::Incremental,
1297                    MetricValue::AggregatedHistogram {
1298                        buckets: vector_lib::buckets![
1299                            1.0 => 0, 2.5 => 0, 5.0 => 0, 10.0 => 1
1300                        ],
1301                        count: 1,
1302                        sum: 8.0,
1303                    },
1304                )
1305                .with_tags(Some(metric_tags! { "type" => "a" }))
1306                .with_timestamp(Some(*TIMESTAMP)),
1307                Metric::new(
1308                    "jobs_summary",
1309                    MetricKind::Absolute,
1310                    MetricValue::AggregatedSummary {
1311                        quantiles: vector_lib::quantiles![],
1312                        count: 1,
1313                        sum: 8.0,
1314                    },
1315                )
1316                .with_tags(Some(metric_tags! { "type" => "a" }))
1317                .with_timestamp(Some(*TIMESTAMP)),
1318            ]),
1319        );
1320    }
1321
1322    #[test]
1323    fn test_aggregation_disabled_all_absolute() {
1324        let exp = r#"
1325            # TYPE jobs_total counter
1326            # HELP jobs_total Total number of jobs
1327            jobs_total{type="a"} 1.0 1612411506789
1328            # TYPE jobs_current gauge
1329            # HELP jobs_current Current number of jobs
1330            jobs_current{type="a"} 5.0 1612411506789
1331            # TYPE jobs_distribution histogram
1332            # HELP jobs_distribution Distribution of jobs
1333            jobs_distribution_bucket{type="a",le="1"} 0.0 1612411506789
1334            jobs_distribution_bucket{type="a",le="2.5"} 0.0 1612411506789
1335            jobs_distribution_bucket{type="a",le="5"} 0.0 1612411506789
1336            jobs_distribution_bucket{type="a",le="10"} 1.0 1612411506789
1337            jobs_distribution_bucket{type="a",le="+Inf"} 1.0 1612411506789
1338            jobs_distribution_sum{type="a"} 8.0 1612411506789
1339            jobs_distribution_count{type="a"} 1.0 1612411506789
1340            # TYPE jobs_summary summary
1341            # HELP jobs_summary Summary of jobs
1342            jobs_summary_sum{type="a"} 8.0 1612411506789
1343            jobs_summary_count{type="a"} 1.0 1612411506789
1344            "#;
1345
1346        assert_event_data_eq!(
1347            events_to_metrics(parse_text_with_overrides(exp, vec![], false)),
1348            Ok(vec![
1349                Metric::new(
1350                    "jobs_total",
1351                    MetricKind::Absolute,
1352                    MetricValue::Counter { value: 1.0 },
1353                )
1354                .with_tags(Some(metric_tags! { "type" => "a" }))
1355                .with_timestamp(Some(*TIMESTAMP)),
1356                Metric::new(
1357                    "jobs_current",
1358                    MetricKind::Absolute,
1359                    MetricValue::Gauge { value: 5.0 },
1360                )
1361                .with_tags(Some(metric_tags! { "type" => "a" }))
1362                .with_timestamp(Some(*TIMESTAMP)),
1363                Metric::new(
1364                    "jobs_distribution",
1365                    MetricKind::Absolute,
1366                    MetricValue::AggregatedHistogram {
1367                        buckets: vector_lib::buckets![
1368                            1.0 => 0, 2.5 => 0, 5.0 => 0, 10.0 => 1
1369                        ],
1370                        count: 1,
1371                        sum: 8.0,
1372                    },
1373                )
1374                .with_tags(Some(metric_tags! { "type" => "a" }))
1375                .with_timestamp(Some(*TIMESTAMP)),
1376                Metric::new(
1377                    "jobs_summary",
1378                    MetricKind::Absolute,
1379                    MetricValue::AggregatedSummary {
1380                        quantiles: vector_lib::quantiles![],
1381                        count: 1,
1382                        sum: 8.0,
1383                    },
1384                )
1385                .with_tags(Some(metric_tags! { "type" => "a" }))
1386                .with_timestamp(Some(*TIMESTAMP)),
1387            ]),
1388        );
1389    }
1390
1391    #[test]
1392    fn test_skip_nan_counter_enabled() {
1393        let exp = r#"
1394            # TYPE name counter
1395            name{labelname="val1"} NaN 1612411506789
1396            name{labelname="val2"} 123.0 1612411506789
1397            "#;
1398
1399        let result = events_to_metrics(parse_text_with_nan_filtering(exp)).unwrap();
1400        assert_eq!(result.len(), 1);
1401        assert_eq!(result[0].name(), "name");
1402        match result[0].value() {
1403            MetricValue::Counter { value } => {
1404                assert_eq!(*value, 123.0);
1405            }
1406            _ => unreachable!(),
1407        }
1408        assert_eq!(result[0].tags().unwrap().get("labelname").unwrap(), "val2");
1409    }
1410
1411    #[test]
1412    fn test_skip_nan_counter_disabled() {
1413        let exp = r#"
1414            # TYPE name counter
1415            name{labelname="val1"} NaN 1612411506789
1416            name{labelname="val2"} 123.0 1612411506789
1417            "#;
1418
1419        let result = events_to_metrics(parse_text(exp)).unwrap();
1420        assert_eq!(result.len(), 2);
1421
1422        // Find the NaN metric
1423        let nan_metric = result
1424            .iter()
1425            .find(|m| m.tags().as_ref().and_then(|tags| tags.get("labelname")) == Some("val1"))
1426            .unwrap();
1427
1428        match nan_metric.value() {
1429            MetricValue::Counter { value } => {
1430                assert!(value.is_nan());
1431            }
1432            _ => unreachable!(),
1433        }
1434    }
1435
1436    #[test]
1437    fn test_skip_nan_gauge_enabled() {
1438        let exp = r#"
1439            # TYPE name gauge
1440            name{labelname="val1"} NaN 1612411506789
1441            name{labelname="val2"} 123.0 1612411506789
1442            "#;
1443
1444        let result = events_to_metrics(parse_text_with_nan_filtering(exp)).unwrap();
1445        assert_eq!(result.len(), 1);
1446        assert_eq!(result[0].name(), "name");
1447        match result[0].value() {
1448            MetricValue::Gauge { value } => {
1449                assert_eq!(*value, 123.0);
1450            }
1451            _ => unreachable!(),
1452        }
1453    }
1454
1455    #[test]
1456    fn test_skip_nan_histogram_bucket_enabled() {
1457        let exp = r#"
1458            # TYPE duration histogram
1459            duration_bucket{le="1"} 133988 1612411506789
1460            duration_bucket{le="NaN"} 144320 1612411506789
1461            duration_sum 53423 1612411506789
1462            duration_count 144320 1612411506789
1463            "#;
1464
1465        let result = events_to_metrics(parse_text_with_nan_filtering(exp)).unwrap();
1466        assert_eq!(result.len(), 0); // Should skip entire histogram due to NaN bucket le value
1467    }
1468
1469    #[test]
1470    fn test_skip_nan_histogram_sum_enabled() {
1471        let exp = r#"
1472            # TYPE duration histogram
1473            duration_bucket{le="1"} 133988 1612411506789
1474            duration_bucket{le="+Inf"} 144320 1612411506789
1475            duration_sum NaN 1612411506789
1476            duration_count 144320 1612411506789
1477            "#;
1478
1479        let result = events_to_metrics(parse_text_with_nan_filtering(exp)).unwrap();
1480        assert_eq!(result.len(), 0); // Should skip entire histogram due to NaN sum
1481    }
1482
1483    #[test]
1484    fn test_skip_nan_summary_enabled() {
1485        let exp = r#"
1486            # TYPE duration summary
1487            duration{quantile="0.5"} NaN 1612411506789
1488            duration{quantile="0.99"} 76656 1612411506789
1489            duration_sum 1.7560473e+07 1612411506789
1490            duration_count 2693 1612411506789
1491            "#;
1492
1493        let result = events_to_metrics(parse_text_with_nan_filtering(exp)).unwrap();
1494        assert_eq!(result.len(), 0); // Should skip entire summary due to NaN quantile value
1495    }
1496
1497    #[test]
1498    fn test_skip_nan_all_valid_values() {
1499        let exp = r#"
1500            # TYPE counter_metric counter
1501            counter_metric 123.0 1612411506789
1502            # TYPE gauge_metric gauge
1503            gauge_metric 456.0 1612411506789
1504            "#;
1505
1506        let result = events_to_metrics(parse_text_with_nan_filtering(exp)).unwrap();
1507        assert_eq!(result.len(), 2); // Both should be preserved
1508    }
1509}