prometheus_parser/
lib.rs

1#![deny(warnings)]
2
3use std::{collections::BTreeMap, convert::TryFrom};
4
5use indexmap::IndexMap;
6use snafu::ResultExt;
7
8mod line;
9
10pub use line::ErrorKind;
11use line::{Line, Metric, MetricKind};
12
13pub const METRIC_NAME_LABEL: &str = "__name__";
14
15#[allow(warnings)] // Ignore some clippy warnings
16pub mod proto {
17    include!(concat!(env!("OUT_DIR"), "/prometheus.rs"));
18
19    pub use metric_metadata::MetricType;
20
21    impl MetricType {
22        pub fn as_str(&self) -> &'static str {
23            match self {
24                MetricType::Counter => "counter",
25                MetricType::Gauge => "gauge",
26                MetricType::Histogram => "histogram",
27                MetricType::Summary => "summary",
28                MetricType::Gaugehistogram => "gaugehistogram",
29                MetricType::Info => "info",
30                MetricType::Stateset => "stateset",
31                MetricType::Unknown => "unknown",
32            }
33        }
34    }
35}
36
37#[derive(Debug, snafu::Snafu, PartialEq)]
38pub enum ParserError {
39    #[snafu(display("{}, line: `{}`", kind, line))]
40    WithLine {
41        line: String,
42        #[snafu(source)]
43        kind: ErrorKind,
44    },
45    #[snafu(display("expected \"le\" tag for histogram metric"))]
46    ExpectedLeTag,
47    #[snafu(display("expected \"quantile\" tag for summary metric"))]
48    ExpectedQuantileTag,
49
50    #[snafu(display("error parsing label value: {}", error))]
51    ParseLabelValue {
52        #[snafu(source)]
53        error: ErrorKind,
54    },
55
56    #[snafu(display("expected value in range [0, {}], found: {}", max, value))]
57    ValueOutOfRange { value: f64, max: u64 },
58
59    #[snafu(display("multiple metric kinds given for metric name `{}`", name))]
60    MultipleMetricKinds { name: String },
61    #[snafu(display("request is missing metric name label"))]
62    RequestNoNameLabel,
63}
64
65vector_common::impl_event_data_eq!(ParserError);
66
67/// Defines how the parser should behave when encountering metadata conflicts.
68#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
69pub enum MetadataConflictStrategy {
70    /// Silently ignore metadata conflicts, keeping the first metadata entry.
71    Ignore,
72    /// Reject requests with conflicting metadata.
73    #[default]
74    Reject,
75}
76
77#[derive(Debug, Eq, Hash, PartialEq)]
78pub struct GroupKey {
79    pub timestamp: Option<i64>,
80    pub labels: BTreeMap<String, String>,
81}
82
83#[derive(Debug, Default, PartialEq)]
84pub struct SummaryQuantile {
85    pub quantile: f64,
86    pub value: f64,
87}
88
89#[derive(Debug, Default, PartialEq)]
90pub struct SummaryMetric {
91    pub quantiles: Vec<SummaryQuantile>,
92    pub sum: f64,
93    pub count: u64,
94}
95
96#[derive(Debug, Default, PartialEq, PartialOrd)]
97pub struct HistogramBucket {
98    pub bucket: f64,
99    pub count: u64,
100}
101
102#[derive(Debug, Default, PartialEq)]
103pub struct HistogramMetric {
104    pub buckets: Vec<HistogramBucket>,
105    pub sum: f64,
106    pub count: u64,
107}
108
109#[derive(Debug, Default, PartialEq)]
110pub struct SimpleMetric {
111    pub value: f64,
112}
113
114type MetricMap<T> = IndexMap<GroupKey, T>;
115
116#[derive(Debug)]
117pub enum GroupKind {
118    Summary(MetricMap<SummaryMetric>),
119    Histogram(MetricMap<HistogramMetric>),
120    Gauge(MetricMap<SimpleMetric>),
121    Counter(MetricMap<SimpleMetric>),
122    Untyped(MetricMap<SimpleMetric>),
123}
124
125impl GroupKind {
126    fn new(kind: MetricKind) -> Self {
127        match kind {
128            MetricKind::Histogram => Self::Histogram(IndexMap::default()),
129            MetricKind::Summary => Self::Summary(IndexMap::default()),
130            MetricKind::Counter => Self::Counter(IndexMap::default()),
131            MetricKind::Gauge => Self::Gauge(IndexMap::default()),
132            MetricKind::Untyped => Self::Untyped(IndexMap::default()),
133        }
134    }
135
136    fn new_untyped(key: GroupKey, value: f64) -> Self {
137        let mut metrics = IndexMap::default();
138        metrics.insert(key, SimpleMetric { value });
139        Self::Untyped(metrics)
140    }
141
142    fn matches_kind(&self, kind: MetricKind) -> bool {
143        match self {
144            Self::Counter { .. } => kind == MetricKind::Counter,
145            Self::Gauge { .. } => kind == MetricKind::Gauge,
146            Self::Histogram { .. } => kind == MetricKind::Histogram,
147            Self::Summary { .. } => kind == MetricKind::Summary,
148            Self::Untyped { .. } => true,
149        }
150    }
151
152    /// Err(_) if there are irrecoverable error.
153    /// Ok(Some(metric)) if this metric belongs to another group.
154    /// Ok(None) pushed successfully.
155    fn try_push(
156        &mut self,
157        prefix_len: usize,
158        metric: Metric,
159    ) -> Result<Option<Metric>, ParserError> {
160        let suffix = &metric.name[prefix_len..];
161        let mut key = GroupKey {
162            timestamp: metric.timestamp,
163            labels: metric.labels,
164        };
165        let value = metric.value;
166
167        match self {
168            Self::Counter(metrics) | Self::Gauge(metrics) | Self::Untyped(metrics) => {
169                if !suffix.is_empty() {
170                    return Ok(Some(Metric {
171                        name: metric.name,
172                        timestamp: key.timestamp,
173                        labels: key.labels,
174                        value,
175                    }));
176                }
177                metrics.insert(key, SimpleMetric { value });
178            }
179            Self::Histogram(metrics) => match suffix {
180                "_bucket" => {
181                    let bucket = key.labels.remove("le").ok_or(ParserError::ExpectedLeTag)?;
182                    let (_, bucket) = line::Metric::parse_value(&bucket)
183                        .map_err(Into::into)
184                        .context(ParseLabelValueSnafu)?;
185                    let count = try_f64_to_u64(metric.value)?;
186                    matching_group(metrics, key)
187                        .buckets
188                        .push(HistogramBucket { bucket, count });
189                }
190                "_sum" => {
191                    let sum = metric.value;
192                    matching_group(metrics, key).sum = sum;
193                }
194                "_count" => {
195                    let count = try_f64_to_u64(metric.value)?;
196                    matching_group(metrics, key).count = count;
197                }
198                _ => {
199                    return Ok(Some(Metric {
200                        name: metric.name,
201                        timestamp: key.timestamp,
202                        labels: key.labels,
203                        value,
204                    }));
205                }
206            },
207            Self::Summary(metrics) => match suffix {
208                "" => {
209                    let quantile = key
210                        .labels
211                        .remove("quantile")
212                        .ok_or(ParserError::ExpectedQuantileTag)?;
213                    let value = metric.value;
214                    let (_, quantile) = line::Metric::parse_value(&quantile)
215                        .map_err(Into::into)
216                        .context(ParseLabelValueSnafu)?;
217                    matching_group(metrics, key)
218                        .quantiles
219                        .push(SummaryQuantile { quantile, value });
220                }
221                "_sum" => {
222                    let sum = metric.value;
223                    matching_group(metrics, key).sum = sum;
224                }
225                "_count" => {
226                    let count = try_f64_to_u64(metric.value)?;
227                    matching_group(metrics, key).count = count;
228                }
229                _ => {
230                    return Ok(Some(Metric {
231                        name: metric.name,
232                        timestamp: key.timestamp,
233                        labels: key.labels,
234                        value,
235                    }));
236                }
237            },
238        }
239        Ok(None)
240    }
241}
242
243#[derive(Debug)]
244pub struct MetricGroup {
245    pub name: String,
246    pub metrics: GroupKind,
247}
248
249fn try_f64_to_u64(f: f64) -> Result<u64, ParserError> {
250    if 0.0 <= f && f <= u64::MAX as f64 {
251        Ok(f as u64)
252    } else {
253        Err(ParserError::ValueOutOfRange {
254            value: f,
255            max: u64::MAX,
256        })
257    }
258}
259
260impl MetricGroup {
261    fn new(name: String, kind: MetricKind) -> Self {
262        let metrics = GroupKind::new(kind);
263        MetricGroup { name, metrics }
264    }
265
266    // For cases where a metric group was not defined with `# TYPE ...`.
267    fn new_untyped(metric: Metric) -> Self {
268        let Metric {
269            name,
270            labels,
271            value,
272            timestamp,
273        } = metric;
274        let key = GroupKey { timestamp, labels };
275        MetricGroup {
276            name,
277            metrics: GroupKind::new_untyped(key, value),
278        }
279    }
280
281    /// `Err(_)` if there are irrecoverable error.
282    /// `Ok(Some(metric))` if this metric belongs to another group.
283    /// `Ok(None)` pushed successfully.
284    fn try_push(&mut self, metric: Metric) -> Result<Option<Metric>, ParserError> {
285        if !metric.name.starts_with(&self.name) {
286            return Ok(Some(metric));
287        }
288        self.metrics.try_push(self.name.len(), metric)
289    }
290}
291
292fn matching_group<T: Default>(values: &mut MetricMap<T>, group: GroupKey) -> &mut T {
293    values.entry(group).or_default()
294}
295
296/// Parse the given text input, and group the result into higher-level
297/// metric types based on the declared types in the text.
298pub fn parse_text(input: &str) -> Result<Vec<MetricGroup>, ParserError> {
299    let mut groups = Vec::new();
300
301    for line in input.lines() {
302        let line = Line::parse(line).with_context(|_| WithLineSnafu {
303            line: line.to_owned(),
304        })?;
305        if let Some(line) = line {
306            match line {
307                Line::Header(header) => {
308                    groups.push(MetricGroup::new(header.metric_name, header.kind));
309                }
310                Line::Metric(metric) => {
311                    let metric = match groups.last_mut() {
312                        Some(group) => group.try_push(metric)?,
313                        None => Some(metric),
314                    };
315                    if let Some(metric) = metric {
316                        groups.push(MetricGroup::new_untyped(metric));
317                    }
318                }
319            }
320        }
321    }
322
323    Ok(groups)
324}
325
326#[derive(Default)]
327struct MetricGroupSet(IndexMap<String, GroupKind>);
328
329impl MetricGroupSet {
330    fn get_group<'a>(&'a mut self, name: &str) -> (usize, &'a String, &'a mut GroupKind) {
331        let len = name.len();
332        let name = if self.0.contains_key(name) {
333            name
334        } else if name.ends_with("_bucket") && self.0.contains_key(&name[..len - 7]) {
335            &name[..len - 7]
336        } else if name.ends_with("_sum") && self.0.contains_key(&name[..len - 4]) {
337            &name[..len - 4]
338        } else if name.ends_with("_count") && self.0.contains_key(&name[..len - 6]) {
339            &name[..len - 6]
340        } else {
341            self.0
342                .insert(name.into(), GroupKind::new(MetricKind::Untyped));
343            name
344        };
345        self.0.get_full_mut(name).unwrap()
346    }
347
348    fn insert_metadata(
349        &mut self,
350        name: String,
351        kind: MetricKind,
352        metadata_conflict_strategy: MetadataConflictStrategy,
353    ) -> Result<(), ParserError> {
354        match self.0.get(&name) {
355            Some(group) if !group.matches_kind(kind) => {
356                if matches!(metadata_conflict_strategy, MetadataConflictStrategy::Reject) {
357                    Err(ParserError::MultipleMetricKinds { name })
358                } else {
359                    Ok(())
360                }
361            }
362            Some(_) => Ok(()), // metadata already exists and is the right type
363            None => {
364                self.0.insert(name, GroupKind::new(kind));
365                Ok(())
366            }
367        }
368    }
369
370    fn insert_sample(
371        &mut self,
372        name: &str,
373        labels: &BTreeMap<String, String>,
374        sample: proto::Sample,
375    ) -> Result<(), ParserError> {
376        let (_, basename, group) = self.get_group(name);
377        if let Some(metric) = group.try_push(
378            basename.len(),
379            Metric {
380                name: name.into(),
381                labels: labels.clone(),
382                value: sample.value,
383                timestamp: Some(sample.timestamp),
384            },
385        )? {
386            let key = GroupKey {
387                timestamp: metric.timestamp,
388                labels: metric.labels,
389            };
390            let group = GroupKind::new_untyped(key, metric.value);
391            self.0.insert(metric.name, group);
392        }
393        Ok(())
394    }
395
396    fn finish(self) -> Vec<MetricGroup> {
397        self.0
398            .into_iter()
399            .map(|(name, metrics)| MetricGroup { name, metrics })
400            .collect()
401    }
402}
403
404/// Parse the given remote_write request, grouping the metrics into
405/// higher-level metric types based on the metadata.
406pub fn parse_request(
407    request: proto::WriteRequest,
408    metadata_conflict_strategy: MetadataConflictStrategy,
409) -> Result<Vec<MetricGroup>, ParserError> {
410    let mut groups = MetricGroupSet::default();
411
412    for metadata in request.metadata {
413        let name = metadata.metric_family_name;
414        let kind = proto::MetricType::try_from(metadata.r#type)
415            .unwrap_or(proto::MetricType::Unknown)
416            .into();
417        groups.insert_metadata(name, kind, metadata_conflict_strategy)?;
418    }
419
420    for timeseries in request.timeseries {
421        let mut labels: BTreeMap<String, String> = timeseries
422            .labels
423            .into_iter()
424            .map(|label| (label.name, label.value))
425            .collect();
426        let name = match labels.remove(METRIC_NAME_LABEL) {
427            Some(name) => name,
428            None => return Err(ParserError::RequestNoNameLabel),
429        };
430
431        for sample in timeseries.samples {
432            groups.insert_sample(&name, &labels, sample)?;
433        }
434    }
435
436    Ok(groups.finish())
437}
438
439impl From<proto::MetricType> for MetricKind {
440    fn from(kind: proto::MetricType) -> Self {
441        use proto::MetricType::*;
442        match kind {
443            Counter => MetricKind::Counter,
444            Gauge => MetricKind::Gauge,
445            Histogram => MetricKind::Histogram,
446            Gaugehistogram => MetricKind::Histogram,
447            Summary => MetricKind::Summary,
448            _ => MetricKind::Untyped,
449        }
450    }
451}
452
453#[cfg(test)]
454mod test {
455    use super::*;
456
457    macro_rules! match_group {
458        ($group:expr, $name:literal, $kind:ident => $inner:expr) => {{
459            assert_eq!($group.name, $name);
460            let inner = $inner;
461            match &$group.metrics {
462                GroupKind::$kind(metrics) => inner(metrics),
463                _ => panic!("Invalid metric group type"),
464            }
465        }};
466    }
467
468    macro_rules! labels {
469        () => { BTreeMap::new() };
470        ( $( $name:ident => $value:literal ),* ) => {{
471            let mut result = BTreeMap::<String, String>::new();
472            $( result.insert(stringify!($name).into(), $value.to_string()); )*
473            result
474        }};
475    }
476
477    macro_rules! simple_metric {
478        ( $timestamp:expr, $labels:expr, $value:expr ) => {
479            (
480                &GroupKey {
481                    timestamp: $timestamp,
482                    labels: $labels,
483                },
484                &SimpleMetric { value: $value },
485            )
486        };
487    }
488
489    #[test]
490    fn test_parse_text() {
491        let input = r#"
492            # HELP http_requests_total The total number of HTTP requests.
493            # TYPE http_requests_total counter
494            http_requests_total{method="post",code="200"} 1027 1395066363000
495            http_requests_total{method="post",code="400"}    3 1395066363000
496
497            # Escaping in label values:
498            msdos_file_access_time_seconds{path="C:\\DIR\\FILE.TXT",error="Cannot find file:\n\"FILE.TXT\""} 1.458255915e9
499
500            # Minimalistic line:
501            metric_without_timestamp_and_labels 12.47
502
503            # A weird metric from before the epoch:
504            something_weird{problem="division by zero"} +Inf -3982045
505
506            # A histogram, which has a pretty complex representation in the text format:
507            # HELP http_request_duration_seconds A histogram of the request duration.
508            # TYPE http_request_duration_seconds histogram
509            http_request_duration_seconds_bucket{le="0.05"} 24054
510            http_request_duration_seconds_bucket{le="0.1"} 33444
511            http_request_duration_seconds_bucket{le="0.2"} 100392
512            http_request_duration_seconds_bucket{le="0.5"} 129389
513            http_request_duration_seconds_bucket{le="1"} 133988
514            http_request_duration_seconds_bucket{le="+Inf"} 144320
515            http_request_duration_seconds_sum 53423
516            http_request_duration_seconds_count 144320
517
518            # A histogram with a very large count.
519            # HELP go_gc_heap_allocs_by_size_bytes A histogram of something gc
520            # TYPE go_gc_heap_allocs_by_size_bytes histogram
521            go_gc_heap_allocs_by_size_bytes_bucket{le="24.999999999999996"} 1.8939392877e+10
522            go_gc_heap_allocs_by_size_bytes_sum 5
523            go_gc_heap_allocs_by_size_bytes_count 10
524
525            # Finally a summary, which has a complex representation, too:
526            # HELP rpc_duration_seconds A summary of the RPC duration in seconds.
527            # TYPE rpc_duration_seconds summary
528            rpc_duration_seconds{quantile="0.01"} 3102
529            rpc_duration_seconds{quantile="0.05"} 3272
530            rpc_duration_seconds{quantile="0.5"} 4773
531            rpc_duration_seconds{quantile="0.9"} 9001
532            rpc_duration_seconds{quantile="0.99"} 76656
533            rpc_duration_seconds_sum 1.7560473e+07
534            rpc_duration_seconds_count 4.588206224e+09
535            "#;
536        let output = parse_text(input).unwrap();
537        assert_eq!(output.len(), 7);
538        match_group!(output[0], "http_requests_total", Counter => |metrics: &MetricMap<SimpleMetric>| {
539            assert_eq!(metrics.len(), 2);
540            assert_eq!(
541                metrics.get_index(0).unwrap(),
542                simple_metric!(Some(1395066363000), labels!(method => "post", code => 200), 1027.0)
543            );
544            assert_eq!(
545                metrics.get_index(1).unwrap(),
546                simple_metric!(Some(1395066363000), labels!(method => "post", code => 400), 3.0)
547            );
548        });
549        match_group!(output[1], "msdos_file_access_time_seconds", Untyped => |metrics: &MetricMap<SimpleMetric>| {
550            assert_eq!(metrics.len(), 1);
551            assert_eq!(metrics.get_index(0).unwrap(), simple_metric!(
552                None,
553                labels!(path => "C:\\DIR\\FILE.TXT", error => "Cannot find file:\n\"FILE.TXT\""),
554                1.458255915e9
555            ));
556        });
557        match_group!(output[2], "metric_without_timestamp_and_labels", Untyped => |metrics: &MetricMap<SimpleMetric>| {
558            assert_eq!(metrics.len(), 1);
559            assert_eq!(metrics.get_index(0).unwrap(), simple_metric!(None, labels!(), 12.47));
560        });
561        match_group!(output[3], "something_weird", Untyped => |metrics: &MetricMap<SimpleMetric>| {
562            assert_eq!(metrics.len(), 1);
563            assert_eq!(
564                metrics.get_index(0).unwrap(),
565                simple_metric!(Some(-3982045), labels!(problem => "division by zero"), f64::INFINITY)
566            );
567        });
568        match_group!(output[4], "http_request_duration_seconds", Histogram => |metrics: &MetricMap<HistogramMetric>| {
569            assert_eq!(metrics.len(), 1);
570            assert_eq!(metrics.get_index(0).unwrap(), (
571                &GroupKey {
572                    timestamp: None,
573                    labels: labels!(),
574                },
575                &HistogramMetric {
576                    buckets: vec![
577                        HistogramBucket { bucket: 0.05, count: 24054 },
578                        HistogramBucket { bucket: 0.1, count: 33444 },
579                        HistogramBucket { bucket: 0.2, count: 100392 },
580                        HistogramBucket { bucket: 0.5, count: 129389 },
581                        HistogramBucket { bucket: 1.0, count: 133988 },
582                        HistogramBucket { bucket: f64::INFINITY, count: 144320 },
583                    ],
584                    count: 144320,
585                    sum: 53423.0,
586                },
587            ));
588        });
589        match_group!(output[5], "go_gc_heap_allocs_by_size_bytes", Histogram => |metrics: &MetricMap<HistogramMetric>| {
590            assert_eq!(metrics.len(), 1);
591            assert_eq!(metrics.get_index(0).unwrap(), (
592                &GroupKey {
593                    timestamp: None,
594                    labels: labels!(),
595                },
596                &HistogramMetric {
597                    buckets: vec![
598                        HistogramBucket { bucket: 24.999999999999996, count: 18_939_392_877},
599                    ],
600                    count: 10,
601                    sum: 5.0,
602                },
603            ));
604        });
605        match_group!(output[6], "rpc_duration_seconds", Summary => |metrics: &MetricMap<SummaryMetric>| {
606            assert_eq!(metrics.len(), 1);
607            assert_eq!(metrics.get_index(0).unwrap(), (
608                &GroupKey {
609                    timestamp: None,
610                    labels: labels!(),
611                },
612                &SummaryMetric {
613                    quantiles: vec![
614                        SummaryQuantile { quantile: 0.01, value: 3102.0 },
615                        SummaryQuantile { quantile: 0.05, value: 3272.0 },
616                        SummaryQuantile { quantile: 0.5, value: 4773.0 },
617                        SummaryQuantile { quantile: 0.9, value: 9001.0 },
618                        SummaryQuantile { quantile: 0.99, value: 76656.0 },
619                    ],
620                    count: 4588206224,
621                    sum: 1.7560473e+07,
622                },
623            ));
624        });
625    }
626
627    #[test]
628    fn test_f64_to_u64() {
629        let value = -1.0;
630        let error = try_f64_to_u64(value).unwrap_err();
631        assert_eq!(
632            error,
633            ParserError::ValueOutOfRange {
634                value,
635                max: u64::MAX
636            }
637        );
638
639        let value = f64::NAN;
640        let error = try_f64_to_u64(value).unwrap_err();
641        assert!(
642            matches!(error, ParserError::ValueOutOfRange { value, max: u64::MAX} if value.is_nan())
643        );
644
645        let value = f64::INFINITY;
646        let error = try_f64_to_u64(value).unwrap_err();
647        assert_eq!(
648            error,
649            ParserError::ValueOutOfRange {
650                value,
651                max: u64::MAX
652            }
653        );
654
655        let value = f64::NEG_INFINITY;
656        let error = try_f64_to_u64(value).unwrap_err();
657        assert_eq!(
658            error,
659            ParserError::ValueOutOfRange {
660                value,
661                max: u64::MAX
662            }
663        );
664
665        assert_eq!(try_f64_to_u64(0.0).unwrap(), 0);
666        assert_eq!(try_f64_to_u64(u64::MAX as f64).unwrap(), u64::MAX);
667
668        // The following tests fails because we lose accuracy converting from u64 to f64
669        // assert_eq!(try_f64_to_u64((u64::MAX - 1) as f64).unwrap(), u64::MAX - 1);
670    }
671
672    #[test]
673    fn test_errors() {
674        let input = r#"name{registry="default" content_type="html"} 1890"#;
675        let error = parse_text(input).unwrap_err();
676        assert!(matches!(
677            error,
678            ParserError::WithLine {
679                kind: ErrorKind::ExpectedChar { expected: ',', .. },
680                ..
681            }
682        ));
683
684        let input = r##"# TYPE a counte"##;
685        let error = parse_text(input).unwrap_err();
686        assert!(matches!(
687            error,
688            ParserError::WithLine {
689                kind: ErrorKind::InvalidMetricKind { .. },
690                ..
691            }
692        ));
693
694        let input = r##"# TYPEabcd asdf"##;
695        let error = parse_text(input).unwrap_err();
696        assert!(matches!(
697            error,
698            ParserError::WithLine {
699                kind: ErrorKind::ExpectedSpace { .. },
700                ..
701            }
702        ));
703
704        let input = r#"name{registry="} 1890"#;
705        let error = parse_text(input).unwrap_err();
706        assert!(matches!(
707            error,
708            ParserError::WithLine {
709                kind: ErrorKind::ExpectedChar { expected: '"', .. },
710                ..
711            }
712        ));
713
714        let input = r##"name{registry=} 1890"##;
715        let error = parse_text(input).unwrap_err();
716        assert!(matches!(
717            error,
718            ParserError::WithLine {
719                kind: ErrorKind::ExpectedChar { expected: '"', .. },
720                ..
721            }
722        ));
723
724        let input = r##"name abcd"##;
725        let error = parse_text(input).unwrap_err();
726        assert!(matches!(
727            error,
728            ParserError::WithLine {
729                kind: ErrorKind::ParseFloatError { .. },
730                ..
731            }
732        ));
733    }
734
735    macro_rules! write_request {
736        (
737            [ $( $name:literal = $type:ident ),* ],
738            [
739                $( [ $( $label:ident => $value:literal ),* ] => [ $( $sample:literal @ $timestamp:literal ),* ] ),*
740            ]
741        ) => {
742            proto::WriteRequest {
743                metadata: vec![
744                    $( proto::MetricMetadata {
745                        r#type: proto::MetricType::$type as i32,
746                        metric_family_name: $name.into(),
747                        help: String::default(),
748                        unit: String::default(),
749                    }, )*
750                ],
751                timeseries: vec![ $( proto::TimeSeries {
752                    labels: vec![ $( proto::Label {
753                        name: stringify!($label).into(),
754                        value: $value.to_string(),
755                    }, )* ],
756                    samples: vec![
757                        $( proto::Sample { value: $sample as f64, timestamp: $timestamp as i64 }, )*
758                    ],
759                }, )* ],
760            }
761        };
762    }
763
764    #[test]
765    fn parse_request_empty() {
766        let parsed =
767            parse_request(write_request!([], []), MetadataConflictStrategy::Ignore).unwrap();
768        assert!(parsed.is_empty());
769    }
770
771    #[test]
772    fn parse_request_only_metadata() {
773        let parsed = parse_request(
774            write_request!(["one" = Counter, "two" = Gauge], []),
775            MetadataConflictStrategy::Ignore,
776        )
777        .unwrap();
778        assert_eq!(parsed.len(), 2);
779        match_group!(parsed[0], "one", Counter => |metrics: &MetricMap<SimpleMetric>| {
780            assert!(metrics.is_empty());
781        });
782        match_group!(parsed[1], "two", Gauge => |metrics: &MetricMap<SimpleMetric>| {
783            assert!(metrics.is_empty());
784        });
785    }
786
787    #[test]
788    fn parse_request_untyped() {
789        let parsed = parse_request(
790            write_request!([], [ [__name__ => "one", big => "small"] => [123 @ 1395066367500] ]),
791            MetadataConflictStrategy::Ignore,
792        )
793        .unwrap();
794
795        assert_eq!(parsed.len(), 1);
796        match_group!(parsed[0], "one", Untyped => |metrics: &MetricMap<SimpleMetric>| {
797            assert_eq!(metrics.len(), 1);
798            assert_eq!(
799                metrics.get_index(0).unwrap(),
800                simple_metric!(Some(1395066367500), labels!(big => "small"), 123.0)
801            );
802        });
803    }
804
805    #[test]
806    fn parse_request_gauge() {
807        let parsed = parse_request(
808            write_request!(
809                ["one" = Gauge],
810                [
811                    [__name__ => "one"] => [ 12 @ 1395066367600, 14 @ 1395066367800 ],
812                    [__name__ => "two"] => [ 13 @ 1395066367700 ]
813                ]
814            ),
815            MetadataConflictStrategy::Ignore,
816        )
817        .unwrap();
818
819        assert_eq!(parsed.len(), 2);
820        match_group!(parsed[0], "one", Gauge => |metrics: &MetricMap<SimpleMetric>| {
821            assert_eq!(metrics.len(), 2);
822            assert_eq!(
823                metrics.get_index(0).unwrap(),
824                simple_metric!(Some(1395066367600), labels!(), 12.0)
825            );
826            assert_eq!(
827                metrics.get_index(1).unwrap(),
828                simple_metric!(Some(1395066367800), labels!(), 14.0)
829            );
830        });
831        match_group!(parsed[1], "two", Untyped => |metrics: &MetricMap<SimpleMetric>| {
832            assert_eq!(metrics.len(), 1);
833            assert_eq!(
834                metrics.get_index(0).unwrap(),
835                simple_metric!(Some(1395066367700), labels!(), 13.0)
836            );
837        });
838    }
839
840    #[test]
841    fn parse_request_histogram() {
842        let parsed = parse_request(
843            write_request!(
844                ["one" = Histogram],
845                [
846                    [__name__ => "one_bucket", le => "1"] => [ 15 @ 1395066367700 ],
847                    [__name__ => "one_bucket", le => "+Inf"] => [ 19 @ 1395066367700 ],
848                    [__name__ => "one_count"] => [ 19 @ 1395066367700 ],
849                    [__name__ => "one_sum"] => [ 12 @ 1395066367700 ],
850                    [__name__ => "one_total"] => [24 @ 1395066367700]
851                ]
852            ),
853            MetadataConflictStrategy::Ignore,
854        )
855        .unwrap();
856
857        assert_eq!(parsed.len(), 2);
858        match_group!(parsed[0], "one", Histogram => |metrics: &MetricMap<HistogramMetric>| {
859            assert_eq!(metrics.len(), 1);
860            assert_eq!(
861                metrics.get_index(0).unwrap(), (
862                    &GroupKey {
863                        timestamp: Some(1395066367700),
864                        labels: labels!(),
865                    },
866                    &HistogramMetric {
867                        buckets: vec![
868                            HistogramBucket { bucket: 1.0, count: 15 },
869                            HistogramBucket { bucket: f64::INFINITY, count: 19 },
870                        ],
871                        count: 19,
872                        sum: 12.0,
873                    })
874            );
875        });
876        match_group!(parsed[1], "one_total", Untyped => |metrics: &MetricMap<SimpleMetric>| {
877            assert_eq!(metrics.len(), 1);
878            assert_eq!(metrics.get_index(0).unwrap(), simple_metric!(Some(1395066367700), labels!(), 24.0));
879        });
880    }
881
882    #[test]
883    fn parse_request_summary() {
884        let parsed = parse_request(
885            write_request!(
886                ["one" = Summary],
887                [
888                    [__name__ => "one", quantile => "0.5"] => [ 15 @ 1395066367700 ],
889                    [__name__ => "one", quantile => "0.9"] => [ 19 @ 1395066367700 ],
890                    [__name__ => "one_count"] => [ 21 @ 1395066367700 ],
891                    [__name__ => "one_sum"] => [ 12 @ 1395066367700 ],
892                    [__name__ => "one_total"] => [24 @ 1395066367700]
893                ]
894            ),
895            MetadataConflictStrategy::Ignore,
896        )
897        .unwrap();
898
899        assert_eq!(parsed.len(), 2);
900        match_group!(parsed[0], "one", Summary => |metrics: &MetricMap<SummaryMetric>| {
901            assert_eq!(metrics.len(), 1);
902            assert_eq!(
903                metrics.get_index(0).unwrap(), (
904                    &GroupKey {
905                        timestamp: Some(1395066367700),
906                        labels: labels!(),
907                    },
908                    &SummaryMetric {
909                        quantiles: vec![
910                            SummaryQuantile { quantile: 0.5, value: 15.0 },
911                            SummaryQuantile { quantile: 0.9, value: 19.0 },
912                        ],
913                        count: 21,
914                        sum: 12.0,
915                    })
916            );
917        });
918        match_group!(parsed[1], "one_total", Untyped => |metrics: &MetricMap<SimpleMetric>| {
919            assert_eq!(metrics.len(), 1);
920            assert_eq!(metrics.get_index(0).unwrap(), simple_metric!(Some(1395066367700), labels!(), 24.0));
921        });
922    }
923
924    #[test]
925    fn parse_request_conflicting_metadata() {
926        let request = proto::WriteRequest {
927            metadata: vec![
928                proto::MetricMetadata {
929                    r#type: proto::MetricType::Gauge as i32,
930                    metric_family_name: "go_memstats_alloc_bytes".into(),
931                    help: "Number of bytes allocated and still in use.".into(),
932                    unit: String::default(),
933                },
934                proto::MetricMetadata {
935                    r#type: proto::MetricType::Counter as i32,
936                    metric_family_name: "go_memstats_alloc_bytes".into(),
937                    help: "Total number of bytes allocated, even if freed.".into(),
938                    unit: String::default(),
939                },
940            ],
941            timeseries: vec![proto::TimeSeries {
942                labels: vec![proto::Label {
943                    name: "__name__".into(),
944                    value: "go_memstats_alloc_bytes".into(),
945                }],
946                samples: vec![proto::Sample {
947                    value: 12345.0,
948                    timestamp: 1395066367500,
949                }],
950            }],
951        };
952
953        // Should succeed and use the first metadata entry (Gauge)
954        let parsed = parse_request(request.clone(), MetadataConflictStrategy::Ignore).unwrap();
955        assert_eq!(parsed.len(), 1);
956        match_group!(parsed[0], "go_memstats_alloc_bytes", Gauge => |metrics: &MetricMap<SimpleMetric>| {
957            assert_eq!(metrics.len(), 1);
958            assert_eq!(
959                metrics.get_index(0).unwrap(),
960                simple_metric!(Some(1395066367500), labels!(), 12345.0)
961            );
962        });
963
964        // Should fail when conflicts are rejected
965        let err = parse_request(request, MetadataConflictStrategy::Reject).unwrap_err();
966        assert!(matches!(
967            err,
968            ParserError::MultipleMetricKinds { name } if name == "go_memstats_alloc_bytes"
969        ));
970    }
971}