1use crate::metrics::counter::{self, Counter};
28use crate::metrics::exemplar::{CounterWithExemplar, Exemplar, HistogramWithExemplars};
29use crate::metrics::family::{Family, MetricConstructor};
30use crate::metrics::gauge::{self, Gauge};
31use crate::metrics::histogram::Histogram;
32use crate::metrics::info::Info;
33use crate::metrics::{MetricType, TypedMetric};
34use crate::registry::{Registry, Unit};
35
36use std::borrow::Cow;
37use std::collections::HashMap;
38use std::io::Write;
39use std::ops::Deref;
40
41pub use open_metrics_client_derive_text_encode::*;
42
43pub fn encode<W, M>(writer: &mut W, registry: &Registry<M>) -> Result<(), std::io::Error>
44where
45 W: Write,
46 M: EncodeMetric,
47{
48 for (desc, metric) in registry.iter() {
49 writer.write_all(b"# HELP ")?;
50 writer.write_all(desc.name().as_bytes())?;
51 if let Some(unit) = desc.unit() {
52 writer.write_all(b"_")?;
53 unit.encode(writer)?;
54 }
55 writer.write_all(b" ")?;
56 writer.write_all(desc.help().as_bytes())?;
57 writer.write_all(b"\n")?;
58
59 writer.write_all(b"# TYPE ")?;
60 writer.write_all(desc.name().as_bytes())?;
61 if let Some(unit) = desc.unit() {
62 writer.write_all(b"_")?;
63 unit.encode(writer)?;
64 }
65 writer.write_all(b" ")?;
66 metric.metric_type().encode(writer)?;
67 writer.write_all(b"\n")?;
68
69 if let Some(unit) = desc.unit() {
70 writer.write_all(b"# UNIT ")?;
71 writer.write_all(desc.name().as_bytes())?;
72 writer.write_all(b"_")?;
73 unit.encode(writer)?;
74 writer.write_all(b" ")?;
75 unit.encode(writer)?;
76 writer.write_all(b"\n")?;
77 }
78
79 let encoder = Encoder {
80 writer,
81 name: desc.name(),
82 unit: desc.unit(),
83 const_labels: desc.labels(),
84 labels: None,
85 };
86
87 metric.encode(encoder)?;
88 }
89
90 writer.write_all(b"# EOF\n")?;
91
92 Ok(())
93}
94
95pub trait Encode {
96 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error>;
97}
98
99impl Encode for f64 {
100 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
101 writer.write_all(dtoa::Buffer::new().format(*self).as_bytes())?;
102 Ok(())
103 }
104}
105
106impl Encode for u64 {
107 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
108 writer.write_all(itoa::Buffer::new().format(*self).as_bytes())?;
109 Ok(())
110 }
111}
112
113impl Encode for u32 {
114 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
115 writer.write_all(itoa::Buffer::new().format(*self).as_bytes())?;
116 Ok(())
117 }
118}
119
120impl<T: Encode> Encode for &[T] {
121 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
122 if self.is_empty() {
123 return Ok(());
124 }
125
126 let mut iter = self.iter().peekable();
127 while let Some(x) = iter.next() {
128 x.encode(writer)?;
129
130 if iter.peek().is_some() {
131 writer.write_all(b",")?;
132 }
133 }
134
135 Ok(())
136 }
137}
138
139impl<T: Encode> Encode for Vec<T> {
140 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
141 self.as_slice().encode(writer)
142 }
143}
144
145impl<K: Encode, V: Encode> Encode for (K, V) {
146 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
147 let (key, value) = self;
148
149 key.encode(writer)?;
150 writer.write_all(b"=\"")?;
151
152 value.encode(writer)?;
153 writer.write_all(b"\"")?;
154
155 Ok(())
156 }
157}
158
159impl Encode for &str {
160 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
161 writer.write_all(self.as_bytes())?;
163 Ok(())
164 }
165}
166
167impl Encode for String {
168 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
169 self.as_str().encode(writer)
170 }
171}
172
173impl<'a> Encode for Cow<'a, str> {
174 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
175 self.as_ref().encode(writer)
176 }
177}
178
179impl Encode for MetricType {
180 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
181 let t = match self {
182 MetricType::Counter => "counter",
183 MetricType::Gauge => "gauge",
184 MetricType::Histogram => "histogram",
185 MetricType::Info => "info",
186 MetricType::Unknown => "unknown",
187 };
188
189 writer.write_all(t.as_bytes())?;
190 Ok(())
191 }
192}
193
194impl Encode for Unit {
195 fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
196 let u = match self {
197 Unit::Amperes => "amperes",
198 Unit::Bytes => "bytes",
199 Unit::Celsius => "celsius",
200 Unit::Grams => "grams",
201 Unit::Joules => "joules",
202 Unit::Meters => "meters",
203 Unit::Ratios => "ratios",
204 Unit::Seconds => "seconds",
205 Unit::Volts => "volts",
206 Unit::Other(other) => other.as_str(),
207 };
208
209 writer.write_all(u.as_bytes())?;
210 Ok(())
211 }
212}
213
214impl Encode for () {
215 fn encode(&self, _writer: &mut dyn Write) -> Result<(), std::io::Error> {
216 Ok(())
217 }
218}
219
220pub struct Encoder<'a, 'b> {
227 writer: &'a mut dyn Write,
228 name: &'a str,
229 unit: &'a Option<Unit>,
230 const_labels: &'a [(Cow<'static, str>, Cow<'static, str>)],
231 labels: Option<&'b dyn Encode>,
232}
233
234impl<'a, 'b> Encoder<'a, 'b> {
235 pub fn encode_suffix(&mut self, suffix: &'static str) -> Result<BucketEncoder, std::io::Error> {
236 self.write_name_and_unit()?;
237
238 self.writer.write_all(b"_")?;
239 self.writer.write_all(suffix.as_bytes()).map(|_| ())?;
240
241 self.encode_labels()
242 }
243
244 pub fn no_suffix(&mut self) -> Result<BucketEncoder, std::io::Error> {
245 self.write_name_and_unit()?;
246
247 self.encode_labels()
248 }
249
250 fn write_name_and_unit(&mut self) -> Result<(), std::io::Error> {
251 self.writer.write_all(self.name.as_bytes())?;
252 if let Some(unit) = self.unit {
253 self.writer.write_all(b"_")?;
254 unit.encode(self.writer)?;
255 }
256
257 Ok(())
258 }
259
260 fn encode_labels(&mut self) -> Result<BucketEncoder, std::io::Error> {
263 let mut opened_curly_brackets = false;
264
265 if !self.const_labels.is_empty() {
266 self.writer.write_all(b"{")?;
267 opened_curly_brackets = true;
268
269 self.const_labels.encode(self.writer)?;
270 }
271
272 if let Some(labels) = &self.labels {
273 if opened_curly_brackets {
274 self.writer.write_all(b",")?;
275 } else {
276 opened_curly_brackets = true;
277 self.writer.write_all(b"{")?;
278 }
279 labels.encode(self.writer)?;
280 }
281
282 Ok(BucketEncoder {
283 opened_curly_brackets,
284 writer: self.writer,
285 })
286 }
287
288 pub fn with_label_set<'c, 'd>(&'c mut self, label_set: &'d dyn Encode) -> Encoder<'c, 'd> {
289 debug_assert!(self.labels.is_none());
290
291 Encoder {
292 writer: self.writer,
293 name: self.name,
294 unit: self.unit,
295 const_labels: self.const_labels,
296 labels: Some(label_set),
297 }
298 }
299}
300
301#[must_use]
302pub struct BucketEncoder<'a> {
303 writer: &'a mut dyn Write,
304 opened_curly_brackets: bool,
305}
306
307impl<'a> BucketEncoder<'a> {
308 fn encode_bucket(&mut self, upper_bound: f64) -> Result<ValueEncoder, std::io::Error> {
309 if self.opened_curly_brackets {
310 self.writer.write_all(b",")?;
311 } else {
312 self.writer.write_all(b"{")?;
313 }
314
315 self.writer.write_all(b"le=\"")?;
316 if upper_bound == f64::MAX {
317 self.writer.write_all(b"+Inf")?;
318 } else {
319 upper_bound.encode(self.writer)?;
320 }
321 self.writer.write_all(b"\"}")?;
322
323 Ok(ValueEncoder {
324 writer: self.writer,
325 })
326 }
327
328 fn no_bucket(&mut self) -> Result<ValueEncoder, std::io::Error> {
329 if self.opened_curly_brackets {
330 self.writer.write_all(b"}")?;
331 }
332 Ok(ValueEncoder {
333 writer: self.writer,
334 })
335 }
336}
337
338#[must_use]
339pub struct ValueEncoder<'a> {
340 writer: &'a mut dyn Write,
341}
342
343impl<'a> ValueEncoder<'a> {
344 fn encode_value<V: Encode>(&mut self, v: V) -> Result<ExemplarEncoder, std::io::Error> {
345 self.writer.write_all(b" ")?;
346 v.encode(self.writer)?;
347 Ok(ExemplarEncoder {
348 writer: self.writer,
349 })
350 }
351}
352
353#[must_use]
354pub struct ExemplarEncoder<'a> {
355 writer: &'a mut dyn Write,
356}
357
358impl<'a> ExemplarEncoder<'a> {
359 fn encode_exemplar<S: Encode, V: Encode>(
360 &mut self,
361 exemplar: &Exemplar<S, V>,
362 ) -> Result<(), std::io::Error> {
363 self.writer.write_all(b" # {")?;
364 exemplar.label_set.encode(self.writer)?;
365 self.writer.write_all(b"} ")?;
366 exemplar.value.encode(self.writer)?;
367 self.writer.write_all(b"\n")?;
368 Ok(())
369 }
370
371 fn no_exemplar(&mut self) -> Result<(), std::io::Error> {
372 self.writer.write_all(b"\n")?;
373 Ok(())
374 }
375}
376
377pub trait EncodeMetric {
378 fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error>;
379
380 fn metric_type(&self) -> MetricType;
383}
384
385impl EncodeMetric for Box<dyn EncodeMetric> {
386 fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> {
387 self.deref().encode(encoder)
388 }
389
390 fn metric_type(&self) -> MetricType {
391 self.deref().metric_type()
392 }
393}
394
395pub trait SendEncodeMetric: EncodeMetric + Send {}
396
397impl<T: EncodeMetric + Send> SendEncodeMetric for T {}
398
399impl EncodeMetric for Box<dyn SendEncodeMetric> {
400 fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> {
401 self.deref().encode(encoder)
402 }
403
404 fn metric_type(&self) -> MetricType {
405 self.deref().metric_type()
406 }
407}
408
409impl<N, A> EncodeMetric for Counter<N, A>
413where
414 N: Encode,
415 A: counter::Atomic<N>,
416{
417 fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> {
418 encode_counter_with_maybe_exemplar::<(), _>(self.get(), None, encoder)
420 }
421
422 fn metric_type(&self) -> MetricType {
423 Self::TYPE
424 }
425}
426
427impl<S, N, A> EncodeMetric for CounterWithExemplar<S, N, A>
429where
430 S: Encode,
431 N: Encode + Clone,
432 A: counter::Atomic<N>,
433{
434 fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> {
435 let (value, exemplar) = self.get();
436 encode_counter_with_maybe_exemplar(value, exemplar.as_ref().as_ref(), encoder)
437 }
438
439 fn metric_type(&self) -> MetricType {
440 Counter::<N, A>::TYPE
441 }
442}
443
444fn encode_counter_with_maybe_exemplar<S, N>(
445 value: N,
446 exemplar: Option<&Exemplar<S, N>>,
447 mut encoder: Encoder,
448) -> Result<(), std::io::Error>
449where
450 S: Encode,
451 N: Encode,
452{
453 let mut bucket_encoder = encoder.encode_suffix("total")?;
454 let mut value_encoder = bucket_encoder.no_bucket()?;
455 let mut exemplar_encoder = value_encoder.encode_value(value)?;
456
457 match exemplar {
458 Some(exemplar) => exemplar_encoder.encode_exemplar(exemplar)?,
459 None => exemplar_encoder.no_exemplar()?,
460 }
461
462 Ok(())
463}
464
465impl<N, A> EncodeMetric for Gauge<N, A>
469where
470 N: Encode,
471 A: gauge::Atomic<N>,
472{
473 fn encode(&self, mut encoder: Encoder) -> Result<(), std::io::Error> {
474 encoder
475 .no_suffix()?
476 .no_bucket()?
477 .encode_value(self.get())?
478 .no_exemplar()?;
479
480 Ok(())
481 }
482 fn metric_type(&self) -> MetricType {
483 Self::TYPE
484 }
485}
486
487impl<S, M, C> EncodeMetric for Family<S, M, C>
491where
492 S: Clone + std::hash::Hash + Eq + Encode,
493 M: EncodeMetric + TypedMetric,
494 C: MetricConstructor<M>,
495{
496 fn encode(&self, mut encoder: Encoder) -> Result<(), std::io::Error> {
497 let guard = self.read();
498 for (label_set, m) in guard.iter() {
499 let encoder = encoder.with_label_set(label_set);
500 m.encode(encoder)?;
501 }
502 Ok(())
503 }
504
505 fn metric_type(&self) -> MetricType {
506 M::TYPE
507 }
508}
509
510impl EncodeMetric for Histogram {
514 fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> {
515 let (sum, count, buckets) = self.get();
516 encode_histogram_with_maybe_exemplars::<()>(sum, count, &buckets, None, encoder)
518 }
519
520 fn metric_type(&self) -> MetricType {
521 Self::TYPE
522 }
523}
524
525impl<S: Encode> EncodeMetric for HistogramWithExemplars<S> {
526 fn encode(&self, encoder: Encoder) -> Result<(), std::io::Error> {
527 let inner = self.inner();
528 let (sum, count, buckets) = inner.histogram.get();
529 encode_histogram_with_maybe_exemplars(sum, count, &buckets, Some(&inner.exemplars), encoder)
530 }
531
532 fn metric_type(&self) -> MetricType {
533 Histogram::TYPE
534 }
535}
536
537fn encode_histogram_with_maybe_exemplars<S: Encode>(
538 sum: f64,
539 count: u64,
540 buckets: &[(f64, u64)],
541 exemplars: Option<&HashMap<usize, Exemplar<S, f64>>>,
542 mut encoder: Encoder,
543) -> Result<(), std::io::Error> {
544 encoder
545 .encode_suffix("sum")?
546 .no_bucket()?
547 .encode_value(sum)?
548 .no_exemplar()?;
549 encoder
550 .encode_suffix("count")?
551 .no_bucket()?
552 .encode_value(count)?
553 .no_exemplar()?;
554
555 let mut cummulative = 0;
556 for (i, (upper_bound, count)) in buckets.iter().enumerate() {
557 cummulative += count;
558 let mut bucket_encoder = encoder.encode_suffix("bucket")?;
559 let mut value_encoder = bucket_encoder.encode_bucket(*upper_bound)?;
560 let mut exemplar_encoder = value_encoder.encode_value(cummulative)?;
561
562 match exemplars.map(|es| es.get(&i)).flatten() {
563 Some(exemplar) => exemplar_encoder.encode_exemplar(exemplar)?,
564 None => exemplar_encoder.no_exemplar()?,
565 }
566 }
567
568 Ok(())
569}
570
571impl<S> EncodeMetric for Info<S>
575where
576 S: Clone + std::hash::Hash + Eq + Encode,
577{
578 fn encode(&self, mut encoder: Encoder) -> Result<(), std::io::Error> {
579 encoder
580 .with_label_set(&self.0)
581 .encode_suffix("info")?
582 .no_bucket()?
583 .encode_value(1u32)?
584 .no_exemplar()?;
585
586 Ok(())
587 }
588
589 fn metric_type(&self) -> MetricType {
590 Self::TYPE
591 }
592}
593
594#[cfg(test)]
595mod tests {
596 use super::*;
597 use crate::metrics::counter::Counter;
598 use crate::metrics::gauge::Gauge;
599 use crate::metrics::histogram::exponential_buckets;
600 use pyo3::{prelude::*, types::PyModule};
601 use std::borrow::Cow;
602
603 #[test]
604 fn encode_counter() {
605 let counter: Counter = Counter::default();
606 let mut registry = Registry::default();
607 registry.register("my_counter", "My counter", counter.clone());
608
609 let mut encoded = Vec::new();
610
611 encode(&mut encoded, ®istry).unwrap();
612
613 parse_with_python_client(String::from_utf8(encoded).unwrap());
614 }
615
616 #[test]
617 fn encode_counter_with_unit() {
618 let mut registry = Registry::default();
619 let counter: Counter = Counter::default();
620 registry.register_with_unit("my_counter", "My counter", Unit::Seconds, counter.clone());
621
622 let mut encoded = Vec::new();
623 encode(&mut encoded, ®istry).unwrap();
624
625 let expected = "# HELP my_counter_seconds My counter.\n".to_owned()
626 + "# TYPE my_counter_seconds counter\n"
627 + "# UNIT my_counter_seconds seconds\n"
628 + "my_counter_seconds_total 0\n"
629 + "# EOF\n";
630 assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap());
631
632 parse_with_python_client(String::from_utf8(encoded).unwrap());
633 }
634
635 #[test]
636 fn encode_counter_with_exemplar() {
637 let mut registry = Registry::default();
638
639 let counter_with_exemplar: CounterWithExemplar<(String, u64)> =
640 CounterWithExemplar::default();
641 registry.register_with_unit(
642 "my_counter_with_exemplar",
643 "My counter with exemplar",
644 Unit::Seconds,
645 counter_with_exemplar.clone(),
646 );
647
648 counter_with_exemplar.inc_by(1, Some(("user_id".to_string(), 42)));
649
650 let mut encoded = Vec::new();
651 encode(&mut encoded, ®istry).unwrap();
652
653 let expected = "# HELP my_counter_with_exemplar_seconds My counter with exemplar.\n"
654 .to_owned()
655 + "# TYPE my_counter_with_exemplar_seconds counter\n"
656 + "# UNIT my_counter_with_exemplar_seconds seconds\n"
657 + "my_counter_with_exemplar_seconds_total 1 # {user_id=\"42\"} 1\n"
658 + "# EOF\n";
659 assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap());
660
661 parse_with_python_client(String::from_utf8(encoded).unwrap());
662 }
663
664 #[test]
665 fn encode_gauge() {
666 let mut registry = Registry::default();
667 let gauge: Gauge = Gauge::default();
668 registry.register("my_gauge", "My gauge", gauge.clone());
669
670 let mut encoded = Vec::new();
671
672 encode(&mut encoded, ®istry).unwrap();
673
674 parse_with_python_client(String::from_utf8(encoded).unwrap());
675 }
676
677 #[test]
678 fn encode_counter_family() {
679 let mut registry = Registry::default();
680 let family = Family::<Vec<(String, String)>, Counter>::default();
681 registry.register("my_counter_family", "My counter family", family.clone());
682
683 family
684 .get_or_create(&vec![
685 ("method".to_string(), "GET".to_string()),
686 ("status".to_string(), "200".to_string()),
687 ])
688 .inc();
689
690 let mut encoded = Vec::new();
691
692 encode(&mut encoded, ®istry).unwrap();
693
694 parse_with_python_client(String::from_utf8(encoded).unwrap());
695 }
696
697 #[test]
698 fn encode_counter_family_with_prefix_with_label() {
699 let mut registry = Registry::default();
700 let sub_registry = registry.sub_registry_with_prefix("my_prefix");
701 let sub_sub_registry = sub_registry
702 .sub_registry_with_label((Cow::Borrowed("my_key"), Cow::Borrowed("my_value")));
703 let family = Family::<Vec<(String, String)>, Counter>::default();
704 sub_sub_registry.register("my_counter_family", "My counter family", family.clone());
705
706 family
707 .get_or_create(&vec![
708 ("method".to_string(), "GET".to_string()),
709 ("status".to_string(), "200".to_string()),
710 ])
711 .inc();
712
713 let mut encoded = Vec::new();
714
715 encode(&mut encoded, ®istry).unwrap();
716
717 let expected = "# HELP my_prefix_my_counter_family My counter family.\n"
718 .to_owned()
719 + "# TYPE my_prefix_my_counter_family counter\n"
720 + "my_prefix_my_counter_family_total{my_key=\"my_value\",method=\"GET\",status=\"200\"} 1\n"
721 + "# EOF\n";
722 assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap());
723
724 parse_with_python_client(String::from_utf8(encoded).unwrap());
725 }
726
727 #[test]
728 fn encode_info() {
729 let mut registry = Registry::default();
730 let info = Info::new(vec![("os".to_string(), "GNU/linux".to_string())]);
731 registry.register("my_info_metric", "My info metric", info);
732
733 let mut encoded = Vec::new();
734 encode(&mut encoded, ®istry).unwrap();
735
736 let expected = "# HELP my_info_metric My info metric.\n".to_owned()
737 + "# TYPE my_info_metric info\n"
738 + "my_info_metric_info{os=\"GNU/linux\"} 1\n"
739 + "# EOF\n";
740 assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap());
741
742 parse_with_python_client(String::from_utf8(encoded).unwrap());
743 }
744
745 #[test]
746 fn encode_histogram() {
747 let mut registry = Registry::default();
748 let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10));
749 registry.register("my_histogram", "My histogram", histogram.clone());
750 histogram.observe(1.0);
751
752 let mut encoded = Vec::new();
753
754 encode(&mut encoded, ®istry).unwrap();
755
756 parse_with_python_client(String::from_utf8(encoded).unwrap());
757 }
758
759 #[test]
760 fn encode_histogram_family() {
761 let mut registry = Registry::default();
762 let family =
763 Family::new_with_constructor(|| Histogram::new(exponential_buckets(1.0, 2.0, 10)));
764 registry.register("my_histogram", "My histogram", family.clone());
765 family
766 .get_or_create(&vec![
767 ("method".to_string(), "GET".to_string()),
768 ("status".to_string(), "200".to_string()),
769 ])
770 .observe(1.0);
771
772 let mut encoded = Vec::new();
773
774 encode(&mut encoded, ®istry).unwrap();
775
776 parse_with_python_client(String::from_utf8(encoded).unwrap());
777 }
778
779 #[test]
780 fn encode_histogram_with_exemplars() {
781 let mut registry = Registry::default();
782 let histogram = HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10));
783 registry.register("my_histogram", "My histogram", histogram.clone());
784 histogram.observe(1.0, Some(("user_id".to_string(), 42u64)));
785
786 let mut encoded = Vec::new();
787 encode(&mut encoded, ®istry).unwrap();
788
789 let expected = "# HELP my_histogram My histogram.\n".to_owned()
790 + "# TYPE my_histogram histogram\n"
791 + "my_histogram_sum 1.0\n"
792 + "my_histogram_count 1\n"
793 + "my_histogram_bucket{le=\"1.0\"} 1 # {user_id=\"42\"} 1.0\n"
794 + "my_histogram_bucket{le=\"2.0\"} 1\n"
795 + "my_histogram_bucket{le=\"4.0\"} 1\n"
796 + "my_histogram_bucket{le=\"8.0\"} 1\n"
797 + "my_histogram_bucket{le=\"16.0\"} 1\n"
798 + "my_histogram_bucket{le=\"32.0\"} 1\n"
799 + "my_histogram_bucket{le=\"64.0\"} 1\n"
800 + "my_histogram_bucket{le=\"128.0\"} 1\n"
801 + "my_histogram_bucket{le=\"256.0\"} 1\n"
802 + "my_histogram_bucket{le=\"512.0\"} 1\n"
803 + "my_histogram_bucket{le=\"+Inf\"} 1\n"
804 + "# EOF\n";
805 assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap());
806
807 parse_with_python_client(String::from_utf8(encoded).unwrap());
808 }
809
810 fn parse_with_python_client(input: String) {
811 pyo3::prepare_freethreaded_python();
812
813 println!("{:?}", input);
814 Python::with_gil(|py| {
815 let parser = PyModule::from_code(
816 py,
817 r#"
818from prometheus_client.openmetrics.parser import text_string_to_metric_families
819
820def parse(input):
821 families = text_string_to_metric_families(input)
822 list(families)
823"#,
824 "parser.py",
825 "parser",
826 )
827 .map_err(|e| e.to_string())
828 .unwrap();
829
830 parser
831 .getattr("parse")
832 .expect("`parse` to exist.")
833 .call1((input,))
834 .map_err(|e| e.to_string())
835 .unwrap();
836 })
837 }
838}