1use std::{
2 convert::Infallible,
3 hash::Hash,
4 mem::{Discriminant, discriminant},
5 net::{IpAddr, Ipv4Addr, SocketAddr},
6 sync::{Arc, RwLock},
7 time::{Duration, Instant},
8};
9
10use async_trait::async_trait;
11use base64::prelude::{BASE64_STANDARD, Engine as _};
12use futures::{FutureExt, StreamExt, future, stream::BoxStream};
13use hyper::{
14 Body, Method, Request, Response, Server, StatusCode,
15 body::HttpBody,
16 header::HeaderValue,
17 service::{make_service_fn, service_fn},
18};
19use indexmap::{IndexMap, map::Entry};
20use serde_with::serde_as;
21use snafu::Snafu;
22use stream_cancel::{Trigger, Tripwire};
23use tower::ServiceBuilder;
24use tower_http::compression::CompressionLayer;
25use tracing::{Instrument, Span};
26use vector_lib::{
27 ByteSizeOf, EstimatedJsonEncodedSizeOf,
28 configurable::configurable_component,
29 internal_event::{
30 ByteSize, BytesSent, CountByteSize, EventsSent, InternalEventHandle as _, Output, Protocol,
31 Registered,
32 },
33};
34
35use super::collector::{MetricCollector, StringCollector};
36use crate::{
37 config::{AcknowledgementsConfig, GenerateConfig, Input, Resource, SinkConfig, SinkContext},
38 event::{
39 Event, EventStatus, Finalizable,
40 metric::{Metric, MetricData, MetricKind, MetricSeries, MetricValue},
41 },
42 http::{Auth, build_http_trace_layer},
43 internal_events::PrometheusNormalizationError,
44 sinks::{
45 Healthcheck, VectorSink,
46 util::{StreamSink, statistic::validate_quantiles},
47 },
48 tls::{MaybeTlsSettings, TlsEnableableConfig},
49};
50
51const MIN_FLUSH_PERIOD_SECS: u64 = 1;
52
53const LOCK_FAILED: &str = "Prometheus exporter data lock is poisoned";
54
55#[derive(Debug, Snafu)]
56enum BuildError {
57 #[snafu(display("Flush period for sets must be greater or equal to {} secs", min))]
58 FlushPeriodTooShort { min: u64 },
59}
60
61#[serde_as]
63#[configurable_component(sink(
64 "prometheus_exporter",
65 "Expose metric events on a Prometheus compatible endpoint."
66))]
67#[derive(Clone, Debug)]
68#[serde(deny_unknown_fields)]
69pub struct PrometheusExporterConfig {
70 #[serde(alias = "namespace")]
79 #[configurable(metadata(docs::advanced))]
80 pub default_namespace: Option<String>,
81
82 #[serde(default = "default_address")]
86 #[configurable(metadata(docs::examples = "192.160.0.10:9598"))]
87 pub address: SocketAddr,
88
89 #[configurable(derived)]
90 pub auth: Option<Auth>,
91
92 #[configurable(derived)]
93 pub tls: Option<TlsEnableableConfig>,
94
95 #[serde(default = "super::default_histogram_buckets")]
99 #[configurable(metadata(docs::advanced))]
100 pub buckets: Vec<f64>,
101
102 #[serde(default = "super::default_summary_quantiles")]
106 #[configurable(metadata(docs::advanced))]
107 pub quantiles: Vec<f64>,
108
109 #[serde(default = "default_distributions_as_summaries")]
119 #[configurable(metadata(docs::advanced))]
120 pub distributions_as_summaries: bool,
121
122 #[serde(default = "default_flush_period_secs")]
129 #[serde_as(as = "serde_with::DurationSeconds<u64>")]
130 #[configurable(metadata(docs::advanced))]
131 #[configurable(metadata(docs::human_name = "Flush Interval"))]
132 pub flush_period_secs: Duration,
133
134 #[serde(default)]
140 #[configurable(metadata(docs::advanced))]
141 pub suppress_timestamp: bool,
142
143 #[configurable(derived)]
144 #[serde(
145 default,
146 deserialize_with = "crate::serde::bool_or_struct",
147 skip_serializing_if = "crate::serde::is_default"
148 )]
149 pub acknowledgements: AcknowledgementsConfig,
150}
151
152impl Default for PrometheusExporterConfig {
153 fn default() -> Self {
154 Self {
155 default_namespace: None,
156 address: default_address(),
157 auth: None,
158 tls: None,
159 buckets: super::default_histogram_buckets(),
160 quantiles: super::default_summary_quantiles(),
161 distributions_as_summaries: default_distributions_as_summaries(),
162 flush_period_secs: default_flush_period_secs(),
163 suppress_timestamp: default_suppress_timestamp(),
164 acknowledgements: Default::default(),
165 }
166 }
167}
168
169const fn default_address() -> SocketAddr {
170 SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9598)
171}
172
173const fn default_distributions_as_summaries() -> bool {
174 false
175}
176
177const fn default_flush_period_secs() -> Duration {
178 Duration::from_secs(60)
179}
180
181const fn default_suppress_timestamp() -> bool {
182 false
183}
184
185impl GenerateConfig for PrometheusExporterConfig {
186 fn generate_config() -> toml::Value {
187 toml::Value::try_from(Self::default()).unwrap()
188 }
189}
190
191#[async_trait::async_trait]
192#[typetag::serde(name = "prometheus_exporter")]
193impl SinkConfig for PrometheusExporterConfig {
194 async fn build(&self, _cx: SinkContext) -> crate::Result<(VectorSink, Healthcheck)> {
195 if self.flush_period_secs.as_secs() < MIN_FLUSH_PERIOD_SECS {
196 return Err(Box::new(BuildError::FlushPeriodTooShort {
197 min: MIN_FLUSH_PERIOD_SECS,
198 }));
199 }
200
201 validate_quantiles(&self.quantiles)?;
202
203 let sink = PrometheusExporter::new(self.clone());
204 let healthcheck = future::ok(()).boxed();
205
206 Ok((VectorSink::from_event_streamsink(sink), healthcheck))
207 }
208
209 fn input(&self) -> Input {
210 Input::metric()
211 }
212
213 fn resources(&self) -> Vec<Resource> {
214 vec![Resource::tcp(self.address)]
215 }
216
217 fn acknowledgements(&self) -> &AcknowledgementsConfig {
218 &self.acknowledgements
219 }
220}
221
222struct PrometheusExporter {
223 server_shutdown_trigger: Option<Trigger>,
224 config: PrometheusExporterConfig,
225 metrics: Arc<RwLock<IndexMap<MetricRef, (Metric, MetricMetadata)>>>,
226}
227
228#[derive(Clone, Copy, Debug)]
230struct MetricMetadata {
231 expiration_window: Duration,
232 expires_at: Instant,
233}
234
235impl MetricMetadata {
236 pub fn new(expiration_window: Duration) -> Self {
237 Self {
238 expiration_window,
239 expires_at: Instant::now() + expiration_window,
240 }
241 }
242
243 pub fn refresh(&mut self) {
245 self.expires_at = Instant::now() + self.expiration_window;
246 }
247
248 pub fn has_expired(&self, now: Instant) -> bool {
250 now >= self.expires_at
251 }
252}
253
254#[derive(Clone, Debug)]
263struct MetricRef {
264 series: MetricSeries,
265 value: Discriminant<MetricValue>,
266 bounds: Option<Vec<f64>>,
267}
268
269impl MetricRef {
270 pub fn from_metric(metric: &Metric) -> Self {
272 let bounds = match metric.value() {
274 MetricValue::AggregatedHistogram { buckets, .. } => {
275 Some(buckets.iter().map(|b| b.upper_limit).collect())
276 }
277 MetricValue::AggregatedSummary { quantiles, .. } => {
278 Some(quantiles.iter().map(|q| q.quantile).collect())
279 }
280 _ => None,
281 };
282
283 Self {
284 series: metric.series().clone(),
285 value: discriminant(metric.value()),
286 bounds,
287 }
288 }
289}
290
291impl PartialEq for MetricRef {
292 fn eq(&self, other: &Self) -> bool {
293 self.series == other.series && self.value == other.value && self.bounds == other.bounds
294 }
295}
296
297impl Eq for MetricRef {}
298
299impl Hash for MetricRef {
300 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
301 self.series.hash(state);
302 self.value.hash(state);
303 if let Some(bounds) = &self.bounds {
304 for bound in bounds {
305 bound.to_bits().hash(state);
306 }
307 }
308 }
309}
310
311fn authorized<T: HttpBody>(req: &Request<T>, auth: &Option<Auth>) -> bool {
312 if let Some(auth) = auth {
313 let headers = req.headers();
314 if let Some(auth_header) = headers.get(hyper::header::AUTHORIZATION) {
315 let encoded_credentials = match auth {
316 Auth::Basic { user, password } => Some(HeaderValue::from_str(
317 format!(
318 "Basic {}",
319 BASE64_STANDARD.encode(format!("{}:{}", user, password.inner()))
320 )
321 .as_str(),
322 )),
323 Auth::Bearer { token } => Some(HeaderValue::from_str(
324 format!("Bearer {}", token.inner()).as_str(),
325 )),
326 Auth::Custom { value } => Some(HeaderValue::from_str(value)),
327 #[cfg(feature = "aws-core")]
328 _ => None,
329 };
330
331 if let Some(Ok(encoded_credentials)) = encoded_credentials
332 && auth_header == encoded_credentials
333 {
334 return true;
335 }
336 }
337 } else {
338 return true;
339 }
340
341 false
342}
343
344#[derive(Clone)]
345struct Handler {
346 auth: Option<Auth>,
347 default_namespace: Option<String>,
348 buckets: Box<[f64]>,
349 quantiles: Box<[f64]>,
350 bytes_sent: Registered<BytesSent>,
351 events_sent: Registered<EventsSent>,
352}
353
354impl Handler {
355 fn handle<T: HttpBody>(
356 &self,
357 req: Request<T>,
358 metrics: &RwLock<IndexMap<MetricRef, (Metric, MetricMetadata)>>,
359 ) -> Response<Body> {
360 let mut response = Response::new(Body::empty());
361
362 match (authorized(&req, &self.auth), req.method(), req.uri().path()) {
363 (false, _, _) => {
364 *response.status_mut() = StatusCode::UNAUTHORIZED;
365 response.headers_mut().insert(
366 http::header::WWW_AUTHENTICATE,
367 HeaderValue::from_static("Basic, Bearer"),
368 );
369 }
370
371 (true, &Method::GET, "/metrics") => {
372 let metrics = metrics.read().expect(LOCK_FAILED);
373
374 let count = metrics.len();
375 let byte_size = metrics
376 .iter()
377 .map(|(_, (metric, _))| metric.estimated_json_encoded_size_of())
378 .sum();
379
380 let mut collector = StringCollector::new();
381
382 for (_, (metric, _)) in metrics.iter() {
383 collector.encode_metric(
384 self.default_namespace.as_deref(),
385 &self.buckets,
386 &self.quantiles,
387 metric,
388 );
389 }
390
391 drop(metrics);
392
393 let body = collector.finish();
394 let body_size = body.size_of();
395
396 *response.body_mut() = body.into();
397
398 response.headers_mut().insert(
399 "Content-Type",
400 HeaderValue::from_static("text/plain; version=0.0.4"),
401 );
402
403 self.events_sent.emit(CountByteSize(count, byte_size));
404 self.bytes_sent.emit(ByteSize(body_size));
405 }
406
407 (true, _, _) => {
408 *response.status_mut() = StatusCode::NOT_FOUND;
409 }
410 }
411
412 response
413 }
414}
415
416impl PrometheusExporter {
417 fn new(config: PrometheusExporterConfig) -> Self {
418 Self {
419 server_shutdown_trigger: None,
420 config,
421 metrics: Arc::new(RwLock::new(IndexMap::new())),
422 }
423 }
424
425 async fn start_server_if_needed(&mut self) -> crate::Result<()> {
426 if self.server_shutdown_trigger.is_some() {
427 return Ok(());
428 }
429
430 let handler = Handler {
431 bytes_sent: register!(BytesSent::from(Protocol::HTTP)),
432 events_sent: register!(EventsSent::from(Output(None))),
433 default_namespace: self.config.default_namespace.clone(),
434 buckets: self.config.buckets.clone().into(),
435 quantiles: self.config.quantiles.clone().into(),
436 auth: self.config.auth.clone(),
437 };
438
439 let span = Span::current();
440 let metrics = Arc::clone(&self.metrics);
441
442 let new_service = make_service_fn(move |_| {
443 let span = Span::current();
444 let metrics = Arc::clone(&metrics);
445 let handler = handler.clone();
446
447 let inner = service_fn(move |req| {
448 let response = handler.handle(req, &metrics);
449
450 future::ok::<_, Infallible>(response)
451 });
452
453 let service = ServiceBuilder::new()
454 .layer(build_http_trace_layer(span.clone()))
455 .layer(CompressionLayer::new())
456 .service(inner);
457
458 async move { Ok::<_, Infallible>(service) }
459 });
460
461 let (trigger, tripwire) = Tripwire::new();
462
463 let tls = self.config.tls.clone();
464 let address = self.config.address;
465
466 let tls = MaybeTlsSettings::from_config(tls.as_ref(), true)?;
467 let listener = tls.bind(&address).await?;
468
469 crate::spawn_in_current_span(async move {
470 info!(message = "Building HTTP server.", address = %address);
471
472 Server::builder(hyper::server::accept::from_stream(listener.accept_stream()))
473 .serve(new_service)
474 .with_graceful_shutdown(tripwire.then(crate::shutdown::tripwire_handler))
475 .instrument(span)
476 .await
477 .map_err(|error| error!("Server error: {}.", error))?;
478
479 Ok::<(), ()>(())
480 });
481
482 self.server_shutdown_trigger = Some(trigger);
483 Ok(())
484 }
485
486 fn normalize(&mut self, metric: Metric) -> Option<Metric> {
487 let new_metric = match metric.value() {
488 MetricValue::Distribution { .. } => {
489 let (series, data, metadata) = metric.into_parts();
491 let (time, kind, value) = data.into_parts();
492
493 let new_value = if self.config.distributions_as_summaries {
494 value
499 .distribution_to_sketch()
500 .expect("value should be distribution already")
501 } else {
502 value
503 .distribution_to_agg_histogram(&self.config.buckets)
504 .expect("value should be distribution already")
505 };
506
507 let data = MetricData::from_parts(time, kind, new_value);
508 Metric::from_parts(series, data, metadata)
509 }
510 _ => metric,
511 };
512
513 match new_metric.kind() {
514 MetricKind::Absolute => Some(new_metric),
515 MetricKind::Incremental => {
516 let metrics = self.metrics.read().expect(LOCK_FAILED);
517 let metric_ref = MetricRef::from_metric(&new_metric);
518
519 if let Some(existing) = metrics.get(&metric_ref) {
520 let mut current = existing.0.value().clone();
521 if current.add(new_metric.value()) {
522 return Some(new_metric.with_value(current).into_absolute());
525 }
526 }
527
528 Some(new_metric.into_absolute())
531 }
532 }
533 }
534}
535
536#[async_trait]
537impl StreamSink<Event> for PrometheusExporter {
538 async fn run(mut self: Box<Self>, mut input: BoxStream<'_, Event>) -> Result<(), ()> {
539 self.start_server_if_needed()
540 .await
541 .map_err(|error| error!("Failed to start Prometheus exporter: {}.", error))?;
542
543 let mut last_flush = Instant::now();
544 let flush_period = self.config.flush_period_secs;
545
546 while let Some(event) = input.next().await {
547 if last_flush.elapsed() > self.config.flush_period_secs {
556 last_flush = Instant::now();
557
558 let mut metrics = self.metrics.write().expect(LOCK_FAILED);
559
560 metrics.retain(|_metric_ref, (_, metadata)| !metadata.has_expired(last_flush));
561 }
562
563 let mut metric = event.into_metric();
565 let finalizers = metric.take_finalizers();
566
567 match self.normalize(metric) {
568 Some(normalized) => {
569 let normalized = if self.config.suppress_timestamp {
570 normalized.with_timestamp(None)
571 } else {
572 normalized
573 };
574
575 let mut metrics = self.metrics.write().expect(LOCK_FAILED);
578
579 match metrics.entry(MetricRef::from_metric(&normalized)) {
580 Entry::Occupied(mut entry) => {
581 let (data, metadata) = entry.get_mut();
582 *data = normalized;
583 metadata.refresh();
584 }
585 Entry::Vacant(entry) => {
586 entry.insert((normalized, MetricMetadata::new(flush_period)));
587 }
588 }
589 finalizers.update_status(EventStatus::Delivered);
590 }
591 _ => {
592 emit!(PrometheusNormalizationError {});
593 finalizers.update_status(EventStatus::Errored);
594 }
595 }
596 }
597
598 Ok(())
599 }
600}
601
602#[cfg(test)]
603mod tests {
604 use chrono::{Duration, Utc};
605 use futures::stream;
606 use indoc::indoc;
607 use similar_asserts::assert_eq;
608 use tokio::{sync::oneshot::error::TryRecvError, time};
609 use vector_common::decompression::CappedDecoder;
610 use vector_lib::{
611 event::{MetricTags, StatisticKind},
612 finalization::{BatchNotifier, BatchStatus},
613 metric_tags, samples,
614 sensitive_string::SensitiveString,
615 };
616
617 use super::*;
618 use crate::{
619 config::ProxyConfig,
620 event::metric::{Metric, MetricValue},
621 http::HttpClient,
622 sinks::prometheus::{distribution_to_agg_histogram, distribution_to_ddsketch},
623 test_util::{
624 addr::next_addr,
625 components::{SINK_TAGS, run_and_assert_sink_compliance},
626 random_string, trace_init,
627 },
628 tls::MaybeTlsSettings,
629 };
630
631 #[test]
632 fn generate_config() {
633 crate::test_util::test_generate_config::<PrometheusExporterConfig>();
634 }
635
636 #[tokio::test]
637 async fn prometheus_notls() {
638 export_and_fetch_simple(None).await;
639 }
640
641 #[tokio::test]
642 async fn prometheus_tls() {
643 let mut tls_config = TlsEnableableConfig::test_config();
644 tls_config.options.verify_hostname = Some(false);
645 export_and_fetch_simple(Some(tls_config)).await;
646 }
647
648 #[tokio::test]
649 async fn prometheus_noauth() {
650 let (name1, event1) = create_metric_gauge(None, 123.4);
651 let (name2, event2) = tests::create_metric_set(None, vec!["0", "1", "2"]);
652 let events = vec![event1, event2];
653
654 let response_result = export_and_fetch_with_auth(None, None, events, false).await;
655
656 assert!(response_result.is_ok());
657
658 let body = response_result.expect("Cannot extract body from the response");
659
660 assert!(body.contains(&format!(
661 indoc! {r#"
662 # HELP {name} {name}
663 # TYPE {name} gauge
664 {name}{{some_tag="some_value"}} 123.4
665 "#},
666 name = name1
667 )));
668 assert!(body.contains(&format!(
669 indoc! {r#"
670 # HELP {name} {name}
671 # TYPE {name} gauge
672 {name}{{some_tag="some_value"}} 3
673 "#},
674 name = name2
675 )));
676 }
677
678 #[tokio::test]
679 async fn prometheus_successful_basic_auth() {
680 let (name1, event1) = create_metric_gauge(None, 123.4);
681 let (name2, event2) = tests::create_metric_set(None, vec!["0", "1", "2"]);
682 let events = vec![event1, event2];
683
684 let auth_config = Auth::Basic {
685 user: "user".to_string(),
686 password: SensitiveString::from("password".to_string()),
687 };
688
689 let response_result =
690 export_and_fetch_with_auth(Some(auth_config.clone()), Some(auth_config), events, false)
691 .await;
692
693 assert!(response_result.is_ok());
694
695 let body = response_result.expect("Cannot extract body from the response");
696
697 assert!(body.contains(&format!(
698 indoc! {r#"
699 # HELP {name} {name}
700 # TYPE {name} gauge
701 {name}{{some_tag="some_value"}} 123.4
702 "#},
703 name = name1
704 )));
705 assert!(body.contains(&format!(
706 indoc! {r#"
707 # HELP {name} {name}
708 # TYPE {name} gauge
709 {name}{{some_tag="some_value"}} 3
710 "#},
711 name = name2
712 )));
713 }
714
715 #[tokio::test]
716 async fn prometheus_successful_token_auth() {
717 let (name1, event1) = create_metric_gauge(None, 123.4);
718 let (name2, event2) = tests::create_metric_set(None, vec!["0", "1", "2"]);
719 let events = vec![event1, event2];
720
721 let auth_config = Auth::Bearer {
722 token: SensitiveString::from("token".to_string()),
723 };
724
725 let response_result =
726 export_and_fetch_with_auth(Some(auth_config.clone()), Some(auth_config), events, false)
727 .await;
728
729 assert!(response_result.is_ok());
730
731 let body = response_result.expect("Cannot extract body from the response");
732
733 assert!(body.contains(&format!(
734 indoc! {r#"
735 # HELP {name} {name}
736 # TYPE {name} gauge
737 {name}{{some_tag="some_value"}} 123.4
738 "#},
739 name = name1
740 )));
741 assert!(body.contains(&format!(
742 indoc! {r#"
743 # HELP {name} {name}
744 # TYPE {name} gauge
745 {name}{{some_tag="some_value"}} 3
746 "#},
747 name = name2
748 )));
749 }
750
751 #[tokio::test]
752 async fn prometheus_missing_auth() {
753 let (_, event1) = create_metric_gauge(None, 123.4);
754 let (_, event2) = tests::create_metric_set(None, vec!["0", "1", "2"]);
755 let events = vec![event1, event2];
756
757 let server_auth_config = Auth::Bearer {
758 token: SensitiveString::from("token".to_string()),
759 };
760
761 let response_result =
762 export_and_fetch_with_auth(Some(server_auth_config), None, events, false).await;
763
764 assert!(response_result.is_err());
765 assert_eq!(response_result.unwrap_err(), StatusCode::UNAUTHORIZED);
766 }
767
768 #[tokio::test]
769 async fn prometheus_wrong_auth() {
770 let (_, event1) = create_metric_gauge(None, 123.4);
771 let (_, event2) = tests::create_metric_set(None, vec!["0", "1", "2"]);
772 let events = vec![event1, event2];
773
774 let server_auth_config = Auth::Bearer {
775 token: SensitiveString::from("token".to_string()),
776 };
777
778 let client_auth_config = Auth::Basic {
779 user: "user".to_string(),
780 password: SensitiveString::from("password".to_string()),
781 };
782
783 let response_result = export_and_fetch_with_auth(
784 Some(server_auth_config),
785 Some(client_auth_config),
786 events,
787 false,
788 )
789 .await;
790
791 assert!(response_result.is_err());
792 assert_eq!(response_result.unwrap_err(), StatusCode::UNAUTHORIZED);
793 }
794
795 #[tokio::test]
796 async fn encoding_gzip() {
797 let (name1, event1) = create_metric_gauge(None, 123.4);
798 let events = vec![event1];
799
800 let body_raw = export_and_fetch_raw(None, events, false, Some(String::from("gzip"))).await;
801 let expected = format!(
802 indoc! {r#"
803 # HELP {name} {name}
804 # TYPE {name} gauge
805 {name}{{some_tag="some_value"}} 123.4
806 "#},
807 name = name1,
808 );
809
810 let body_decoded =
811 String::from_utf8(CappedDecoder::gzip(&body_raw[..]).decompress().unwrap()).unwrap();
812
813 assert!(body_raw.len() < expected.len());
814 assert_eq!(body_decoded, expected);
815 }
816
817 #[tokio::test]
818 async fn updates_timestamps() {
819 let timestamp1 = Utc::now();
820 let (name, event1) = create_metric_gauge(None, 123.4);
821 let event1 = Event::from(event1.into_metric().with_timestamp(Some(timestamp1)));
822 let (_, event2) = create_metric_gauge(Some(name.clone()), 12.0);
823 let timestamp2 = timestamp1 + Duration::seconds(1);
824 let event2 = Event::from(event2.into_metric().with_timestamp(Some(timestamp2)));
825 let events = vec![event1, event2];
826
827 let body = export_and_fetch(None, events, false).await;
828 let timestamp = timestamp2.timestamp_millis();
829 assert_eq!(
830 body,
831 format!(
832 indoc! {r#"
833 # HELP {name} {name}
834 # TYPE {name} gauge
835 {name}{{some_tag="some_value"}} 135.4 {timestamp}
836 "#},
837 name = name,
838 timestamp = timestamp
839 )
840 );
841 }
842
843 #[tokio::test]
844 async fn suppress_timestamp() {
845 let timestamp = Utc::now();
846 let (name, event) = create_metric_gauge(None, 123.4);
847 let event = Event::from(event.into_metric().with_timestamp(Some(timestamp)));
848 let events = vec![event];
849
850 let body = export_and_fetch(None, events, true).await;
851 assert_eq!(
852 body,
853 format!(
854 indoc! {r#"
855 # HELP {name} {name}
856 # TYPE {name} gauge
857 {name}{{some_tag="some_value"}} 123.4
858 "#},
859 name = name,
860 )
861 );
862 }
863
864 #[tokio::test]
869 async fn prometheus_duplicate_labels() {
870 let (name, event) = create_metric_with_tags(
871 None,
872 MetricValue::Gauge { value: 123.4 },
873 Some(metric_tags!("code" => "200", "code" => "success")),
874 );
875 let events = vec![event];
876
877 let response_result = export_and_fetch_with_auth(None, None, events, false).await;
878
879 assert!(response_result.is_ok());
880
881 let body = response_result.expect("Cannot extract body from the response");
882
883 assert!(body.contains(&format!(
884 indoc! {r#"
885 # HELP {name} {name}
886 # TYPE {name} gauge
887 {name}{{code="success"}} 123.4
888 "# },
889 name = name
890 )));
891 }
892
893 async fn export_and_fetch_raw(
894 tls_config: Option<TlsEnableableConfig>,
895 mut events: Vec<Event>,
896 suppress_timestamp: bool,
897 encoding: Option<String>,
898 ) -> hyper::body::Bytes {
899 trace_init();
900
901 let client_settings = MaybeTlsSettings::from_config(tls_config.as_ref(), false).unwrap();
902 let proto = client_settings.http_protocol_name();
903
904 let (_guard, address) = next_addr();
905 let config = PrometheusExporterConfig {
906 address,
907 tls: tls_config,
908 suppress_timestamp,
909 ..Default::default()
910 };
911
912 let mut receiver = BatchNotifier::apply_to(&mut events[..]);
914 assert_eq!(receiver.try_recv(), Err(TryRecvError::Empty));
915
916 let (sink, _) = config.build(SinkContext::default()).await.unwrap();
917 let (_, delayed_event) = create_metric_gauge(Some("delayed".to_string()), 123.4);
918 let sink_handle = tokio::spawn(run_and_assert_sink_compliance(
919 sink,
920 stream::iter(events).chain(stream::once(async move {
921 time::sleep(time::Duration::from_millis(500)).await;
923 delayed_event
924 })),
925 &SINK_TAGS,
926 ));
927
928 time::sleep(time::Duration::from_millis(100)).await;
929
930 assert_eq!(receiver.try_recv(), Ok(BatchStatus::Delivered));
932
933 let mut request = Request::get(format!("{proto}://{address}/metrics"))
934 .body(Body::empty())
935 .expect("Error creating request.");
936
937 if let Some(ref encoding) = encoding {
938 request.headers_mut().insert(
939 http::header::ACCEPT_ENCODING,
940 HeaderValue::from_str(encoding.as_str()).unwrap(),
941 );
942 }
943
944 let proxy = ProxyConfig::default();
945 let result = HttpClient::new(client_settings, &proxy)
946 .unwrap()
947 .send(request)
948 .await
949 .expect("Could not fetch query");
950
951 assert!(result.status().is_success());
952
953 if encoding.is_some() {
954 assert!(
955 result
956 .headers()
957 .contains_key(http::header::CONTENT_ENCODING)
958 );
959 }
960
961 let body = result.into_body();
962 let bytes = http_body::Body::collect(body)
963 .await
964 .expect("Reading body failed")
965 .to_bytes();
966
967 sink_handle.await.unwrap();
968
969 bytes
970 }
971
972 async fn export_and_fetch(
973 tls_config: Option<TlsEnableableConfig>,
974 events: Vec<Event>,
975 suppress_timestamp: bool,
976 ) -> String {
977 let bytes = export_and_fetch_raw(tls_config, events, suppress_timestamp, None);
978 String::from_utf8(bytes.await.to_vec()).unwrap()
979 }
980
981 async fn export_and_fetch_with_auth(
982 server_auth_config: Option<Auth>,
983 client_auth_config: Option<Auth>,
984 mut events: Vec<Event>,
985 suppress_timestamp: bool,
986 ) -> Result<String, http::status::StatusCode> {
987 trace_init();
988
989 let client_settings = MaybeTlsSettings::from_config(None, false).unwrap();
990 let proto = client_settings.http_protocol_name();
991
992 let (_guard, address) = next_addr();
993 let config = PrometheusExporterConfig {
994 address,
995 auth: server_auth_config,
996 tls: None,
997 suppress_timestamp,
998 ..Default::default()
999 };
1000
1001 let mut receiver = BatchNotifier::apply_to(&mut events[..]);
1003 assert_eq!(receiver.try_recv(), Err(TryRecvError::Empty));
1004
1005 let (sink, _) = config.build(SinkContext::default()).await.unwrap();
1006 let (_, delayed_event) = create_metric_gauge(Some("delayed".to_string()), 123.4);
1007 let sink_handle = tokio::spawn(run_and_assert_sink_compliance(
1008 sink,
1009 stream::iter(events).chain(stream::once(async move {
1010 time::sleep(time::Duration::from_millis(500)).await;
1012 delayed_event
1013 })),
1014 &SINK_TAGS,
1015 ));
1016
1017 time::sleep(time::Duration::from_millis(100)).await;
1018
1019 assert_eq!(receiver.try_recv(), Ok(BatchStatus::Delivered));
1021
1022 let mut request = Request::get(format!("{proto}://{address}/metrics"))
1023 .body(Body::empty())
1024 .expect("Error creating request.");
1025
1026 if let Some(client_auth_config) = client_auth_config {
1027 client_auth_config.apply(&mut request);
1028 }
1029
1030 let proxy = ProxyConfig::default();
1031 let result = HttpClient::new(client_settings, &proxy)
1032 .unwrap()
1033 .send(request)
1034 .await
1035 .expect("Could not fetch query");
1036
1037 if !result.status().is_success() {
1038 return Err(result.status());
1039 }
1040
1041 let body = result.into_body();
1042 let bytes = http_body::Body::collect(body)
1043 .await
1044 .expect("Reading body failed")
1045 .to_bytes();
1046 let result = String::from_utf8(bytes.to_vec()).unwrap();
1047
1048 sink_handle.await.unwrap();
1049
1050 Ok(result)
1051 }
1052
1053 async fn export_and_fetch_simple(tls_config: Option<TlsEnableableConfig>) {
1054 let (name1, event1) = create_metric_gauge(None, 123.4);
1055 let (name2, event2) = tests::create_metric_set(None, vec!["0", "1", "2"]);
1056 let events = vec![event1, event2];
1057
1058 let body = export_and_fetch(tls_config, events, false).await;
1059
1060 assert!(body.contains(&format!(
1061 indoc! {r#"
1062 # HELP {name} {name}
1063 # TYPE {name} gauge
1064 {name}{{some_tag="some_value"}} 123.4
1065 "#},
1066 name = name1
1067 )));
1068 assert!(body.contains(&format!(
1069 indoc! {r#"
1070 # HELP {name} {name}
1071 # TYPE {name} gauge
1072 {name}{{some_tag="some_value"}} 3
1073 "#},
1074 name = name2
1075 )));
1076 }
1077
1078 pub fn create_metric_gauge(name: Option<String>, value: f64) -> (String, Event) {
1079 create_metric(name, MetricValue::Gauge { value })
1080 }
1081
1082 pub fn create_metric_set(name: Option<String>, values: Vec<&'static str>) -> (String, Event) {
1083 create_metric(
1084 name,
1085 MetricValue::Set {
1086 values: values.into_iter().map(Into::into).collect(),
1087 },
1088 )
1089 }
1090
1091 fn create_metric(name: Option<String>, value: MetricValue) -> (String, Event) {
1092 create_metric_with_tags(name, value, Some(metric_tags!("some_tag" => "some_value")))
1093 }
1094
1095 fn create_metric_with_tags(
1096 name: Option<String>,
1097 value: MetricValue,
1098 tags: Option<MetricTags>,
1099 ) -> (String, Event) {
1100 let name = name.unwrap_or_else(|| format!("vector_set_{}", random_string(16)));
1101 let event = Metric::new(name.clone(), MetricKind::Incremental, value)
1102 .with_tags(tags)
1103 .into();
1104 (name, event)
1105 }
1106
1107 #[tokio::test]
1108 async fn sink_absolute() {
1109 let (_guard, address) = next_addr();
1110 let config = PrometheusExporterConfig {
1111 address,
1112 tls: None,
1113 ..Default::default()
1114 };
1115
1116 let sink = PrometheusExporter::new(config);
1117
1118 let m1 = Metric::new(
1119 "absolute",
1120 MetricKind::Absolute,
1121 MetricValue::Counter { value: 32. },
1122 )
1123 .with_tags(Some(metric_tags!("tag1" => "value1")));
1124
1125 let m2 = m1.clone().with_tags(Some(metric_tags!("tag1" => "value2")));
1126
1127 let events = vec![
1128 Event::Metric(m1.clone().with_value(MetricValue::Counter { value: 32. })),
1129 Event::Metric(m2.clone().with_value(MetricValue::Counter { value: 33. })),
1130 Event::Metric(m1.clone().with_value(MetricValue::Counter { value: 40. })),
1131 ];
1132
1133 let metrics_handle = Arc::clone(&sink.metrics);
1134
1135 let sink = VectorSink::from_event_streamsink(sink);
1136 let input_events = stream::iter(events).map(Into::into);
1137 sink.run(input_events).await.unwrap();
1138
1139 let metrics_after = metrics_handle.read().unwrap();
1140
1141 let expected_m1 = metrics_after
1142 .get(&MetricRef::from_metric(&m1))
1143 .expect("m1 should exist");
1144 let expected_m1_value = MetricValue::Counter { value: 40. };
1145 assert_eq!(expected_m1.0.value(), &expected_m1_value);
1146
1147 let expected_m2 = metrics_after
1148 .get(&MetricRef::from_metric(&m2))
1149 .expect("m2 should exist");
1150 let expected_m2_value = MetricValue::Counter { value: 33. };
1151 assert_eq!(expected_m2.0.value(), &expected_m2_value);
1152 }
1153
1154 #[tokio::test]
1155 async fn sink_distributions_as_histograms() {
1156 let (_guard, address) = next_addr();
1164 let config = PrometheusExporterConfig {
1165 address,
1166 tls: None,
1167 ..Default::default()
1168 };
1169 let buckets = config.buckets.clone();
1170
1171 let sink = PrometheusExporter::new(config);
1172
1173 let base_summary_metric = Metric::new(
1175 "distrib_summary",
1176 MetricKind::Incremental,
1177 MetricValue::Distribution {
1178 statistic: StatisticKind::Summary,
1179 samples: samples!(1.0 => 1, 3.0 => 2),
1180 },
1181 );
1182
1183 let base_histogram_metric = Metric::new(
1184 "distrib_histo",
1185 MetricKind::Incremental,
1186 MetricValue::Distribution {
1187 statistic: StatisticKind::Histogram,
1188 samples: samples!(7.0 => 1, 9.0 => 2),
1189 },
1190 );
1191
1192 let metrics = [
1193 base_summary_metric.clone(),
1194 base_summary_metric
1195 .clone()
1196 .with_value(MetricValue::Distribution {
1197 statistic: StatisticKind::Summary,
1198 samples: samples!(1.0 => 2, 2.9 => 1),
1199 }),
1200 base_summary_metric
1201 .clone()
1202 .with_value(MetricValue::Distribution {
1203 statistic: StatisticKind::Summary,
1204 samples: samples!(1.0 => 4, 3.2 => 1),
1205 }),
1206 base_histogram_metric.clone(),
1207 base_histogram_metric
1208 .clone()
1209 .with_value(MetricValue::Distribution {
1210 statistic: StatisticKind::Histogram,
1211 samples: samples!(7.0 => 2, 9.9 => 1),
1212 }),
1213 base_histogram_metric
1214 .clone()
1215 .with_value(MetricValue::Distribution {
1216 statistic: StatisticKind::Histogram,
1217 samples: samples!(7.0 => 4, 10.2 => 1),
1218 }),
1219 ];
1220
1221 let mut merged_summary = base_summary_metric.clone();
1223 assert!(merged_summary.update(&metrics[1]));
1224 assert!(merged_summary.update(&metrics[2]));
1225 let expected_summary = distribution_to_agg_histogram(merged_summary, &buckets)
1226 .expect("input summary metric should have been distribution")
1227 .into_absolute();
1228
1229 let mut merged_histogram = base_histogram_metric.clone();
1230 assert!(merged_histogram.update(&metrics[4]));
1231 assert!(merged_histogram.update(&metrics[5]));
1232 let expected_histogram = distribution_to_agg_histogram(merged_histogram, &buckets)
1233 .expect("input histogram metric should have been distribution")
1234 .into_absolute();
1235
1236 let metrics_handle = Arc::clone(&sink.metrics);
1241
1242 let events = metrics
1243 .iter()
1244 .cloned()
1245 .map(Event::Metric)
1246 .collect::<Vec<_>>();
1247
1248 let sink = VectorSink::from_event_streamsink(sink);
1249 let input_events = stream::iter(events).map(Into::into);
1250 sink.run(input_events).await.unwrap();
1251
1252 let metrics_after = metrics_handle.read().unwrap();
1253
1254 assert_eq!(metrics_after.len(), 2);
1256
1257 let actual_summary = metrics_after
1258 .get(&MetricRef::from_metric(&expected_summary))
1259 .expect("summary metric should exist");
1260 assert_eq!(actual_summary.0.value(), expected_summary.value());
1261
1262 let actual_histogram = metrics_after
1263 .get(&MetricRef::from_metric(&expected_histogram))
1264 .expect("histogram metric should exist");
1265 assert_eq!(actual_histogram.0.value(), expected_histogram.value());
1266 }
1267
1268 #[tokio::test]
1269 async fn sink_distributions_as_summaries() {
1270 let (_guard, address) = next_addr();
1284 let config = PrometheusExporterConfig {
1285 address,
1286 tls: None,
1287 distributions_as_summaries: true,
1288 ..Default::default()
1289 };
1290
1291 let sink = PrometheusExporter::new(config);
1292
1293 let base_summary_metric = Metric::new(
1295 "distrib_summary",
1296 MetricKind::Incremental,
1297 MetricValue::Distribution {
1298 statistic: StatisticKind::Summary,
1299 samples: samples!(1.0 => 1, 3.0 => 2),
1300 },
1301 );
1302
1303 let base_histogram_metric = Metric::new(
1304 "distrib_histo",
1305 MetricKind::Incremental,
1306 MetricValue::Distribution {
1307 statistic: StatisticKind::Histogram,
1308 samples: samples!(7.0 => 1, 9.0 => 2),
1309 },
1310 );
1311
1312 let metrics = [
1313 base_summary_metric.clone(),
1314 base_summary_metric
1315 .clone()
1316 .with_value(MetricValue::Distribution {
1317 statistic: StatisticKind::Summary,
1318 samples: samples!(1.0 => 2, 2.9 => 1),
1319 }),
1320 base_summary_metric
1321 .clone()
1322 .with_value(MetricValue::Distribution {
1323 statistic: StatisticKind::Summary,
1324 samples: samples!(1.0 => 4, 3.2 => 1),
1325 }),
1326 base_histogram_metric.clone(),
1327 base_histogram_metric
1328 .clone()
1329 .with_value(MetricValue::Distribution {
1330 statistic: StatisticKind::Histogram,
1331 samples: samples!(7.0 => 2, 9.9 => 1),
1332 }),
1333 base_histogram_metric
1334 .clone()
1335 .with_value(MetricValue::Distribution {
1336 statistic: StatisticKind::Histogram,
1337 samples: samples!(7.0 => 4, 10.2 => 1),
1338 }),
1339 ];
1340
1341 let mut merged_summary = base_summary_metric.clone();
1343 assert!(merged_summary.update(&metrics[1]));
1344 assert!(merged_summary.update(&metrics[2]));
1345 let expected_summary = distribution_to_ddsketch(merged_summary)
1346 .expect("input summary metric should have been distribution")
1347 .into_absolute();
1348
1349 let mut merged_histogram = base_histogram_metric.clone();
1350 assert!(merged_histogram.update(&metrics[4]));
1351 assert!(merged_histogram.update(&metrics[5]));
1352 let expected_histogram = distribution_to_ddsketch(merged_histogram)
1353 .expect("input histogram metric should have been distribution")
1354 .into_absolute();
1355
1356 let metrics_handle = Arc::clone(&sink.metrics);
1358
1359 let events = metrics
1360 .iter()
1361 .cloned()
1362 .map(Event::Metric)
1363 .collect::<Vec<_>>();
1364
1365 let sink = VectorSink::from_event_streamsink(sink);
1366 let input_events = stream::iter(events).map(Into::into);
1367 sink.run(input_events).await.unwrap();
1368
1369 let metrics_after = metrics_handle.read().unwrap();
1370
1371 assert_eq!(metrics_after.len(), 2);
1373
1374 let actual_summary = metrics_after
1375 .get(&MetricRef::from_metric(&expected_summary))
1376 .expect("summary metric should exist");
1377 assert_eq!(actual_summary.0.value(), expected_summary.value());
1378
1379 let actual_histogram = metrics_after
1380 .get(&MetricRef::from_metric(&expected_histogram))
1381 .expect("histogram metric should exist");
1382 assert_eq!(actual_histogram.0.value(), expected_histogram.value());
1383 }
1384
1385 #[tokio::test]
1386 async fn sink_gauge_incremental_absolute_mix() {
1387 let (_guard, address) = next_addr();
1394 let config = PrometheusExporterConfig {
1395 address,
1396 tls: None,
1397 ..Default::default()
1398 };
1399
1400 let sink = PrometheusExporter::new(config);
1401
1402 let base_absolute_gauge_metric = Metric::new(
1403 "gauge",
1404 MetricKind::Absolute,
1405 MetricValue::Gauge { value: 100.0 },
1406 );
1407
1408 let base_incremental_gauge_metric = Metric::new(
1409 "gauge",
1410 MetricKind::Incremental,
1411 MetricValue::Gauge { value: -10.0 },
1412 );
1413
1414 let metrics = [
1415 base_absolute_gauge_metric.clone(),
1416 base_absolute_gauge_metric
1417 .clone()
1418 .with_value(MetricValue::Gauge { value: 333.0 }),
1419 base_incremental_gauge_metric.clone(),
1420 base_incremental_gauge_metric
1421 .clone()
1422 .with_value(MetricValue::Gauge { value: 4.0 }),
1423 ];
1424
1425 let metrics_handle = Arc::clone(&sink.metrics);
1427
1428 let events = metrics
1429 .iter()
1430 .cloned()
1431 .map(Event::Metric)
1432 .collect::<Vec<_>>();
1433
1434 let sink = VectorSink::from_event_streamsink(sink);
1435 let input_events = stream::iter(events).map(Into::into);
1436 sink.run(input_events).await.unwrap();
1437
1438 let metrics_after = metrics_handle.read().unwrap();
1439
1440 assert_eq!(metrics_after.len(), 1);
1442
1443 let expected_gauge = Metric::new(
1444 "gauge",
1445 MetricKind::Absolute,
1446 MetricValue::Gauge { value: 327.0 },
1447 );
1448
1449 let actual_gauge = metrics_after
1450 .get(&MetricRef::from_metric(&expected_gauge))
1451 .expect("gauge metric should exist");
1452 assert_eq!(actual_gauge.0.value(), expected_gauge.value());
1453 }
1454}
1455
1456#[cfg(all(test, feature = "prometheus-integration-tests"))]
1457mod integration_tests {
1458 #![allow(clippy::print_stdout)] #![allow(clippy::print_stderr)] #![allow(clippy::dbg_macro)] use chrono::Utc;
1463 use futures::{future::ready, stream};
1464 use serde_json::Value;
1465 use tokio::{sync::mpsc, time};
1466 use tokio_stream::wrappers::UnboundedReceiverStream;
1467
1468 use super::*;
1469 use crate::{
1470 config::ProxyConfig,
1471 http::HttpClient,
1472 test_util::{
1473 components::{SINK_TAGS, run_and_assert_sink_compliance},
1474 trace_init,
1475 },
1476 };
1477
1478 fn sink_exporter_address() -> String {
1479 std::env::var("SINK_EXPORTER_ADDRESS").unwrap_or_else(|_| "127.0.0.1:9101".into())
1480 }
1481
1482 fn prometheus_address() -> String {
1483 std::env::var("PROMETHEUS_ADDRESS").unwrap_or_else(|_| "localhost:9090".into())
1484 }
1485
1486 async fn fetch_exporter_body() -> String {
1487 let url = format!("http://{}/metrics", sink_exporter_address());
1488 let request = Request::get(url)
1489 .body(Body::empty())
1490 .expect("Error creating request.");
1491 let proxy = ProxyConfig::default();
1492 let result = HttpClient::new(None, &proxy)
1493 .unwrap()
1494 .send(request)
1495 .await
1496 .expect("Could not send request");
1497 let result = http_body::Body::collect(result.into_body())
1498 .await
1499 .expect("Error fetching body")
1500 .to_bytes();
1501 String::from_utf8_lossy(&result).to_string()
1502 }
1503
1504 async fn prometheus_query(query: &str) -> Value {
1505 let url = format!(
1506 "http://{}/api/v1/query?query={}",
1507 prometheus_address(),
1508 query
1509 );
1510 let request = Request::post(url)
1511 .body(Body::empty())
1512 .expect("Error creating request.");
1513 let proxy = ProxyConfig::default();
1514 let result = HttpClient::new(None, &proxy)
1515 .unwrap()
1516 .send(request)
1517 .await
1518 .expect("Could not fetch query");
1519 let result = http_body::Body::collect(result.into_body())
1520 .await
1521 .expect("Error fetching body")
1522 .to_bytes();
1523 let result = String::from_utf8_lossy(&result);
1524 serde_json::from_str(result.as_ref()).expect("Invalid JSON from prometheus")
1525 }
1526
1527 #[tokio::test]
1528 async fn prometheus_metrics() {
1529 trace_init();
1530
1531 prometheus_scrapes_metrics().await;
1532 time::sleep(time::Duration::from_millis(500)).await;
1533 reset_on_flush_period().await;
1534 expire_on_flush_period().await;
1535 }
1536
1537 async fn prometheus_scrapes_metrics() {
1538 let start = Utc::now().timestamp();
1539
1540 let config = PrometheusExporterConfig {
1541 address: sink_exporter_address().parse().unwrap(),
1542 flush_period_secs: Duration::from_secs(2),
1543 ..Default::default()
1544 };
1545 let (sink, _) = config.build(SinkContext::default()).await.unwrap();
1546 let (name, event) = tests::create_metric_gauge(None, 123.4);
1547 let (_, delayed_event) = tests::create_metric_gauge(Some("delayed".to_string()), 123.4);
1548
1549 run_and_assert_sink_compliance(
1550 sink,
1551 stream::once(ready(event)).chain(stream::once(async move {
1552 time::sleep(time::Duration::from_secs(2)).await;
1554 delayed_event
1555 })),
1556 &SINK_TAGS,
1557 )
1558 .await;
1559
1560 let result = prometheus_query(&name).await;
1562
1563 let data = &result["data"]["result"][0];
1564 assert_eq!(data["metric"]["__name__"], Value::String(name));
1565 assert_eq!(
1566 data["metric"]["instance"],
1567 Value::String(sink_exporter_address())
1568 );
1569 assert_eq!(
1570 data["metric"]["some_tag"],
1571 Value::String("some_value".into())
1572 );
1573 assert!(data["value"][0].as_f64().unwrap() >= start as f64);
1574 assert_eq!(data["value"][1], Value::String("123.4".into()));
1575 }
1576
1577 async fn reset_on_flush_period() {
1578 let config = PrometheusExporterConfig {
1579 address: sink_exporter_address().parse().unwrap(),
1580 flush_period_secs: Duration::from_secs(3),
1581 ..Default::default()
1582 };
1583 let (sink, _) = config.build(SinkContext::default()).await.unwrap();
1584 let (tx, rx) = mpsc::unbounded_channel();
1585 let input_events = UnboundedReceiverStream::new(rx);
1586
1587 let input_events = input_events.map(Into::into);
1588 let sink_handle = tokio::spawn(async move { sink.run(input_events).await.unwrap() });
1589
1590 let (name1, event) = tests::create_metric_set(None, vec!["0", "1", "2"]);
1592 tx.send(event).expect("Failed to send.");
1593 let (name2, event) = tests::create_metric_set(None, vec!["3", "4", "5"]);
1594 tx.send(event).expect("Failed to send.");
1595
1596 time::sleep(time::Duration::from_secs(2)).await;
1599
1600 let result = prometheus_query(&name1).await;
1602 assert_eq!(
1603 result["data"]["result"][0]["value"][1],
1604 Value::String("3".into())
1605 );
1606 let result = prometheus_query(&name2).await;
1607 assert_eq!(
1608 result["data"]["result"][0]["value"][1],
1609 Value::String("3".into())
1610 );
1611
1612 time::sleep(time::Duration::from_secs(3)).await;
1616
1617 let (name2, event) = tests::create_metric_set(Some(name2), vec!["8", "9"]);
1618 tx.send(event).expect("Failed to send.");
1619
1620 time::sleep(time::Duration::from_secs(2)).await;
1622 let result = prometheus_query(&name1).await;
1623 assert_eq!(result["data"]["result"][0]["value"][1], Value::Null);
1624 let result = prometheus_query(&name2).await;
1625 assert_eq!(
1626 result["data"]["result"][0]["value"][1],
1627 Value::String("2".into())
1628 );
1629
1630 drop(tx);
1631 sink_handle.await.unwrap();
1632 }
1633
1634 async fn expire_on_flush_period() {
1635 let config = PrometheusExporterConfig {
1636 address: sink_exporter_address().parse().unwrap(),
1637 flush_period_secs: Duration::from_secs(3),
1638 ..Default::default()
1639 };
1640 let (sink, _) = config.build(SinkContext::default()).await.unwrap();
1641 let (tx, rx) = mpsc::unbounded_channel();
1642 let input_events = UnboundedReceiverStream::new(rx);
1643
1644 let input_events = input_events.map(Into::into);
1645 let sink_handle = tokio::spawn(async move { sink.run(input_events).await.unwrap() });
1646
1647 let (name1, event) = tests::create_metric_set(None, vec!["42"]);
1649 tx.send(event).expect("Failed to send.");
1650 let (name2, event) = tests::create_metric_gauge(None, 100.0);
1651 tx.send(event).expect("Failed to send.");
1652
1653 time::sleep(time::Duration::from_secs(1)).await;
1655
1656 let body = fetch_exporter_body().await;
1658 assert!(body.contains(&name1));
1659 assert!(body.contains(&name2));
1660
1661 for _ in 0..7 {
1663 let (_, event) = tests::create_metric_set(Some(name1.clone()), vec!["43"]);
1665 tx.send(event).expect("Failed to send.");
1666
1667 time::sleep(time::Duration::from_secs(1)).await;
1669 }
1670
1671 let body = fetch_exporter_body().await;
1673 assert!(body.contains(&name1));
1674 assert!(!body.contains(&name2));
1675
1676 drop(tx);
1677 sink_handle.await.unwrap();
1678 }
1679}