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(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 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 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 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
288pub 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(()), 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
387pub 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 }
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}