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