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)] pub 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(Clone, Copy, Debug, Default, PartialEq, Eq)]
69pub enum MetadataConflictStrategy {
70 Ignore,
72 #[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 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 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 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
296pub 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(()), 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
404pub 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 }
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 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 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}