1use std::{
10 future::Future,
11 io, mem,
12 num::NonZeroUsize,
13 path::PathBuf,
14 pin::Pin,
15 sync::{Arc, LazyLock, Mutex as StdMutex},
16 time::{Duration, SystemTime, UNIX_EPOCH},
17};
18
19use async_trait::async_trait;
20use bytes::{Buf, BufMut, Bytes};
21use bytesize::ByteSize;
22use saluki_common::sync::shutdown::{ShutdownCoordinator, ShutdownHandle};
23use saluki_core::{
24 accounting::{MemoryBounds, MemoryBoundsBuilder, MemoryLimiter, UsageExpr},
25 components::{sources::*, BuildContext},
26 data_model::{
27 event::{
28 eventd::EventD,
29 metric::{Metric, MetricMetadata, MetricOrigin},
30 service_check::ServiceCheck,
31 Event, EventType,
32 },
33 tags::{RawTags, RawTagsFilter},
34 },
35 pooling::{ElasticObjectPool, ObjectPool as _},
36 runtime::{
37 self,
38 state::{AcquireError, ResourceLease},
39 InitializationError, ShutdownStrategy, Supervisable, SupervisorFuture,
40 },
41 topology::{EventsBuffer, OutputDefinition},
42};
43use saluki_env::{workload::CaptureEntityResolver, WorkloadProvider};
44use saluki_error::{generic_error, ErrorContext as _, GenericError};
45use saluki_io::{
46 buf::{BytesBuffer, ClearableIoBuffer as _, CollapsibleReadWriteIoBuffer as _, FixedSizeVec, ReadIoBuffer as _},
47 deser::{
48 codec::dogstatsd::*,
49 framing::{Framer as _, FramingError},
50 },
51 net::{
52 dns::{resolve_host, ResolveHostError},
53 listener::Listener,
54 ConnectionAddress, ListenAddress, ProcessCredentialsError, ProcessIdentity, SocketSpecification, Stream,
55 },
56};
57use snafu::Snafu;
58use stringtheory::MetaString;
59use tokio::{
60 pin, select,
61 sync::{mpsc, oneshot, Mutex},
62 time::{interval, MissedTickBehavior},
63};
64use tracing::{debug, error, info, trace, warn};
65
66mod forwarder;
67use self::forwarder::{PacketForwarder, PacketForwarderTarget};
68
69mod framer;
70use self::framer::{get_framer, DsdFramer};
71use crate::sources::dogstatsd::tags::{WellKnownTags, WellKnownTagsFilterPredicate};
72
73mod filters;
74use self::filters::EnablePayloadsFilter;
75
76mod metrics;
77use self::metrics::{build_metrics, Metrics};
78
79mod replay;
80use self::replay::{CaptureRecord, CapturedTaggerHandle, TrafficCapture};
81pub use self::replay::{
82 DogStatsDCaptureAPIHandler, DogStatsDCaptureControl, DogStatsDReplayAPIHandler, DogStatsDReplayControl,
83 ReplaySession, TimestampResolution, TrafficCaptureReader, DEFAULT_REPLAY_LOOPS, REPLAY_CREDENTIALS_GID,
84};
85
86mod origin;
87pub use self::origin::OriginEnrichmentConfiguration;
88use self::origin::{
89 origin_from_event_packet, origin_from_metric_packet, origin_from_service_check_packet, DogStatsDOriginTagResolver,
90 ProcessOrigin,
91};
92
93mod resolver;
94use self::resolver::ContextResolvers;
95
96mod tags;
97
98#[derive(Debug, Snafu)]
99#[snafu(context(suffix(false)))]
100enum Error {
101 #[snafu(display("Failed to acquire {} listener: {}", listener_type, source))]
102 FailedToAcquireListener {
103 listener_type: &'static str,
104 source: AcquireError,
105 },
106
107 #[snafu(display("No listeners could be created. Please specify a port (`dogstatsd_port`) or a socket path (`dogstatsd_socket` or `dogstatsd_stream_socket`) that can be listened on."))]
108 NoListeners,
109
110 #[snafu(context(false), display("Could not resolve bind_host: {}", source))]
111 UnresolvableBindHost { source: ResolveHostError },
112}
113
114const INTERNER_BASELINE_BYTES_PER_ENTRY: u64 = 512;
119const DOGSTATSD_LISTENER_WORKER_COUNT: usize = 1;
120const DOGSTATSD_PIPELINE_COUNT: usize = 1;
121const MIN_DOGSTATSD_WORKER_COUNT: usize = 2;
122
123fn default_decoder_worker_count(vcpus: usize) -> usize {
124 vcpus
125 .saturating_sub(DOGSTATSD_LISTENER_WORKER_COUNT + DOGSTATSD_PIPELINE_COUNT)
126 .max(MIN_DOGSTATSD_WORKER_COUNT)
127}
128
129pub struct EnablePayloadsConfiguration {
131 pub series: bool,
133
134 pub sketches: bool,
136
137 pub events: bool,
139
140 pub service_checks: bool,
142}
143
144const MIN_CAPTURE_DEPTH: usize = 1024;
145
146#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
150pub enum ListenerRequirement {
151 #[default]
155 All,
156
157 AtLeastOne,
162
163 Optional,
168}
169
170pub struct DogStatsDConfiguration {
174 pub default_hostname: MetaString,
176
177 pub buffer_size: usize,
181
182 pub buffer_count: usize,
188
189 pub buffer_count_max: usize,
200
201 pub workers_count: usize,
207
208 pub port: u16,
212
213 pub socket_receive_buffer_size: usize,
217
218 pub tcp_port: u16,
222
223 pub statsd_forward_host: Option<MetaString>,
228
229 pub statsd_forward_port: u16,
233
234 pub socket_path: Option<String>,
238
239 pub socket_stream_path: Option<String>,
243
244 pub stream_log_too_big: bool,
251
252 pub listener_requirement: ListenerRequirement,
263
264 pub pipe_name: Option<String>,
269
270 pub windows_pipe_security_descriptor: String,
274
275 pub disable_verbose_logs: bool,
281
282 pub eol_required: Vec<String>,
288
289 pub bind_host: Option<String>,
295
296 pub non_local_traffic: bool,
301
302 pub autoscale_udp_listeners: bool,
315
316 pub allow_context_heap_allocations: bool,
324
325 pub no_aggregation_pipeline_support: bool,
331
332 pub context_string_interner_entry_count: u64,
338
339 pub context_string_interner_size_bytes: Option<ByteSize>,
345
346 pub cached_contexts_limit: usize,
352
353 pub cached_tagsets_limit: usize,
359
360 pub context_expiry_seconds: u64,
364
365 pub permissive_decoding: bool,
370
371 pub minimum_sample_rate: f64,
379
380 pub enable_payloads: EnablePayloadsConfiguration,
382
383 pub origin_enrichment: OriginEnrichmentConfiguration,
385
386 pub origin_telemetry_enabled: bool,
391
392 pub workload_provider: Option<Arc<dyn WorkloadProvider + Send + Sync>>,
397
398 pub capture_entity_resolver: Option<Arc<dyn CaptureEntityResolver + Send + Sync>>,
404
405 pub additional_tags: Vec<String>,
409
410 pub capture_path: PathBuf,
414
415 pub capture_depth: usize,
421
422 pub capture_control: DogStatsDCaptureControl,
427
428 pub replay_control: DogStatsDReplayControl,
433}
434
435#[derive(Clone, Copy, Default)]
436struct EolRequired {
437 udp: bool,
438 uds: bool,
439 named_pipe: bool,
440}
441
442impl EolRequired {
443 fn from_config_values(values: &[String]) -> Self {
444 let mut eol_required = Self::default();
445
446 for value in values {
447 match value.as_str() {
448 "udp" => eol_required.udp = true,
449 "uds" => eol_required.uds = true,
450 "named_pipe" => eol_required.named_pipe = true,
451 _ => warn!(
452 value,
453 "Invalid dogstatsd_eol_required value. Expected 'udp', 'uds', or 'named_pipe'."
454 ),
455 }
456 }
457
458 eol_required
459 }
460
461 fn for_listener(&self, listen_addr: &ListenAddress) -> bool {
462 match listen_addr {
463 ListenAddress::Udp(_) => self.udp,
464 ListenAddress::Tcp(_) => false,
465 ListenAddress::Unixgram(_) | ListenAddress::Unix(_) => self.uds,
466 ListenAddress::NamedPipe { .. } => self.named_pipe,
467 }
468 }
469}
470
471#[cfg(test)]
472impl DogStatsDConfiguration {
473 fn for_test() -> Self {
479 Self {
480 default_hostname: MetaString::default(),
481 buffer_size: 8192,
482 buffer_count: 128,
483 buffer_count_max: 32_768,
484 workers_count: 0,
485 port: 8125,
486 socket_receive_buffer_size: 0,
487 tcp_port: 0,
488 statsd_forward_host: None,
489 statsd_forward_port: 0,
490 socket_path: None,
491 socket_stream_path: None,
492 stream_log_too_big: false,
493 listener_requirement: ListenerRequirement::default(),
494 pipe_name: None,
495 windows_pipe_security_descriptor: "D:AI(A;;GA;;;WD)".to_string(),
496 disable_verbose_logs: false,
497 eol_required: Vec::new(),
498 bind_host: None,
499 non_local_traffic: false,
500 autoscale_udp_listeners: false,
501 allow_context_heap_allocations: true,
502 no_aggregation_pipeline_support: true,
503 context_string_interner_entry_count: 4096,
504 context_string_interner_size_bytes: None,
505 cached_contexts_limit: 500_000,
506 cached_tagsets_limit: 500_000,
507 context_expiry_seconds: 20,
508 permissive_decoding: true,
509 minimum_sample_rate: 0.000000003845,
510 enable_payloads: EnablePayloadsConfiguration {
511 series: true,
512 sketches: true,
513 events: true,
514 service_checks: true,
515 },
516 origin_enrichment: OriginEnrichmentConfiguration::for_test(),
517 origin_telemetry_enabled: false,
518 workload_provider: None,
519 capture_entity_resolver: None,
520 additional_tags: Vec::new(),
521 capture_path: PathBuf::new(),
522 capture_depth: MIN_CAPTURE_DEPTH,
523 capture_control: DogStatsDCaptureControl::default(),
524 replay_control: DogStatsDReplayControl::default(),
525 }
526 }
527}
528
529impl DogStatsDConfiguration {
530 fn additional_tags(&self) -> Vec<String> {
532 let mut tags = self.additional_tags.clone();
533 tags.sort_unstable();
534 tags.dedup();
535 tags
536 }
537
538 fn effective_context_string_interner_bytes(&self) -> ByteSize {
543 match self.context_string_interner_size_bytes {
544 Some(explicit_bytes) => explicit_bytes,
545 None => {
546 saluki_antithesis::always_le!(
547 self.context_string_interner_entry_count,
548 u64::MAX / INTERNER_BASELINE_BYTES_PER_ENTRY,
549 "dogstatsd interner byte-size multiply does not overflow",
550 { "entry_count": self.context_string_interner_entry_count }
551 );
552 ByteSize::b(
553 self.context_string_interner_entry_count
554 .saturating_mul(INTERNER_BASELINE_BYTES_PER_ENTRY),
555 )
556 }
557 }
558 }
559
560 fn eol_required(&self) -> EolRequired {
561 EolRequired::from_config_values(&self.eol_required)
562 }
563
564 fn statsd_forward_target(&self) -> Option<(&MetaString, u16)> {
565 let host = self.statsd_forward_host.as_ref()?;
566 if self.statsd_forward_port == 0 {
567 return None;
568 }
569
570 Some((host, self.statsd_forward_port))
571 }
572
573 fn packet_forwarder_target(&self) -> Option<PacketForwarderTarget> {
574 let (host, port) = self.statsd_forward_target()?;
575 Some(PacketForwarderTarget::new(host.clone(), port))
576 }
577
578 fn udp_streams_to_yield(&self) -> Option<NonZeroUsize> {
584 if !self.autoscale_udp_listeners {
585 return None;
586 }
587
588 #[cfg(not(target_os = "linux"))]
589 if self.autoscale_udp_listeners {
590 warn!("UDP stream handler autoscaling not supported on non-Linux platforms. Default to single stream handler.");
591 return None;
592 }
593
594 let vcpus = std::thread::available_parallelism().map(NonZeroUsize::get).unwrap_or(1);
595 let streams = (1 + vcpus / 8).min(4);
596 NonZeroUsize::new(streams)
597 }
598
599 fn effective_max_buffer_count(&self) -> usize {
605 self.buffer_count_max.max(self.buffer_count)
606 }
607
608 fn decoder_worker_count(&self) -> NonZeroUsize {
609 let worker_count = if self.workers_count == 0 {
610 let vcpus = std::thread::available_parallelism().map(NonZeroUsize::get).unwrap_or(1);
611 default_decoder_worker_count(vcpus)
612 } else {
613 self.workers_count
614 };
615
616 NonZeroUsize::new(worker_count).expect("DogStatsD decoder worker count must be non-zero")
617 }
618
619 fn build_addresses(&self, bind_host: Option<std::net::IpAddr>) -> Vec<ListenAddress> {
629 let bind_ip: std::net::IpAddr = if self.non_local_traffic {
630 [0, 0, 0, 0].into()
631 } else {
632 bind_host.unwrap_or_else(|| [127, 0, 0, 1].into())
633 };
634
635 let mut addresses: Vec<ListenAddress> = Vec::new();
636
637 if self.port != 0 {
638 addresses.push(ListenAddress::Udp(std::net::SocketAddr::new(bind_ip, self.port)));
639 }
640
641 if self.tcp_port != 0 {
642 addresses.push(ListenAddress::Tcp(std::net::SocketAddr::new(bind_ip, self.tcp_port)));
643 }
644
645 if let Some(socket_path) = &self.socket_path {
646 addresses.push(ListenAddress::Unixgram(socket_path.into()));
647 }
648
649 if let Some(socket_stream_path) = &self.socket_stream_path {
650 addresses.push(ListenAddress::Unix(socket_stream_path.into()));
651 }
652
653 if let Some(pipe_name) = &self.pipe_name {
654 addresses.push(ListenAddress::named_pipe_with_input_buffer_size(
655 pipe_name,
656 &self.windows_pipe_security_descriptor,
657 self.buffer_size as u32,
658 ));
659 }
660
661 addresses
662 }
663
664 fn uds_origin_detection_unsupported_on_platform(&self, addresses: &[ListenAddress]) -> bool {
665 self.origin_enrichment.enabled
666 && cfg!(not(target_os = "linux"))
667 && addresses
668 .iter()
669 .any(|address| matches!(address, ListenAddress::Unixgram(_) | ListenAddress::Unix(_)))
670 }
671
672 fn warn_if_uds_origin_detection_unsupported(&self, addresses: &[ListenAddress]) {
673 if self.uds_origin_detection_unsupported_on_platform(addresses) {
674 warn!(
675 "DogStatsD UDS origin detection is enabled, but PID-based Unix socket credentials are unsupported on \
676 this platform. Metrics are accepted without PID-based origin enrichment."
677 );
678 }
679 }
680
681 async fn resolve_bind_ip(&self) -> Result<Option<std::net::IpAddr>, Error> {
686 match &self.bind_host {
687 Some(host) if !self.non_local_traffic => Ok(Some(resolve_host(host).await?)),
688 _ => Ok(None),
689 }
690 }
691
692 fn handle_listener_failure(&self, error: Error, listeners: &str) -> Result<(), Error> {
696 let is_key_collision = matches!(
699 error,
700 Error::FailedToAcquireListener {
701 source: AcquireError::TypeMismatch { .. },
702 ..
703 }
704 );
705 let fail = match self.listener_requirement {
706 ListenerRequirement::All => true,
707 ListenerRequirement::AtLeastOne | ListenerRequirement::Optional => false,
708 };
709 if is_key_collision || fail {
710 return Err(error);
711 }
712
713 error!(error = %error, "Can't create DogStatsD {}.", listeners);
714 Ok(())
715 }
716
717 async fn build_listeners(&self, context: &BuildContext) -> Result<Vec<ResourceLease<Listener>>, Error> {
725 let addresses = match self.resolve_bind_ip().await {
726 Ok(bind_ip) => self.build_addresses(bind_ip),
727 Err(e) => {
728 self.handle_listener_failure(e, "UDP and TCP listeners")?;
730 self.build_addresses(None)
731 .into_iter()
732 .filter(|address| !matches!(address, ListenAddress::Udp(_) | ListenAddress::Tcp(_)))
733 .collect()
734 }
735 };
736
737 self.warn_if_uds_origin_detection_unsupported(&addresses);
738
739 let mut listeners = Vec::new();
740 let socket_receive_buffer_size =
741 (self.socket_receive_buffer_size != 0).then_some(self.socket_receive_buffer_size);
742 let udp_streams_to_yield = self.udp_streams_to_yield();
743 for address in addresses {
744 let listener_type = address.listener_type();
745 let listener_streams = matches!(address, ListenAddress::Udp(_))
746 .then_some(udp_streams_to_yield)
747 .flatten();
748 let spec = SocketSpecification::new(address)
749 .with_udp_streams(listener_streams)
750 .with_receive_buffer_size(socket_receive_buffer_size);
751 match context.acquire_resource(spec).await {
752 Ok(listener) => listeners.push(listener),
753 Err(source) => self.handle_listener_failure(
754 Error::FailedToAcquireListener { listener_type, source },
755 &format!("{listener_type} listener"),
756 )?,
757 }
758 }
759
760 if listeners.is_empty() {
761 match self.listener_requirement {
762 ListenerRequirement::All | ListenerRequirement::AtLeastOne => return Err(Error::NoListeners),
763 ListenerRequirement::Optional => error!(error = %Error::NoListeners, "Could not start DogStatsD."),
765 }
766 }
767 Ok(listeners)
768 }
769}
770
771#[async_trait]
772impl SourceBuilder for DogStatsDConfiguration {
773 async fn build(&self, context: BuildContext) -> Result<Box<dyn Source + Send>, GenericError> {
774 let listeners = self.build_listeners(&context).await?;
775
776 let min_buffers: usize = listeners.iter().map(|listener| listener.min_buffer_reservation()).sum();
779 let max_buffers = self.effective_max_buffer_count();
780 if max_buffers == 0 {
783 return Err(generic_error!(
784 "The maximum I/O buffer count must be greater than zero."
785 ));
786 }
787 if max_buffers < min_buffers {
788 return Err(generic_error!(
789 "The maximum I/O buffer count ({}) must be at least {} to service all configured listeners.",
790 max_buffers,
791 min_buffers,
792 ));
793 }
794
795 let origin_detection_enabled = self.origin_enrichment.enabled;
796 let captured_tagger = CapturedTaggerHandle::new();
799
800 let maybe_origin_tags_resolver = self.workload_provider.clone().map(|provider| {
801 DogStatsDOriginTagResolver::new(self.origin_enrichment.clone(), provider, captured_tagger.clone())
802 });
803 let context_resolvers = ContextResolvers::new(self, context.component_context(), maybe_origin_tags_resolver)
804 .error_context("Failed to create context resolvers.")?;
805
806 let codec_config = DogStatsDCodecConfiguration::default()
807 .with_timestamps(self.no_aggregation_pipeline_support)
808 .with_permissive_mode(self.permissive_decoding)
809 .with_minimum_sample_rate(self.minimum_sample_rate)
810 .with_client_origin_detection(self.origin_enrichment.origin_detection_client);
811
812 let codec = DogStatsDCodec::from_configuration(codec_config);
813 let eol_required = self.eol_required();
814
815 let enable_payloads_filter = EnablePayloadsFilter::default()
816 .with_allow_series(self.enable_payloads.series)
817 .with_allow_sketches(self.enable_payloads.sketches)
818 .with_allow_events(self.enable_payloads.events)
819 .with_allow_service_checks(self.enable_payloads.service_checks);
820 let traffic_capture = TrafficCapture::with_workload_provider(
821 self.capture_path.clone(),
822 self.capture_depth,
823 self.workload_provider.clone(),
824 );
825 self.capture_control.bind(traffic_capture.clone());
826 let packet_forwarder_target = self.packet_forwarder_target();
827
828 self.replay_control.bind(captured_tagger);
829
830 let (io_buffer_pool, io_buffer_pool_shrinker) =
834 build_io_buffer_pool(self.buffer_count, max_buffers, self.buffer_size);
835 Ok(Box::new(DogStatsD {
836 listeners,
837 decoder_worker_count: self.decoder_worker_count(),
838 io_buffer_pool,
839 io_buffer_queue_capacity: max_buffers,
840 io_buffer_pool_shrinker: Box::pin(io_buffer_pool_shrinker),
841 codec,
842 context_resolvers,
843 default_hostname: self.default_hostname.clone(),
844 enabled_filter: enable_payloads_filter,
845 origin_detection_enabled,
846 origin_telemetry_enabled: self.origin_telemetry_enabled,
847 stream_log_too_big: self.stream_log_too_big,
848 disable_verbose_logs: self.disable_verbose_logs,
849 eol_required,
850 additional_tags: self.additional_tags().into(),
851 capture_entity_resolver: self.capture_entity_resolver.clone(),
852 traffic_capture,
853 packet_forwarder_target,
854 }))
855 }
856
857 fn outputs(&self) -> &[OutputDefinition<EventType>] {
858 static OUTPUTS: LazyLock<Vec<OutputDefinition<EventType>>> = LazyLock::new(|| {
859 vec![
860 OutputDefinition::named_output("metrics", EventType::Metric),
861 OutputDefinition::named_output("events", EventType::EventD),
862 OutputDefinition::named_output("service_checks", EventType::ServiceCheck),
863 ]
864 });
865 &OUTPUTS
866 }
867}
868
869impl MemoryBounds for DogStatsDConfiguration {
870 fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder) {
871 let additional_buffers = self.effective_max_buffer_count().saturating_sub(self.buffer_count);
872 let adjusted_buffer_size = get_adjusted_buffer_size(self.buffer_size);
873
874 builder
875 .minimum()
876 .with_single_value::<DogStatsD>("source struct")
878 .with_expr(UsageExpr::product(
880 "buffers",
881 UsageExpr::config("dogstatsd_buffer_count", self.buffer_count),
882 UsageExpr::config("dogstatsd_buffer_size", adjusted_buffer_size),
883 ))
884 .with_expr(UsageExpr::config(
887 "dogstatsd_string_interner_size_bytes",
888 self.effective_context_string_interner_bytes().as_u64() as usize,
889 ));
890
891 builder.firm().with_expr(UsageExpr::product(
893 "elastic buffers",
894 UsageExpr::constant("dogstatsd_buffer_count_max_extra", additional_buffers),
895 UsageExpr::config("dogstatsd_buffer_size", adjusted_buffer_size),
896 ));
897 }
898}
899
900pub struct DogStatsD {
902 listeners: Vec<ResourceLease<Listener>>,
903 decoder_worker_count: NonZeroUsize,
904 io_buffer_pool: ElasticObjectPool<BytesBuffer>,
905 io_buffer_queue_capacity: usize,
906 io_buffer_pool_shrinker: Pin<Box<dyn Future<Output = ()> + Send>>,
907 codec: DogStatsDCodec,
908 context_resolvers: ContextResolvers,
909 default_hostname: MetaString,
910 enabled_filter: EnablePayloadsFilter,
911 origin_detection_enabled: bool,
912 origin_telemetry_enabled: bool,
913 stream_log_too_big: bool,
914 disable_verbose_logs: bool,
915 eol_required: EolRequired,
916 additional_tags: Arc<[String]>,
917 capture_entity_resolver: Option<Arc<dyn CaptureEntityResolver + Send + Sync>>,
918 traffic_capture: TrafficCapture,
919 packet_forwarder_target: Option<PacketForwarderTarget>,
920}
921
922struct ListenerContext {
923 listener: ResourceLease<Listener>,
924 datagram_sender: mpsc::Sender<QueuedDatagram>,
925 io_buffer_pool: ElasticObjectPool<BytesBuffer>,
926 origin_telemetry_enabled: bool,
927 eol_required: EolRequired,
928 decoder_context: DecoderContext,
929 capture_entity_resolver: Option<Arc<dyn CaptureEntityResolver + Send + Sync>>,
930 packet_forwarder_target: Option<PacketForwarderTarget>,
931}
932
933#[derive(Clone)]
934struct HandlerContext {
935 listen_addr: ListenAddress,
936 eol_required: bool,
937 io_buffer_pool: ElasticObjectPool<BytesBuffer>,
938 datagram_sender: mpsc::Sender<QueuedDatagram>,
939 datagram_context: Option<Arc<DatagramSocketContext>>,
940 metrics: Metrics,
941 decoder_context: DecoderContext,
942 capture_entity_resolver: Option<Arc<dyn CaptureEntityResolver + Send + Sync>>,
943 packet_forwarder: Option<PacketForwarder>,
944}
945
946#[derive(Clone)]
947struct DecoderContext {
948 codec: DogStatsDCodec,
949 context_resolvers: ContextResolvers,
950 default_hostname: MetaString,
951 enabled_filter: EnablePayloadsFilter,
952 origin_detection_enabled: bool,
953 stream_log_too_big: bool,
954 disable_verbose_logs: bool,
955 additional_tags: Arc<[String]>,
956 traffic_capture: TrafficCapture,
957}
958
959struct DatagramSocketContext {
960 listen_addr: ListenAddress,
961 eol_required: bool,
962 metrics: Metrics,
963 packet_forwarder: Option<PacketForwarder>,
964}
965
966struct QueuedDatagram {
967 result: io::Result<ReceivedBuffer>,
968 socket_context: Arc<DatagramSocketContext>,
969}
970
971struct DogStatsDDecoder {
972 source_context: SourceContext,
973 codec: DogStatsDCodec,
974 context_resolvers: ContextResolvers,
975 default_hostname: MetaString,
976 enabled_filter: EnablePayloadsFilter,
977 origin_detection_enabled: bool,
978 stream_log_too_big: bool,
979 disable_verbose_logs: bool,
980 additional_tags: Arc<[String]>,
981 traffic_capture: TrafficCapture,
982 event_buffer: Option<EventsBuffer>,
983}
984
985#[derive(Clone, Copy)]
986enum BufferDecodeMode {
987 Connectionless,
988 Connected,
989}
990
991impl BufferDecodeMode {
992 fn is_eof(self, bytes_read: usize) -> bool {
993 match self {
994 Self::Connectionless => true,
995 Self::Connected => bytes_read == 0,
996 }
997 }
998
999 fn should_stop_on_eof(self, eof: bool) -> bool {
1000 matches!(self, Self::Connected) && eof
1001 }
1002
1003 fn should_stop_on_framing_error(self) -> bool {
1004 matches!(self, Self::Connected)
1005 }
1006}
1007
1008struct BufferDecodeContext<'a> {
1009 listen_addr: &'a ListenAddress,
1010 metrics: &'a Metrics,
1011 packet_forwarder: Option<&'a PacketForwarder>,
1012 mode: BufferDecodeMode,
1013 framer: DsdFramer,
1014 stream_capture: StreamCaptureState,
1015}
1016
1017impl<'a> BufferDecodeContext<'a> {
1018 fn new(
1019 listen_addr: &'a ListenAddress, eol_required: bool, metrics: &'a Metrics,
1020 packet_forwarder: Option<&'a PacketForwarder>, mode: BufferDecodeMode,
1021 ) -> Self {
1022 Self {
1023 listen_addr,
1024 metrics,
1025 packet_forwarder,
1026 mode,
1027 framer: get_framer(listen_addr, eol_required),
1028 stream_capture: StreamCaptureState::new(),
1029 }
1030 }
1031}
1032
1033#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1034enum DecodeOutcome {
1035 Continue,
1036 Stop,
1037}
1038
1039#[async_trait]
1040impl Source for DogStatsD {
1041 async fn run(mut self: Box<Self>, mut context: SourceContext) -> Result<(), GenericError> {
1042 let global_shutdown = context.take_shutdown_handle();
1043 pin!(global_shutdown);
1044
1045 let mut health = context.take_health_handle();
1046
1047 runtime::worker("io_buffer_pool_shrinker", self.io_buffer_pool_shrinker)
1053 .with_shutdown_strategy(ShutdownStrategy::Brutal)
1054 .spawn();
1055
1056 let (datagram_sender, datagram_receiver) = mpsc::channel(self.io_buffer_queue_capacity);
1057 let datagram_receiver = Arc::new(Mutex::new(datagram_receiver));
1058 let decoder_context = DecoderContext {
1059 codec: self.codec.clone(),
1060 context_resolvers: self.context_resolvers.clone(),
1061 default_hostname: self.default_hostname.clone(),
1062 enabled_filter: self.enabled_filter,
1063 origin_detection_enabled: self.origin_detection_enabled,
1064 stream_log_too_big: self.stream_log_too_big,
1065 disable_verbose_logs: self.disable_verbose_logs,
1066 additional_tags: self.additional_tags.clone(),
1067 traffic_capture: self.traffic_capture.clone(),
1068 };
1069
1070 for worker_id in 0..self.decoder_worker_count.get() {
1080 let datagram_receiver = datagram_receiver.clone();
1081 let decoder_source_context = context.clone();
1082 let decoder_context = decoder_context.clone();
1083
1084 runtime::worker(
1085 format!("datagram_decoder_{worker_id}"),
1086 process_datagram_decoder(datagram_receiver, decoder_source_context, decoder_context),
1087 )
1088 .with_shutdown_timeout(Duration::MAX)
1089 .spawn();
1090 }
1091 drop(datagram_receiver);
1092
1093 for listener in self.listeners {
1096 let task_name = format!("listener_{}", listener.listen_address().listener_type());
1097 let listener_source_context = context.clone();
1098
1099 let listener_context = ListenerContext {
1106 listener,
1107 datagram_sender: datagram_sender.clone(),
1108 io_buffer_pool: self.io_buffer_pool.clone(),
1109 origin_telemetry_enabled: self.origin_telemetry_enabled,
1110 eol_required: self.eol_required,
1111 decoder_context: decoder_context.clone(),
1112 capture_entity_resolver: self.capture_entity_resolver.clone(),
1113 packet_forwarder_target: self.packet_forwarder_target.clone(),
1114 };
1115
1116 runtime::supervisable(ListenerWorker::new(
1119 task_name,
1120 listener_source_context,
1121 listener_context,
1122 ))
1123 .needed()
1124 .spawn();
1125 }
1126 drop(datagram_sender);
1127
1128 health.mark_ready();
1129 debug!("DogStatsD source started.");
1130
1131 loop {
1136 select! {
1137 _ = &mut global_shutdown => {
1138 debug!("Received shutdown signal.");
1139 break
1140 },
1141 _ = health.live() => continue,
1142 }
1143 }
1144
1145 debug!("Stopping DogStatsD source...");
1148
1149 Ok(())
1150 }
1151}
1152
1153fn build_io_buffer_pool(
1154 min_buffers: usize, max_buffers: usize, buffer_size: usize,
1155) -> (ElasticObjectPool<BytesBuffer>, impl Future<Output = ()> + Send) {
1156 saluki_antithesis::always_le!(
1157 buffer_size,
1158 usize::MAX - 4,
1159 "dogstatsd buffer size add does not overflow",
1160 { "buffer_size": buffer_size }
1161 );
1162 let adjusted_buffer_size = get_adjusted_buffer_size(buffer_size);
1163 ElasticObjectPool::with_builder("dsd_packet_bufs", min_buffers, max_buffers, move || {
1164 FixedSizeVec::with_capacity(adjusted_buffer_size)
1165 })
1166}
1167
1168fn is_connectionless_listen_address(listen_addr: &ListenAddress) -> bool {
1169 match listen_addr {
1170 ListenAddress::Udp(_) => true,
1171 #[cfg(unix)]
1172 ListenAddress::Unixgram(_) => true,
1173 _ => false,
1174 }
1175}
1176
1177struct ListenerWorker {
1183 name: String,
1184 inner: StdMutex<Option<(SourceContext, ListenerContext)>>,
1185}
1186
1187impl ListenerWorker {
1188 fn new(name: String, source_context: SourceContext, listener_context: ListenerContext) -> Self {
1189 Self {
1190 name,
1191 inner: StdMutex::new(Some((source_context, listener_context))),
1192 }
1193 }
1194}
1195
1196#[async_trait]
1197impl Supervisable for ListenerWorker {
1198 fn name(&self) -> &str {
1199 &self.name
1200 }
1201
1202 fn shutdown_strategy(&self) -> ShutdownStrategy {
1203 ShutdownStrategy::Graceful(Duration::MAX)
1206 }
1207
1208 fn can_restart(&self) -> bool {
1209 false
1211 }
1212
1213 async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
1214 let (source_context, listener_context) = self
1215 .inner
1216 .lock()
1217 .expect("listener worker mutex poisoned")
1218 .take()
1219 .ok_or_else(|| InitializationError::from(generic_error!("listener worker already initialized")))?;
1220
1221 Ok(Box::pin(async move {
1222 process_listener(source_context, listener_context, process_shutdown).await;
1223 Ok(())
1224 }))
1225 }
1226}
1227
1228async fn process_listener(
1229 source_context: SourceContext, listener_context: ListenerContext, process_shutdown: ShutdownHandle,
1230) {
1231 let ListenerContext {
1232 mut listener,
1233 datagram_sender,
1234 io_buffer_pool,
1235 origin_telemetry_enabled,
1236 eol_required,
1237 decoder_context,
1238 capture_entity_resolver,
1239 packet_forwarder_target,
1240 } = listener_context;
1241
1242 pin!(process_shutdown);
1243
1244 let listen_addr = listener.listen_address().clone();
1245 let metrics = build_metrics(
1246 &listen_addr,
1247 source_context.component_context(),
1248 origin_telemetry_enabled,
1249 );
1250 let packet_forwarder = packet_forwarder_target
1251 .as_ref()
1252 .map(|target| target.to_forwarder(metrics.clone()));
1253 if let Some(packet_forwarder) = &packet_forwarder {
1254 packet_forwarder.spawn();
1255 }
1256 let datagram_context = is_connectionless_listen_address(&listen_addr).then(|| {
1257 Arc::new(DatagramSocketContext {
1258 listen_addr: listen_addr.clone(),
1259 eol_required: eol_required.for_listener(&listen_addr),
1260 metrics: metrics.clone(),
1261 packet_forwarder: packet_forwarder.clone(),
1262 })
1263 });
1264
1265 info!(%listen_addr, "DogStatsD listener started.");
1266
1267 loop {
1268 select! {
1269 _ = &mut process_shutdown => {
1272 debug!(%listen_addr, "Received shutdown signal. Waiting for existing stream handlers to finish...");
1273 break;
1274 }
1275 result = listener.accept() => match result {
1276 Ok(stream) => {
1277 debug!(%listen_addr, "Spawning new stream handler.");
1278
1279 let handler_context = HandlerContext {
1280 listen_addr: listen_addr.clone(),
1281 eol_required: eol_required.for_listener(&listen_addr),
1282 io_buffer_pool: io_buffer_pool.clone(),
1283 datagram_sender: datagram_sender.clone(),
1284 datagram_context: datagram_context.clone(),
1285 metrics: metrics.clone(),
1286 decoder_context: decoder_context.clone(),
1287 capture_entity_resolver: capture_entity_resolver.clone(),
1288 packet_forwarder: packet_forwarder.clone(),
1289 };
1290
1291 let task_name = format!("conn_{}", listen_addr.listener_type());
1292
1293 let handler_source_context = source_context.clone();
1297 runtime::worker_with_shutdown(task_name, move |shutdown| {
1298 process_stream(stream, handler_source_context, handler_context, shutdown)
1299 })
1300 .spawn();
1301 }
1302 Err(e) => {
1303 error!(%listen_addr, error = %e, "Failed to accept connection. Stopping listener.");
1304 break
1305 }
1306 }
1307 }
1308 }
1309
1310 info!(%listen_addr, "DogStatsD listener stopped accepting connections.");
1311}
1312
1313async fn process_stream(
1314 stream: Stream, source_context: SourceContext, handler_context: HandlerContext, shutdown_handle: ShutdownHandle,
1315) {
1316 select! {
1317 _ = shutdown_handle => {
1318 debug!("Stream handler received shutdown signal.");
1319 },
1320 _ = drive_stream(stream, source_context, handler_context) => {},
1321 }
1322}
1323
1324fn origin_detection_error_for_telemetry(
1325 origin_detection_enabled: bool, bytes_read: usize, peer_addr: &ConnectionAddress,
1326) -> Option<ProcessCredentialsError> {
1327 match peer_addr {
1328 ConnectionAddress::ProcessLike(identity @ ProcessIdentity::Error(error))
1329 if origin_detection_enabled && bytes_read > 0 && identity.is_telemetry_error() =>
1330 {
1331 Some(*error)
1332 }
1333 _ => None,
1334 }
1335}
1336
1337struct ReceivedBuffer {
1338 buffer: Option<BytesBuffer>,
1339 bytes_read: usize,
1340 peer_addr: ConnectionAddress,
1341 process_origin: Option<ProcessOrigin>,
1342 buffer_sender: Option<oneshot::Sender<BytesBuffer>>,
1343}
1344
1345impl ReceivedBuffer {
1346 fn with_return(
1347 buffer: BytesBuffer, bytes_read: usize, peer_addr: ConnectionAddress, process_origin: Option<ProcessOrigin>,
1348 ) -> (Self, oneshot::Receiver<BytesBuffer>) {
1349 let (buffer_sender, returned_buffer) = oneshot::channel();
1350 (
1351 Self {
1352 buffer: Some(buffer),
1353 bytes_read,
1354 peer_addr,
1355 process_origin,
1356 buffer_sender: Some(buffer_sender),
1357 },
1358 returned_buffer,
1359 )
1360 }
1361
1362 fn without_return(
1363 buffer: BytesBuffer, bytes_read: usize, peer_addr: ConnectionAddress, process_origin: Option<ProcessOrigin>,
1364 ) -> Self {
1365 Self {
1366 buffer: Some(buffer),
1367 bytes_read,
1368 peer_addr,
1369 process_origin,
1370 buffer_sender: None,
1371 }
1372 }
1373
1374 fn parts_mut(&mut self) -> (&mut BytesBuffer, usize, &ConnectionAddress, Option<&ProcessOrigin>) {
1375 let Self {
1376 buffer,
1377 bytes_read,
1378 peer_addr,
1379 process_origin,
1380 ..
1381 } = self;
1382 (
1383 buffer.as_mut().expect("Received buffer already taken."),
1384 *bytes_read,
1385 peer_addr,
1386 process_origin.as_ref(),
1387 )
1388 }
1389
1390 #[cfg(all(test, unix))]
1391 fn buffer(&self) -> &BytesBuffer {
1392 self.buffer.as_ref().expect("Received buffer already taken.")
1393 }
1394
1395 #[cfg(all(test, unix))]
1396 fn buffer_mut(&mut self) -> &mut BytesBuffer {
1397 self.buffer.as_mut().expect("Received buffer already taken.")
1398 }
1399}
1400
1401impl Drop for ReceivedBuffer {
1402 fn drop(&mut self) {
1403 if let Some(sender) = self.buffer_sender.take() {
1404 let buffer = self.buffer.take().expect("Received buffer already taken.");
1405 let _ = sender.send(buffer);
1406 }
1407 }
1408}
1409
1410struct BufferedStreamReader {
1411 receiver: Option<mpsc::Receiver<io::Result<ReceivedBuffer>>>,
1412
1413 _shutdown_coordinator: ShutdownCoordinator,
1415}
1416
1417impl BufferedStreamReader {
1418 fn new(
1419 stream: Stream, io_buffer_pool: ElasticObjectPool<BytesBuffer>, memory_limiter: MemoryLimiter,
1420 origin_detection_enabled: bool, traffic_capture: TrafficCapture,
1421 capture_entity_resolver: Option<Arc<dyn CaptureEntityResolver + Send + Sync>>,
1422 ) -> Self {
1423 debug_assert!(!stream.is_connectionless());
1424 let (packets_tx, receiver) = mpsc::channel(1);
1425 let (shutdown_coordinator, shutdown_handle) = ShutdownHandle::paired();
1426
1427 let reader = receive_connected_stream(
1428 stream,
1429 io_buffer_pool,
1430 memory_limiter,
1431 origin_detection_enabled,
1432 traffic_capture,
1433 capture_entity_resolver,
1434 packets_tx,
1435 );
1436
1437 runtime::worker("stream_reader", async move {
1438 select! {
1439 _ = shutdown_handle => debug!("Stream reader cancelled."),
1440 _ = reader => {},
1441 }
1442 })
1443 .spawn();
1444
1445 Self {
1446 receiver: Some(receiver),
1447 _shutdown_coordinator: shutdown_coordinator,
1448 }
1449 }
1450
1451 fn take_receiver(&mut self) -> mpsc::Receiver<io::Result<ReceivedBuffer>> {
1452 self.receiver.take().expect("Buffered stream receiver already taken")
1453 }
1454}
1455
1456async fn receive_connected_stream(
1457 mut stream: Stream, io_buffer_pool: ElasticObjectPool<BytesBuffer>, memory_limiter: MemoryLimiter,
1458 origin_detection_enabled: bool, traffic_capture: TrafficCapture,
1459 capture_entity_resolver: Option<Arc<dyn CaptureEntityResolver + Send + Sync>>,
1460 packets_tx: mpsc::Sender<io::Result<ReceivedBuffer>>,
1461) {
1462 debug!("Stream reader started.");
1463
1464 let mut retained_buffer: Option<BytesBuffer> = None;
1465 loop {
1466 memory_limiter.wait_for_capacity().await;
1467
1468 let mut buffer = match retained_buffer.take() {
1469 Some(mut buffer) => {
1470 buffer.collapse();
1471 buffer
1472 }
1473 None => acquire_io_buffer(&io_buffer_pool).await,
1474 };
1475 let (bytes_read, peer_addr) = match stream.receive(&mut buffer).await {
1476 Ok(received) => received,
1477 Err(error) => {
1478 let _ = packets_tx.send(Err(error)).await;
1479 break;
1480 }
1481 };
1482 let process_origin = resolve_process_origin_if_needed(
1483 origin_detection_enabled,
1484 &traffic_capture,
1485 capture_entity_resolver.as_deref(),
1486 &peer_addr,
1487 );
1488
1489 let (received, returned_buffer) = ReceivedBuffer::with_return(buffer, bytes_read, peer_addr, process_origin);
1490
1491 if packets_tx.send(Ok(received)).await.is_err() {
1492 debug!("Failed to enqueue DogStatsD packet for decoding: receiver dropped.");
1493 break;
1494 }
1495
1496 match returned_buffer.await {
1497 Ok(buffer) if buffer.has_remaining() => retained_buffer = Some(buffer),
1498 Ok(buffer) => drop(buffer),
1499 Err(_) => break,
1500 }
1501 }
1502
1503 debug!("Stream reader stopped.");
1504}
1505
1506async fn receive_connectionless_stream(
1507 mut stream: Stream, io_buffer_pool: ElasticObjectPool<BytesBuffer>, memory_limiter: MemoryLimiter,
1508 origin_detection_enabled: bool, traffic_capture: TrafficCapture,
1509 capture_entity_resolver: Option<Arc<dyn CaptureEntityResolver + Send + Sync>>,
1510 datagram_sender: mpsc::Sender<QueuedDatagram>, socket_context: Arc<DatagramSocketContext>,
1511) {
1512 debug!(listen_addr = %socket_context.listen_addr, "Datagram reader started.");
1513
1514 loop {
1515 memory_limiter.wait_for_capacity().await;
1516
1517 let mut buffer = acquire_io_buffer(&io_buffer_pool).await;
1518 let result = match stream.receive(&mut buffer).await {
1519 Ok((bytes_read, peer_addr)) => {
1520 let process_origin = resolve_process_origin_if_needed(
1521 origin_detection_enabled,
1522 &traffic_capture,
1523 capture_entity_resolver.as_deref(),
1524 &peer_addr,
1525 );
1526 Ok(ReceivedBuffer::without_return(
1527 buffer,
1528 bytes_read,
1529 peer_addr,
1530 process_origin,
1531 ))
1532 }
1533 Err(error) => Err(error),
1534 };
1535
1536 let receive_failed = result.is_err();
1537 let queued = QueuedDatagram {
1538 result,
1539 socket_context: socket_context.clone(),
1540 };
1541 if datagram_sender.send(queued).await.is_err() {
1542 debug!(
1543 listen_addr = %socket_context.listen_addr,
1544 "Failed to enqueue DogStatsD packet for decoding: receiver dropped."
1545 );
1546 break;
1547 }
1548 if receive_failed {
1549 continue;
1550 }
1551 }
1552
1553 debug!(listen_addr = %socket_context.listen_addr, "Datagram reader stopped.");
1554}
1555
1556async fn acquire_io_buffer(io_buffer_pool: &ElasticObjectPool<BytesBuffer>) -> BytesBuffer {
1557 let buffer = io_buffer_pool.acquire().await;
1558 trace!(
1559 remaining = buffer.remaining(),
1560 capacity = buffer.capacity(),
1561 "Acquired new buffer from pool."
1562 );
1563 buffer
1564}
1565
1566async fn process_datagram_decoder(
1567 datagram_receiver: Arc<Mutex<mpsc::Receiver<QueuedDatagram>>>, source_context: SourceContext,
1568 decoder_context: DecoderContext,
1569) {
1570 drive_datagram_decoder(datagram_receiver, source_context, decoder_context).await;
1571 debug!("Datagram decoder drained its queue.");
1572}
1573
1574impl DogStatsDDecoder {
1575 fn new(source_context: SourceContext, decoder_context: DecoderContext) -> Self {
1576 let DecoderContext {
1577 codec,
1578 context_resolvers,
1579 default_hostname,
1580 enabled_filter,
1581 origin_detection_enabled,
1582 stream_log_too_big,
1583 disable_verbose_logs,
1584 additional_tags,
1585 traffic_capture,
1586 } = decoder_context;
1587
1588 Self {
1589 source_context,
1590 codec,
1591 context_resolvers,
1592 default_hostname,
1593 enabled_filter,
1594 origin_detection_enabled,
1595 stream_log_too_big,
1596 disable_verbose_logs,
1597 additional_tags,
1598 traffic_capture,
1599 event_buffer: None,
1600 }
1601 }
1602
1603 async fn decode_buffer(
1604 &mut self, context: &mut BufferDecodeContext<'_>, mut received: ReceivedBuffer,
1605 ) -> DecodeOutcome {
1606 let (buffer, bytes_read, peer_addr, process_origin) = received.parts_mut();
1607 self.decode_buffer_contents(context, buffer, bytes_read, peer_addr, process_origin)
1608 .await
1609 }
1610
1611 async fn decode_buffer_contents(
1612 &mut self, context: &mut BufferDecodeContext<'_>, io_buffer: &mut BytesBuffer, bytes_read: usize,
1613 peer_addr: &ConnectionAddress, process_origin: Option<&ProcessOrigin>,
1614 ) -> DecodeOutcome {
1615 let listen_addr = context.listen_addr;
1616 let metrics = context.metrics;
1617 let packet_forwarder = context.packet_forwarder;
1618 let mode = context.mode;
1619
1620 let payload = received_payload(io_buffer, bytes_read);
1621 capture_unixgram_traffic(listen_addr, &self.traffic_capture, peer_addr, process_origin, payload);
1622
1623 let capture_stream_frames = matches!(listen_addr, ListenAddress::Unix(_)) && self.traffic_capture.is_ongoing();
1624 if capture_stream_frames {
1625 context.stream_capture.update_peer_metadata(peer_addr);
1626 }
1627 context.framer.set_retain_outer_frames(capture_stream_frames);
1628
1629 metrics.bytes_received().increment(bytes_read as u64);
1630 metrics.bytes_received_size().record(bytes_read as f64);
1631 let origin_detection_errors =
1632 origin_detection_error_for_telemetry(self.origin_detection_enabled, bytes_read, peer_addr)
1633 .and_then(|error| metrics.origin_detection_errors(error));
1634
1635 if matches!(mode, BufferDecodeMode::Connectionless) {
1636 metrics.packet_receive_success().increment(1);
1637 if let Some(origin_detection_errors) = origin_detection_errors {
1638 origin_detection_errors.increment(1);
1639 }
1640 }
1641
1642 let eof = mode.is_eof(bytes_read);
1643 trace!(
1644 buffer_len = io_buffer.remaining(),
1645 buffer_cap = io_buffer.remaining_mut(),
1646 %listen_addr,
1647 %peer_addr,
1648 eof,
1649 "Received {} bytes from socket.",
1650 bytes_read
1651 );
1652
1653 if should_drop_oversized_named_pipe_frame(listen_addr, io_buffer) {
1654 metrics.framing_errors().increment(1);
1655 debug!(%listen_addr, %peer_addr, "DogStatsD named pipe frame exceeded the configured buffer size. Dropping frame.");
1656 io_buffer.clear();
1657 return DecodeOutcome::Continue;
1658 }
1659
1660 loop {
1661 let frame_result = context.framer.next_frame(io_buffer, eof);
1662 for outer_frame in context.framer.drain_outer_frames() {
1663 let _ = self.traffic_capture.enqueue(build_capture_record(
1664 context.stream_capture.last_pid,
1665 process_origin,
1666 &outer_frame,
1667 ));
1668 }
1669 let completed_outer_frames = context.framer.take_completed_outer_frames();
1670 if completed_outer_frames > 0 {
1671 metrics
1672 .packet_receive_success()
1673 .increment(completed_outer_frames as u64);
1674 if let Some(origin_detection_errors) = origin_detection_errors {
1675 origin_detection_errors.increment(completed_outer_frames as u64);
1676 }
1677 }
1678
1679 match frame_result {
1680 Ok(Some(frame)) => {
1681 if matches!(listen_addr, ListenAddress::NamedPipe { .. }) {
1682 capture_named_pipe_frame(&self.traffic_capture, &frame);
1683 metrics.packet_receive_success().increment(1);
1684 }
1685 self.decode_frame(frame, listen_addr, peer_addr, process_origin, metrics, packet_forwarder)
1686 .await;
1687 }
1688 Ok(None) => {
1689 if mode.should_stop_on_eof(eof) {
1690 debug!(%listen_addr, %peer_addr, "Stream received EOF. Shutting down handler.");
1691 return DecodeOutcome::Stop;
1692 }
1693 return DecodeOutcome::Continue;
1694 }
1695 Err(error) => {
1696 metrics.framing_errors().increment(1);
1697 if should_warn_stream_log_too_big(listen_addr, &error, self.stream_log_too_big) {
1698 warn!(
1699 %listen_addr,
1700 %peer_addr,
1701 error = %error,
1702 "DogStatsD stream frame exceeded the configured buffer size."
1703 );
1704 }
1705
1706 if mode.should_stop_on_framing_error() {
1707 debug!(%listen_addr, %peer_addr, %error, "Error decoding frame. Stopping stream.");
1708 return DecodeOutcome::Stop;
1709 }
1710
1711 debug!(%listen_addr, %peer_addr, %error, "Error decoding datagram frame. Continuing listener.");
1712 return DecodeOutcome::Continue;
1713 }
1714 }
1715 }
1716 }
1717
1718 async fn decode_frame(
1719 &mut self, frame: Bytes, listen_addr: &ListenAddress, peer_addr: &ConnectionAddress,
1720 process_origin: Option<&ProcessOrigin>, metrics: &Metrics, packet_forwarder: Option<&PacketForwarder>,
1721 ) {
1722 trace!(%listen_addr, %peer_addr, ?frame, "Decoded frame.");
1723 if let Some(forwarder) = packet_forwarder {
1724 forwarder.forward(frame.clone()).await;
1725 }
1726
1727 match handle_frame(
1728 &frame[..],
1729 &self.codec,
1730 &mut self.context_resolvers,
1731 metrics,
1732 self.origin_detection_enabled,
1733 process_origin,
1734 self.enabled_filter,
1735 &self.additional_tags,
1736 &self.default_hostname,
1737 ) {
1738 Ok(Some(event)) => {
1739 if let Some(event_buffer) = self.buffer_event(event) {
1740 debug!(%listen_addr, %peer_addr, "Event buffer is full. Forwarding events.");
1741 dispatch_events(event_buffer, &self.source_context).await;
1742 }
1743 }
1744 Ok(None) => {}
1745 Err(error) => {
1746 log_parse_failure(self.disable_verbose_logs, listen_addr, peer_addr, &frame, &error);
1747 }
1748 }
1749 }
1750
1751 fn buffer_event(&mut self, event: Event) -> Option<EventsBuffer> {
1752 let event_buffer = self.event_buffer.get_or_insert_default();
1753 match event_buffer.try_push(event) {
1754 Some(event) => {
1755 let full_event_buffer = mem::take(event_buffer);
1756 assert!(
1757 event_buffer.try_push(event).is_none(),
1758 "New event buffer is unexpectedly full."
1759 );
1760 Some(full_event_buffer)
1761 }
1762 None => None,
1763 }
1764 }
1765
1766 async fn flush_events(&mut self) {
1767 if let Some(event_buffer) = self.event_buffer.take() {
1768 dispatch_events(event_buffer, &self.source_context).await;
1769 }
1770 }
1771}
1772
1773async fn drive_datagram_decoder(
1774 datagram_receiver: Arc<Mutex<mpsc::Receiver<QueuedDatagram>>>, source_context: SourceContext,
1775 decoder_context: DecoderContext,
1776) {
1777 let mut decoder = DogStatsDDecoder::new(source_context, decoder_context);
1778 let mut buffer_flush = interval(Duration::from_millis(100));
1779 buffer_flush.set_missed_tick_behavior(MissedTickBehavior::Delay);
1780
1781 loop {
1782 select! {
1783 maybe_datagram = async {
1784 datagram_receiver.lock().await.recv().await
1785 } => {
1786 let Some(QueuedDatagram { result, socket_context }) = maybe_datagram else {
1787 break;
1788 };
1789 {
1790 let DatagramSocketContext {
1791 listen_addr,
1792 eol_required,
1793 metrics,
1794 packet_forwarder,
1795 } = socket_context.as_ref();
1796
1797 let received = match result {
1798 Ok(received) => received,
1799 Err(error) => {
1800 metrics.packet_receive_failure().increment(1);
1801 warn!(%listen_addr, %error, "I/O error while reading datagram. Continuing listener.");
1802 continue;
1803 }
1804 };
1805
1806 let mut buffer_decode_context = BufferDecodeContext::new(
1807 listen_addr,
1808 *eol_required,
1809 metrics,
1810 packet_forwarder.as_ref(),
1811 BufferDecodeMode::Connectionless,
1812 );
1813 let outcome = decoder.decode_buffer(&mut buffer_decode_context, received).await;
1814 debug_assert_eq!(outcome, DecodeOutcome::Continue);
1815 }
1816 }
1817 _ = buffer_flush.tick() => {
1818 decoder.flush_events().await;
1819 }
1820 }
1821 }
1822
1823 decoder.flush_events().await;
1824}
1825
1826async fn drive_stream(stream: Stream, source_context: SourceContext, handler_context: HandlerContext) {
1827 if stream.is_connectionless() {
1828 let memory_limiter = source_context.topology_context().memory_limiter().clone();
1829 let socket_context = handler_context
1830 .datagram_context
1831 .clone()
1832 .expect("connectionless stream must have a datagram context");
1833 receive_connectionless_stream(
1834 stream,
1835 handler_context.io_buffer_pool,
1836 memory_limiter,
1837 handler_context.decoder_context.origin_detection_enabled,
1838 handler_context.decoder_context.traffic_capture.clone(),
1839 handler_context.capture_entity_resolver,
1840 handler_context.datagram_sender,
1841 socket_context,
1842 )
1843 .await;
1844 return;
1845 }
1846
1847 drive_connected_stream(stream, source_context, handler_context).await;
1848}
1849
1850async fn drive_connected_stream(stream: Stream, source_context: SourceContext, handler_context: HandlerContext) {
1851 let listen_addr = handler_context.listen_addr.clone();
1852 let metrics = handler_context.metrics.clone();
1853 let memory_limiter = source_context.topology_context().memory_limiter().clone();
1854 let mut stream_reader = BufferedStreamReader::new(
1855 stream,
1856 handler_context.io_buffer_pool.clone(),
1857 memory_limiter,
1858 handler_context.decoder_context.origin_detection_enabled,
1859 handler_context.decoder_context.traffic_capture.clone(),
1860 handler_context.capture_entity_resolver.clone(),
1861 );
1862 let receiver = stream_reader.take_receiver();
1863
1864 debug!(%listen_addr, "Stream handler started.");
1865
1866 metrics.connections_active().increment(1);
1867 drive_decoder(receiver, source_context, handler_context).await;
1868 metrics.connections_active().decrement(1);
1869
1870 debug!(%listen_addr, "Stream handler stopped.");
1871}
1872
1873async fn drive_decoder(
1874 mut stream_receiver: mpsc::Receiver<io::Result<ReceivedBuffer>>, source_context: SourceContext,
1875 handler_context: HandlerContext,
1876) {
1877 let HandlerContext {
1878 listen_addr,
1879 eol_required,
1880 metrics,
1881 decoder_context,
1882 packet_forwarder,
1883 ..
1884 } = handler_context;
1885 let mut decoder = DogStatsDDecoder::new(source_context, decoder_context);
1886 let mut buffer_flush = interval(Duration::from_millis(100));
1889 buffer_flush.set_missed_tick_behavior(MissedTickBehavior::Delay);
1890 let mut buffer_decode_context = BufferDecodeContext::new(
1891 &listen_addr,
1892 eol_required,
1893 &metrics,
1894 packet_forwarder.as_ref(),
1895 BufferDecodeMode::Connected,
1896 );
1897
1898 'read: loop {
1899 select! {
1900 maybe_read_result = stream_receiver.recv() => match maybe_read_result {
1902 Some(Ok(received)) => {
1903 let outcome = decoder.decode_buffer(&mut buffer_decode_context, received).await;
1904 if outcome == DecodeOutcome::Stop {
1905 break 'read;
1906 }
1907 },
1908 Some(Err(e)) => {
1909 metrics.packet_receive_failure().increment(1);
1910 warn!(%listen_addr, error = %e, "I/O error while decoding. Stopping stream.");
1911 break 'read;
1912 },
1913 None => {
1914 warn!(%listen_addr, "Buffered stream reader stopped unexpectedly. Stopping stream.");
1915 break 'read;
1916 }
1917 },
1918
1919 _ = buffer_flush.tick() => {
1920 decoder.flush_events().await;
1921 },
1922
1923 }
1924 }
1925
1926 decoder.flush_events().await;
1927}
1928
1929fn should_drop_oversized_named_pipe_frame(listen_addr: &ListenAddress, buffer: &BytesBuffer) -> bool {
1930 matches!(listen_addr, ListenAddress::NamedPipe { .. })
1931 && buffer.remaining_mut() == 0
1932 && memchr::memchr(b'\n', buffer.chunk()).is_none()
1933}
1934
1935fn should_warn_stream_log_too_big(listen_addr: &ListenAddress, error: &FramingError, stream_log_too_big: bool) -> bool {
1936 stream_log_too_big
1937 && matches!(listen_addr, ListenAddress::Unix(_))
1938 && matches!(error, FramingError::InvalidFrame { .. })
1939}
1940
1941fn log_parse_failure(
1942 disable_verbose_logs: bool, listen_addr: &ListenAddress, peer_addr: &ConnectionAddress, frame: &[u8],
1943 error: &ParseError,
1944) {
1945 let frame = String::from_utf8_lossy(frame);
1946 if disable_verbose_logs {
1947 debug!(%listen_addr, %peer_addr, %frame, %error, "Failed to parse frame.");
1948 } else {
1949 warn!(%listen_addr, %peer_addr, %frame, %error, "Failed to parse frame.");
1950 }
1951}
1952
1953fn capture_named_pipe_frame(traffic_capture: &TrafficCapture, frame: &[u8]) {
1954 if !frame.is_empty() && traffic_capture.is_ongoing() {
1955 let _ = traffic_capture.enqueue(build_capture_record(None, None, frame));
1956 }
1957}
1958
1959fn capture_unixgram_traffic(
1960 listen_addr: &ListenAddress, traffic_capture: &TrafficCapture, peer_addr: &ConnectionAddress,
1961 process_origin: Option<&ProcessOrigin>, payload: &[u8],
1962) {
1963 if payload.is_empty() || !matches!(listen_addr, ListenAddress::Unixgram(_)) || !traffic_capture.is_ongoing() {
1964 return;
1965 }
1966
1967 let _ = traffic_capture.enqueue(build_capture_record(
1968 process_id_from_peer_addr(peer_addr),
1969 process_origin,
1970 payload,
1971 ));
1972}
1973
1974struct StreamCaptureState {
1976 last_pid: Option<i32>,
1977}
1978
1979impl StreamCaptureState {
1980 fn new() -> Self {
1981 Self { last_pid: None }
1982 }
1983
1984 fn update_peer_metadata(&mut self, peer_addr: &ConnectionAddress) {
1985 if let Some(process_id) = process_id_from_peer_addr(peer_addr) {
1986 self.last_pid = Some(process_id);
1987 }
1988 }
1989}
1990
1991fn build_capture_record(
1992 process_id: Option<i32>, process_origin: Option<&ProcessOrigin>, payload: &[u8],
1993) -> CaptureRecord {
1994 CaptureRecord {
1995 timestamp_ns: capture_timestamp_ns(),
1996 payload: payload.to_vec(),
1997 pid: process_id,
1998 ancillary: Vec::new(),
1999 container_id: process_origin
2000 .and_then(ProcessOrigin::container_entity_id)
2001 .map(ToString::to_string),
2002 }
2003}
2004
2005fn process_id_from_peer_addr(peer_addr: &ConnectionAddress) -> Option<i32> {
2006 match peer_addr {
2007 ConnectionAddress::ProcessLike(ProcessIdentity::Credentials(creds)) => Some(creds.pid),
2008 _ => None,
2009 }
2010}
2011
2012fn resolve_process_origin(
2013 capture_entity_resolver: Option<&(dyn CaptureEntityResolver + Send + Sync)>, peer_addr: &ConnectionAddress,
2014) -> Option<ProcessOrigin> {
2015 let creds = peer_addr.process_credentials()?;
2016 if creds.gid == REPLAY_CREDENTIALS_GID {
2017 return Some(ProcessOrigin::Replay(creds.uid));
2018 }
2019
2020 let process_id = u32::try_from(creds.pid).ok()?;
2021 Some(match capture_entity_resolver {
2022 Some(resolver) => ProcessOrigin::Pinned(resolver.resolve_container_entity_for_live_pid(process_id)),
2023 None => ProcessOrigin::Unpinned(process_id),
2024 })
2025}
2026
2027fn resolve_process_origin_if_needed(
2028 origin_detection_enabled: bool, traffic_capture: &TrafficCapture,
2029 capture_entity_resolver: Option<&(dyn CaptureEntityResolver + Send + Sync)>, peer_addr: &ConnectionAddress,
2030) -> Option<ProcessOrigin> {
2031 if !origin_detection_enabled && !traffic_capture.is_ongoing() {
2032 return None;
2033 }
2034
2035 resolve_process_origin(capture_entity_resolver, peer_addr)
2036}
2037
2038fn received_payload(buffer: &BytesBuffer, bytes_read: usize) -> &[u8] {
2039 let chunk = buffer.chunk();
2040 let start = chunk.len().saturating_sub(bytes_read);
2041 &chunk[start..]
2042}
2043
2044fn capture_timestamp_ns() -> i64 {
2045 SystemTime::now()
2046 .duration_since(UNIX_EPOCH)
2047 .map(|duration| duration.as_nanos().min(i64::MAX as u128) as i64)
2048 .unwrap_or_default()
2049}
2050
2051#[allow(clippy::too_many_arguments)]
2052fn handle_frame(
2053 frame: &[u8], codec: &DogStatsDCodec, context_resolvers: &mut ContextResolvers, source_metrics: &Metrics,
2054 origin_detection_enabled: bool, process_origin: Option<&ProcessOrigin>, enabled_filter: EnablePayloadsFilter,
2055 additional_tags: &[String], default_hostname: &MetaString,
2056) -> Result<Option<Event>, ParseError> {
2057 let resolve_telemetry_origin = || {
2058 (source_metrics.origin_telemetry_enabled() && origin_detection_enabled)
2059 .then(|| {
2060 process_origin
2061 .and_then(ProcessOrigin::container_entity_id)
2062 .map(ToString::to_string)
2063 })
2064 .flatten()
2065 };
2066
2067 let parsed = match codec.decode_packet(frame) {
2068 Ok(parsed) => parsed,
2069 Err(e) => {
2070 match parse_message_type(frame) {
2072 MessageType::MetricSample => {
2073 source_metrics.record_metric_parse_failed(resolve_telemetry_origin().as_deref())
2074 }
2075 MessageType::Event => source_metrics.event_decode_failed().increment(1),
2076 MessageType::ServiceCheck => source_metrics.service_check_decode_failed().increment(1),
2077 }
2078
2079 return Err(e);
2080 }
2081 };
2082
2083 let event = match parsed {
2084 ParsedPacket::Metric(metric_packet) => {
2085 if metric_packet.num_points == 0 {
2086 return Ok(None);
2087 }
2088 let events_len = metric_packet.num_points;
2089 if !enabled_filter.allow_metric(&metric_packet) {
2090 trace!(
2091 metric.name = metric_packet.metric_name,
2092 "Skipping metric due to filter configuration."
2093 );
2094 return Ok(None);
2095 }
2096
2097 match handle_metric_packet(
2098 metric_packet,
2099 context_resolvers,
2100 process_origin,
2101 additional_tags,
2102 default_hostname,
2103 ) {
2104 Some(metric) => {
2105 source_metrics.record_metrics_received(events_len, resolve_telemetry_origin().as_deref());
2106 Event::Metric(metric)
2107 }
2108 None => {
2109 source_metrics.failed_context_resolve_total().increment(1);
2111 return Ok(None);
2112 }
2113 }
2114 }
2115 ParsedPacket::Event(event) => {
2116 if !enabled_filter.allow_event(&event) {
2117 trace!("Skipping event {} due to filter configuration.", event.title);
2118 return Ok(None);
2119 }
2120 match handle_event_packet(event, context_resolvers, process_origin, additional_tags) {
2121 Some(event) => {
2122 source_metrics.events_received().increment(1);
2123 Event::EventD(event)
2124 }
2125 None => {
2126 source_metrics.failed_context_resolve_total().increment(1);
2127 return Ok(None);
2128 }
2129 }
2130 }
2131 ParsedPacket::ServiceCheck(service_check) => {
2132 if !enabled_filter.allow_service_check(&service_check) {
2133 trace!(
2134 "Skipping service check {} due to filter configuration.",
2135 service_check.name
2136 );
2137 return Ok(None);
2138 }
2139 match handle_service_check_packet(service_check, context_resolvers, process_origin, additional_tags) {
2140 Some(service_check) => {
2141 source_metrics.service_checks_received().increment(1);
2142 Event::ServiceCheck(service_check)
2143 }
2144 None => {
2145 source_metrics.failed_context_resolve_total().increment(1);
2146 return Ok(None);
2147 }
2148 }
2149 }
2150 };
2151
2152 Ok(Some(event))
2153}
2154
2155fn handle_metric_packet(
2156 packet: MetricPacket, context_resolvers: &mut ContextResolvers, process_origin: Option<&ProcessOrigin>,
2157 additional_tags: &[String], default_hostname: &MetaString,
2158) -> Option<Metric> {
2159 let well_known_tags = WellKnownTags::from_raw_tags(&packet.tags);
2160
2161 let origin = origin_from_metric_packet(&packet, &well_known_tags);
2162 let origin_tags = context_resolvers.resolve_origin_tags(origin, process_origin);
2163
2164 let context_resolver = if packet.timestamp.is_some() {
2166 context_resolvers.no_agg()
2167 } else {
2168 context_resolvers.primary()
2169 };
2170
2171 let tags = get_filtered_tags_iterator(&packet.tags, additional_tags);
2172
2173 let hostname = well_known_tags.hostname.unwrap_or(default_hostname);
2174
2175 let maybe_context =
2177 context_resolver.resolve_with_host_and_origin_tags(packet.metric_name, hostname, tags, origin_tags);
2178
2179 match maybe_context {
2180 Some(context) => {
2181 let metric_origin = well_known_tags
2182 .jmx_check_name
2183 .map(MetricOrigin::jmx_check)
2184 .unwrap_or_else(MetricOrigin::dogstatsd);
2185 let metadata = MetricMetadata::default()
2186 .with_origin(metric_origin)
2187 .with_unit(packet.unit.map_or_else(MetaString::empty, MetaString::from_static));
2188
2189 Some(Metric::from_parts(context, packet.values, metadata))
2190 }
2191 None => None,
2193 }
2194}
2195
2196fn handle_event_packet(
2197 packet: EventPacket, context_resolvers: &mut ContextResolvers, process_origin: Option<&ProcessOrigin>,
2198 additional_tags: &[String],
2199) -> Option<EventD> {
2200 let well_known_tags = WellKnownTags::from_raw_tags(&packet.tags);
2201
2202 let origin = origin_from_event_packet(&packet, &well_known_tags);
2203 let origin_tags = context_resolvers.resolve_origin_tags(origin, process_origin);
2204
2205 let tags = get_filtered_tags_iterator(&packet.tags, additional_tags);
2206 let tags_resolver = context_resolvers.tags();
2207 let tags = tags_resolver.create_tag_set(tags)?;
2208
2209 let timestamp = packet
2213 .timestamp
2214 .or_else(|| SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs()));
2215
2216 let eventd = EventD::new(packet.title, packet.text)
2217 .with_timestamp(timestamp)
2218 .with_hostname(packet.hostname.map(|s| s.into()))
2219 .with_aggregation_key(packet.aggregation_key.map(|s| s.into()))
2220 .with_alert_type(packet.alert_type)
2221 .with_priority(packet.priority)
2222 .with_source_type_name(Some(
2227 packet
2228 .source_type_name
2229 .map(|s| s.into())
2230 .unwrap_or_else(|| "api".into()),
2231 ))
2232 .with_alert_type(packet.alert_type)
2233 .with_tags(tags)
2234 .with_origin_tags(origin_tags);
2235
2236 Some(eventd)
2237}
2238
2239fn handle_service_check_packet(
2240 packet: ServiceCheckPacket, context_resolvers: &mut ContextResolvers, process_origin: Option<&ProcessOrigin>,
2241 additional_tags: &[String],
2242) -> Option<ServiceCheck> {
2243 let well_known_tags = WellKnownTags::from_raw_tags(&packet.tags);
2244
2245 let origin = origin_from_service_check_packet(&packet, &well_known_tags);
2246 let origin_tags = context_resolvers.resolve_origin_tags(origin, process_origin);
2247
2248 let tags = get_filtered_tags_iterator(&packet.tags, additional_tags);
2249 let tags_resolver = context_resolvers.tags();
2250 let tags = tags_resolver.create_tag_set(tags)?;
2251
2252 let timestamp = packet
2256 .timestamp
2257 .or_else(|| SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs()));
2258
2259 let service_check = ServiceCheck::new(packet.name, packet.status)
2260 .with_timestamp(timestamp)
2261 .with_hostname(packet.hostname.map(|s| s.into()))
2262 .with_tags(tags)
2263 .with_origin_tags(origin_tags)
2264 .with_message(packet.message.map(|s| s.into()));
2265
2266 Some(service_check)
2267}
2268
2269fn get_filtered_tags_iterator<'a>(
2270 raw_tags: &'a RawTags<'_>, additional_tags: &'a [String],
2271) -> impl Iterator<Item = &'a str> + Clone {
2272 RawTagsFilter::exclude(raw_tags, WellKnownTagsFilterPredicate).chain(additional_tags.iter().map(|s| s.as_str()))
2275}
2276
2277async fn dispatch_events(mut event_buffer: EventsBuffer, source_context: &SourceContext) {
2278 debug!(events_len = event_buffer.len(), "Forwarding events.");
2279
2280 if event_buffer.has_event_type(EventType::EventD) {
2290 let eventd_events = event_buffer.extract(Event::is_eventd);
2291 let events_output = source_context.dispatcher().buffered_named("events");
2292
2293 if events_output.is_err() {
2296 saluki_antithesis::unreachable!("dsd 'events' output missing at dispatch");
2297 }
2298
2299 if let Err(e) = events_output
2300 .expect("events output should always exist")
2301 .send_all(eventd_events)
2302 .await
2303 {
2304 error!(error = %e, "Failed to dispatch eventd events.");
2305
2306 saluki_antithesis::unreachable!("dsd dispatch failed mid-buffer", { "stream": "events" });
2307 }
2308 }
2309
2310 if event_buffer.has_event_type(EventType::ServiceCheck) {
2312 let service_check_events = event_buffer.extract(Event::is_service_check);
2313 let service_checks_output = source_context.dispatcher().buffered_named("service_checks");
2314
2315 if service_checks_output.is_err() {
2316 saluki_antithesis::unreachable!("dsd 'service_checks' output missing at dispatch");
2317 }
2318
2319 if let Err(e) = service_checks_output
2320 .expect("service checks output should always exist")
2321 .send_all(service_check_events)
2322 .await
2323 {
2324 error!(error = %e, "Failed to dispatch service check events.");
2325
2326 saluki_antithesis::unreachable!("dsd dispatch failed mid-buffer", { "stream": "service_checks" });
2327 }
2328 }
2329
2330 if !event_buffer.is_empty() {
2332 if let Err(e) = source_context
2333 .dispatcher()
2334 .dispatch_named("metrics", event_buffer)
2335 .await
2336 {
2337 error!(error = %e, "Failed to dispatch metric events.");
2338
2339 saluki_antithesis::unreachable!("dsd dispatch failed mid-buffer", { "stream": "metrics" });
2340 }
2341 }
2342}
2343
2344const fn get_adjusted_buffer_size(buffer_size: usize) -> usize {
2345 buffer_size + 4
2360}
2361
2362#[cfg(test)]
2363mod tests {
2364 use std::{
2365 collections::HashMap,
2366 net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4},
2367 path::PathBuf,
2368 sync::{
2369 atomic::{AtomicUsize, Ordering},
2370 Arc, Mutex as StdMutex,
2371 },
2372 time::{Duration, Instant},
2373 };
2374
2375 use bytes::{Buf as _, BufMut as _};
2376 use bytesize::ByteSize;
2377 use metrics::{Key, Label};
2378 #[cfg(unix)]
2379 use saluki_core::accounting::MemoryLimiter;
2380 use saluki_core::{
2381 components::{
2382 sources::{SourceBuilder as _, SourceContext},
2383 test_util::{ContextParts, Outputs, TestComponentDriver},
2384 ComponentContext,
2385 },
2386 data_model::event::metric::context::{ContextResolverBuilder, TagsResolverBuilder},
2387 pooling::{helpers::get_pooled_object_via_builder, ObjectPool as _},
2388 topology::EventsBuffer,
2389 };
2390 #[cfg(target_os = "linux")]
2391 use saluki_env::workload::providers::TestWorkloadProvider;
2392 use saluki_env::workload::{CaptureEntityResolver, EntityId};
2393 #[cfg(unix)]
2394 use saluki_io::net::Stream;
2395 use saluki_io::{
2396 buf::{BytesBuffer, FixedSizeVec},
2397 deser::codec::dogstatsd::{DogStatsDCodec, DogStatsDCodecConfiguration, ParsedPacket},
2398 net::{ConnectionAddress, ListenAddress, ProcessCredentials, ProcessIdentity},
2399 };
2400 use saluki_metrics::test::TestRecorder;
2401 use stringtheory::MetaString;
2402 #[cfg(unix)]
2403 use tokio::{
2404 io::AsyncWriteExt as _,
2405 net::{UnixDatagram, UnixStream},
2406 };
2407 use tokio::{
2408 sync::{mpsc, Mutex},
2409 task::yield_now,
2410 time::timeout,
2411 };
2412
2413 use super::{
2414 build_io_buffer_pool, capture_named_pipe_frame, default_decoder_worker_count, filters::EnablePayloadsFilter,
2415 handle_frame, handle_metric_packet, metrics::build_metrics, origin_detection_error_for_telemetry,
2416 resolve_process_origin, resolve_process_origin_if_needed, BufferDecodeContext, BufferDecodeMode,
2417 ContextResolvers, DatagramSocketContext, DecodeOutcome, DecoderContext, DogStatsDConfiguration,
2418 DogStatsDDecoder, OriginEnrichmentConfiguration, ProcessOrigin, QueuedDatagram, ReceivedBuffer, TrafficCapture,
2419 TrafficCaptureReader,
2420 };
2421 #[cfg(unix)]
2422 use super::{receive_connected_stream, receive_connectionless_stream, received_payload};
2423 #[cfg(target_os = "linux")]
2424 use super::{DogStatsDOriginTagResolver, Listener};
2425 use crate::sources::dogstatsd::PacketForwarder;
2426
2427 const TEST_WINDOWS_PIPE_SECURITY_DESCRIPTOR: &str = "D:AI(A;;GA;;;WD)";
2428 const TEST_BUFFER_SIZE: usize = 8192;
2429
2430 fn test_component_context() -> ComponentContext {
2431 ComponentContext::test_source("dogstatsd_test")
2432 }
2433
2434 fn test_datagram_socket_context(listen_addr: ListenAddress) -> Arc<DatagramSocketContext> {
2435 Arc::new(DatagramSocketContext {
2436 metrics: build_metrics(&listen_addr, &test_component_context(), false),
2437 listen_addr,
2438 eol_required: false,
2439 packet_forwarder: None,
2440 })
2441 }
2442
2443 #[derive(Default)]
2444 struct CaptureTestEntityResolver {
2445 pid_map: StdMutex<HashMap<u32, EntityId>>,
2446 resolution_count: AtomicUsize,
2447 }
2448
2449 impl CaptureTestEntityResolver {
2450 fn with_pid_mapping(process_id: u32, entity_id: EntityId) -> Self {
2451 let mut pid_map = HashMap::new();
2452 pid_map.insert(process_id, entity_id);
2453 Self {
2454 pid_map: StdMutex::new(pid_map),
2455 resolution_count: AtomicUsize::new(0),
2456 }
2457 }
2458
2459 fn resolution_count(&self) -> usize {
2460 self.resolution_count.load(Ordering::Relaxed)
2461 }
2462
2463 #[cfg(target_os = "linux")]
2464 fn set_pid_mapping(&self, process_id: u32, entity_id: EntityId) {
2465 self.pid_map
2466 .lock()
2467 .expect("PID map lock should not be poisoned")
2468 .insert(process_id, entity_id);
2469 }
2470 }
2471
2472 impl CaptureEntityResolver for CaptureTestEntityResolver {
2473 fn resolve_container_entity_for_live_pid(&self, process_id: u32) -> Option<EntityId> {
2474 self.resolution_count.fetch_add(1, Ordering::Relaxed);
2475 self.pid_map
2476 .lock()
2477 .expect("PID map lock should not be poisoned")
2478 .get(&process_id)
2479 .cloned()
2480 }
2481 }
2482
2483 fn processed_metric_key(listener_type: &'static str, origin: Option<&str>) -> Key {
2484 let mut labels = vec![
2485 Label::from_static_parts("component_id", "dogstatsd_test"),
2486 Label::from_static_parts("component_type", "source"),
2487 Label::from_static_parts("listener_type", listener_type),
2488 Label::from_static_parts("message_type", "metrics"),
2489 ];
2490 if let Some(origin) = origin {
2491 labels.push(Label::new("origin", origin.to_string()));
2492 }
2493
2494 Key::from_parts("component_events_received_total", labels)
2495 }
2496
2497 fn test_context_resolvers() -> ContextResolvers {
2498 let tags_resolver = TagsResolverBuilder::for_tests().build();
2499 let context_resolver = ContextResolverBuilder::for_tests()
2500 .with_tags_resolver(Some(tags_resolver.clone()))
2501 .build();
2502 ContextResolvers::manual(context_resolver.clone(), context_resolver, tags_resolver)
2503 }
2504
2505 fn test_source_context() -> ContextParts<SourceContext, (), Outputs<EventsBuffer>> {
2511 TestComponentDriver::options()
2512 .with_component_id("dogstatsd_test")
2513 .source_context(DogStatsDConfiguration::for_test().outputs())
2514 .expect("the source's outputs should be valid")
2515 }
2516
2517 fn test_decoder_context(origin_detection_enabled: bool) -> DecoderContext {
2518 DecoderContext {
2519 codec: DogStatsDCodec::from_configuration(DogStatsDCodecConfiguration::default()),
2520 context_resolvers: test_context_resolvers(),
2521 default_hostname: MetaString::from_static("default-hostname"),
2522 enabled_filter: EnablePayloadsFilter::default(),
2523 origin_detection_enabled,
2524 stream_log_too_big: false,
2525 disable_verbose_logs: false,
2526 additional_tags: Vec::<String>::new().into(),
2527 traffic_capture: TrafficCapture::new(PathBuf::new(), 1),
2528 }
2529 }
2530
2531 fn test_io_buffer(payload: &[u8], capacity: usize) -> BytesBuffer {
2532 let mut buffer: BytesBuffer = get_pooled_object_via_builder(|| FixedSizeVec::with_capacity(capacity));
2533 buffer.put_slice(payload);
2534 buffer
2535 }
2536
2537 #[test]
2538 fn named_pipe_frames_are_written_to_an_active_capture() {
2539 let capture_directory = tempfile::tempdir().expect("temporary capture directory should be created");
2540 let capture = TrafficCapture::new(capture_directory.path().to_path_buf(), 1);
2541 let capture_path = capture
2542 .start_capture(None, Duration::from_secs(30), false)
2543 .expect("capture should start");
2544
2545 capture_named_pipe_frame(&capture, b"captured.named_pipe.one:1|c");
2546 capture_named_pipe_frame(&capture, b"captured.named_pipe.two:1|c");
2547 capture.stop_capture();
2548
2549 let deadline = Instant::now() + Duration::from_secs(2);
2550 while capture.is_ongoing() && Instant::now() < deadline {
2551 std::thread::sleep(Duration::from_millis(10));
2552 }
2553 assert!(!capture.is_ongoing(), "capture should stop after its sender is dropped");
2554
2555 let mut reader = TrafficCaptureReader::from_path(&capture_path).expect("capture should be readable");
2556 let first_record = reader
2557 .read_next()
2558 .expect("first capture record should decode")
2559 .expect("first named-pipe frame should be captured");
2560 let second_record = reader
2561 .read_next()
2562 .expect("second capture record should decode")
2563 .expect("second named-pipe frame should be captured");
2564 assert_eq!(first_record.payload, b"captured.named_pipe.one:1|c");
2565 assert_eq!(first_record.pid, 0);
2566 assert_eq!(second_record.payload, b"captured.named_pipe.two:1|c");
2567 assert_eq!(second_record.pid, 0);
2568 assert!(reader.read_next().expect("capture should terminate cleanly").is_none());
2569 }
2570
2571 #[tokio::test]
2572 async fn connectionless_decoder_dispatches_full_and_flushed_buffers_and_forwards_frames() {
2573 let recorder = TestRecorder::default();
2574 let _recorder_guard = metrics::set_default_local_recorder(&recorder);
2575 let listen_addr = ListenAddress::Unixgram("/tmp/dsd.sock".into());
2576 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Unavailable);
2577 let metrics = build_metrics(&listen_addr, &test_component_context(), false);
2578 let mut source = test_source_context();
2579 let mut metrics_rx = source.outputs.take_named_output("metrics");
2580 let mut decoder = DogStatsDDecoder::new(source.context, test_decoder_context(false));
2581 let event_buffer_capacity = EventsBuffer::default().capacity();
2582 let (packet_forwarder, mut packets_rx) = PacketForwarder::for_tests(event_buffer_capacity + 1);
2583 let mut buffer_decode_context = BufferDecodeContext::new(
2584 &listen_addr,
2585 false,
2586 &metrics,
2587 Some(&packet_forwarder),
2588 BufferDecodeMode::Connectionless,
2589 );
2590
2591 let mut payload = b"decoder.metric:1|c\n".repeat(event_buffer_capacity);
2592 payload.extend_from_slice(b"decoder.metric:1|c");
2593 let io_buffer = test_io_buffer(&payload, payload.len());
2594 let (received, returned_buffer) = ReceivedBuffer::with_return(io_buffer, payload.len(), peer_addr, None);
2595 let outcome = decoder.decode_buffer(&mut buffer_decode_context, received).await;
2596
2597 assert_eq!(outcome, DecodeOutcome::Continue);
2598 let io_buffer = returned_buffer
2599 .await
2600 .expect("connectionless decoder should return the I/O buffer");
2601 assert_eq!(io_buffer.remaining(), 0);
2602 let full_buffer = metrics_rx.next().await.expect("metrics output should remain connected");
2603 assert_eq!(full_buffer.len(), event_buffer_capacity);
2604 assert!(
2605 timeout(Duration::from_secs(1), packets_rx.recv())
2606 .await
2607 .expect("forwarded packet should not time out")
2608 .is_some(),
2609 "forwarded packet should be queued"
2610 );
2611 assert_eq!(packets_rx.len(), event_buffer_capacity);
2612
2613 decoder.flush_events().await;
2614 let flushed_buffer = metrics_rx.next().await.expect("metrics output should remain connected");
2615 assert_eq!(flushed_buffer.len(), 1);
2616 decoder.flush_events().await;
2617 assert!(
2618 metrics_rx.try_next().is_none(),
2619 "empty flush should not dispatch another buffer"
2620 );
2621
2622 assert_eq!(
2623 recorder.counter((
2624 "component_packets_received_total",
2625 &[
2626 ("component_id", "dogstatsd_test"),
2627 ("component_type", "source"),
2628 ("listener_type", "unixgram"),
2629 ("state", "ok"),
2630 ],
2631 )),
2632 Some(1)
2633 );
2634 assert_eq!(
2635 recorder.counter((
2636 "component_bytes_received_total",
2637 &[
2638 ("component_id", "dogstatsd_test"),
2639 ("component_type", "source"),
2640 ("listener_type", "unixgram"),
2641 ],
2642 )),
2643 Some(payload.len() as u64)
2644 );
2645 assert_eq!(
2646 recorder.counter(processed_metric_key("unixgram", None)),
2647 Some((event_buffer_capacity + 1) as u64)
2648 );
2649 }
2650
2651 #[tokio::test]
2652 async fn connected_decoder_waits_for_complete_outer_frame_and_stops_on_eof() {
2653 let recorder = TestRecorder::default();
2654 let _recorder_guard = metrics::set_default_local_recorder(&recorder);
2655 let listen_addr = ListenAddress::Unix("/tmp/dsd.socket".into());
2656 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Error(
2657 saluki_io::net::ProcessCredentialsError::InvalidCredentials,
2658 ));
2659 let metrics = build_metrics(&listen_addr, &test_component_context(), false);
2660 let mut source = test_source_context();
2661 let mut metrics_rx = source.outputs.take_named_output("metrics");
2662 let mut decoder = DogStatsDDecoder::new(source.context, test_decoder_context(true));
2663 let (packet_forwarder, mut packets_rx) = PacketForwarder::for_tests(1);
2664 let mut buffer_decode_context = BufferDecodeContext::new(
2665 &listen_addr,
2666 false,
2667 &metrics,
2668 Some(&packet_forwarder),
2669 BufferDecodeMode::Connected,
2670 );
2671
2672 let frame = b"stream.metric:1|c\n";
2673 let mut payload = Vec::with_capacity(frame.len() + 4);
2674 payload.extend_from_slice(&(frame.len() as u32).to_le_bytes());
2675 payload.extend_from_slice(frame);
2676 let io_buffer = test_io_buffer(&payload[..2], payload.len() + 16);
2677 let (received, returned_buffer) = ReceivedBuffer::with_return(io_buffer, 2, peer_addr.clone(), None);
2678
2679 let partial_outcome = decoder.decode_buffer(&mut buffer_decode_context, received).await;
2680 assert_eq!(partial_outcome, DecodeOutcome::Continue);
2681 let mut io_buffer = returned_buffer
2682 .await
2683 .expect("connected decoder should return the partial I/O buffer");
2684 assert_eq!(io_buffer.remaining(), 2);
2685 assert_eq!(
2686 recorder.counter((
2687 "component_packets_received_total",
2688 &[
2689 ("component_id", "dogstatsd_test"),
2690 ("component_type", "source"),
2691 ("listener_type", "unix"),
2692 ("state", "ok"),
2693 ],
2694 )),
2695 Some(0)
2696 );
2697 assert_eq!(
2698 recorder.counter((
2699 "component_errors_total",
2700 &[
2701 ("component_id", "dogstatsd_test"),
2702 ("component_type", "source"),
2703 ("error_type", "origin_detection"),
2704 ("reason", "invalid_credentials"),
2705 ],
2706 )),
2707 Some(0)
2708 );
2709
2710 io_buffer.put_slice(&payload[2..]);
2711 let (received, returned_buffer) =
2712 ReceivedBuffer::with_return(io_buffer, payload.len() - 2, peer_addr.clone(), None);
2713 let complete_outcome = decoder.decode_buffer(&mut buffer_decode_context, received).await;
2714 assert_eq!(complete_outcome, DecodeOutcome::Continue);
2715 let io_buffer = returned_buffer
2716 .await
2717 .expect("connected decoder should return the consumed I/O buffer");
2718 assert_eq!(io_buffer.remaining(), 0);
2719 assert!(
2720 timeout(Duration::from_secs(1), packets_rx.recv())
2721 .await
2722 .expect("forwarded stream packet should not time out")
2723 .is_some(),
2724 "complete outer frame should be forwarded"
2725 );
2726
2727 let (received, returned_buffer) = ReceivedBuffer::with_return(io_buffer, 0, peer_addr, None);
2728 let eof_outcome = decoder.decode_buffer(&mut buffer_decode_context, received).await;
2729 assert_eq!(eof_outcome, DecodeOutcome::Stop);
2730 let returned_buffer = returned_buffer
2731 .await
2732 .expect("stopped decoder should return the I/O buffer");
2733 assert!(!returned_buffer.has_remaining());
2734
2735 decoder.flush_events().await;
2736 let flushed_buffer = metrics_rx.next().await.expect("metrics output should remain connected");
2737 assert_eq!(flushed_buffer.len(), 1);
2738 assert_eq!(
2739 recorder.counter((
2740 "component_packets_received_total",
2741 &[
2742 ("component_id", "dogstatsd_test"),
2743 ("component_type", "source"),
2744 ("listener_type", "unix"),
2745 ("state", "ok"),
2746 ],
2747 )),
2748 Some(1)
2749 );
2750 assert_eq!(
2751 recorder.counter((
2752 "component_bytes_received_total",
2753 &[
2754 ("component_id", "dogstatsd_test"),
2755 ("component_type", "source"),
2756 ("listener_type", "unix"),
2757 ],
2758 )),
2759 Some(payload.len() as u64)
2760 );
2761 assert_eq!(
2762 recorder.counter((
2763 "component_errors_total",
2764 &[
2765 ("component_id", "dogstatsd_test"),
2766 ("component_type", "source"),
2767 ("error_type", "origin_detection"),
2768 ("reason", "invalid_credentials"),
2769 ],
2770 )),
2771 Some(1)
2772 );
2773 assert_eq!(recorder.counter(processed_metric_key("unix", None)), Some(1));
2774 }
2775
2776 fn length_delimited(payload: &[u8]) -> Vec<u8> {
2777 let mut frame = (payload.len() as u32).to_le_bytes().to_vec();
2778 frame.extend_from_slice(payload);
2779 frame
2780 }
2781
2782 async fn feed_connected_stream(
2785 decoder: &mut DogStatsDDecoder, context: &mut BufferDecodeContext<'_>, mut io_buffer: BytesBuffer,
2786 mut data: &[u8], peer_addr: &ConnectionAddress,
2787 ) -> BytesBuffer {
2788 use saluki_io::buf::CollapsibleReadWriteIoBuffer as _;
2789
2790 while !data.is_empty() {
2791 io_buffer.collapse();
2792 let bytes_read = io_buffer.remaining_mut().min(data.len());
2793 io_buffer.put_slice(&data[..bytes_read]);
2794 data = &data[bytes_read..];
2795
2796 let (received, returned_buffer) =
2797 ReceivedBuffer::with_return(io_buffer, bytes_read, peer_addr.clone(), None);
2798 let outcome = decoder.decode_buffer(context, received).await;
2799 assert_eq!(outcome, DecodeOutcome::Continue);
2800 io_buffer = returned_buffer.await.expect("decoder should return the I/O buffer");
2801 }
2802 io_buffer
2803 }
2804
2805 async fn finish_capture(capture: &TrafficCapture, capture_path: &std::path::Path) -> Vec<(Vec<u8>, i32)> {
2806 capture.stop_capture();
2807 timeout(Duration::from_secs(2), async {
2808 while capture.is_ongoing() {
2809 yield_now().await;
2810 }
2811 })
2812 .await
2813 .expect("capture should stop");
2814
2815 let mut reader = TrafficCaptureReader::from_path(capture_path).expect("capture should be readable");
2816 let mut records = Vec::new();
2817 while let Some(record) = reader.read_next().expect("capture record should decode") {
2818 records.push((record.payload, record.pid));
2819 }
2820 records
2821 }
2822
2823 fn stream_test_payloads(count: usize) -> Vec<Vec<u8>> {
2824 (0..count).map(|_| b"capture.stream.metric:1|c\n".repeat(57)).collect()
2826 }
2827
2828 #[tokio::test]
2829 async fn uds_stream_capture_records_every_outer_frame_across_reads() {
2830 let listen_addr = ListenAddress::Unix("/tmp/dsd.socket".into());
2831 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Credentials(ProcessCredentials {
2832 pid: 42,
2833 uid: 0,
2834 gid: 0,
2835 }));
2836 let capture_directory = tempfile::tempdir().expect("temporary capture directory should be created");
2837 let capture = TrafficCapture::new(capture_directory.path().to_path_buf(), 1);
2838 let metrics = build_metrics(&listen_addr, &test_component_context(), false);
2839 let source = test_source_context();
2841 let decoder_context = DecoderContext {
2842 traffic_capture: capture.clone(),
2843 ..test_decoder_context(false)
2844 };
2845 let mut decoder = DogStatsDDecoder::new(source.context, decoder_context);
2846 let mut context = BufferDecodeContext::new(&listen_addr, false, &metrics, None, BufferDecodeMode::Connected);
2847
2848 let capture_path = capture
2849 .start_capture(None, Duration::from_secs(30), false)
2850 .expect("capture should start");
2851 let payloads = stream_test_payloads(40);
2852 let stream = payloads
2853 .iter()
2854 .flat_map(|payload| length_delimited(payload))
2855 .collect::<Vec<_>>();
2856 let io_buffer = test_io_buffer(&[], TEST_BUFFER_SIZE);
2857 let io_buffer = feed_connected_stream(&mut decoder, &mut context, io_buffer, &stream, &peer_addr).await;
2858 assert_eq!(io_buffer.remaining(), 0);
2859
2860 let records = finish_capture(&capture, &capture_path).await;
2861 assert_eq!(records.len(), payloads.len());
2862 for ((payload, pid), expected) in records.iter().zip(&payloads) {
2863 assert_eq!(payload, expected);
2864 assert_eq!(*pid, 42);
2865 }
2866 }
2867
2868 #[tokio::test]
2869 async fn uds_stream_capture_starting_mid_frame_captures_from_next_whole_frame_and_stops_cleanly() {
2870 let listen_addr = ListenAddress::Unix("/tmp/dsd.socket".into());
2871 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Unavailable);
2872 let capture_directory = tempfile::tempdir().expect("temporary capture directory should be created");
2873 let capture = TrafficCapture::new(capture_directory.path().to_path_buf(), 1);
2874 let metrics = build_metrics(&listen_addr, &test_component_context(), false);
2875 let source = test_source_context();
2877 let decoder_context = DecoderContext {
2878 traffic_capture: capture.clone(),
2879 ..test_decoder_context(false)
2880 };
2881 let mut decoder = DogStatsDDecoder::new(source.context, decoder_context);
2882 let mut context = BufferDecodeContext::new(&listen_addr, false, &metrics, None, BufferDecodeMode::Connected);
2883
2884 let payloads = stream_test_payloads(20);
2885 let stream = payloads
2886 .iter()
2887 .flat_map(|payload| length_delimited(payload))
2888 .collect::<Vec<_>>();
2889 let frame_len = stream.len() / payloads.len();
2890
2891 let split = frame_len + frame_len / 2;
2893 let io_buffer = test_io_buffer(&[], TEST_BUFFER_SIZE);
2894 let io_buffer =
2895 feed_connected_stream(&mut decoder, &mut context, io_buffer, &stream[..split], &peer_addr).await;
2896 assert_eq!(io_buffer.remaining(), frame_len / 2);
2897
2898 let capture_path = capture
2899 .start_capture(None, Duration::from_secs(30), false)
2900 .expect("capture should start");
2901 let io_buffer =
2902 feed_connected_stream(&mut decoder, &mut context, io_buffer, &stream[split..], &peer_addr).await;
2903 let records = finish_capture(&capture, &capture_path).await;
2904
2905 assert_eq!(records.len(), payloads.len() - 1);
2906 for ((payload, _), expected) in records.iter().zip(&payloads[1..]) {
2907 assert_eq!(payload, expected);
2908 }
2909
2910 let more = length_delimited(&payloads[0]);
2912 let io_buffer = feed_connected_stream(&mut decoder, &mut context, io_buffer, &more, &peer_addr).await;
2913 assert_eq!(io_buffer.remaining(), 0);
2914 assert_eq!(context.framer.drain_outer_frames().count(), 0);
2915 }
2916
2917 #[tokio::test]
2918 async fn connectionless_decoder_counts_origin_detection_errors_by_reason() {
2919 let recorder = TestRecorder::default();
2920 let _recorder_guard = metrics::set_default_local_recorder(&recorder);
2921 let listen_addr = ListenAddress::Unixgram("/tmp/dsd.sock".into());
2922 let metrics = build_metrics(&listen_addr, &test_component_context(), false);
2923 let source = test_source_context();
2924 let mut decoder = DogStatsDDecoder::new(source.context, test_decoder_context(true));
2925 let mut buffer_decode_context =
2926 BufferDecodeContext::new(&listen_addr, false, &metrics, None, BufferDecodeMode::Connectionless);
2927
2928 let payload = b"decoder.metric:1|c\ndecoder.metric:2|c";
2931 for (error, datagrams) in [
2932 (saluki_io::net::ProcessCredentialsError::ZeroPid, 2),
2933 (saluki_io::net::ProcessCredentialsError::InvalidCredentials, 1),
2934 ] {
2935 for _ in 0..datagrams {
2936 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Error(error));
2937 let io_buffer = test_io_buffer(payload, payload.len());
2938 let (received, _returned_buffer) =
2939 ReceivedBuffer::with_return(io_buffer, payload.len(), peer_addr, None);
2940 let outcome = decoder.decode_buffer(&mut buffer_decode_context, received).await;
2941 assert_eq!(outcome, DecodeOutcome::Continue);
2942 }
2943 }
2944
2945 for (reason, expected) in [("zero_pid", 2), ("invalid_credentials", 1)] {
2946 assert_eq!(
2947 recorder.counter((
2948 "component_errors_total",
2949 &[
2950 ("component_id", "dogstatsd_test"),
2951 ("component_type", "source"),
2952 ("error_type", "origin_detection"),
2953 ("reason", reason),
2954 ],
2955 )),
2956 Some(expected),
2957 "unexpected origin detection error count for reason `{reason}`"
2958 );
2959 }
2960 }
2961
2962 #[tokio::test]
2963 async fn decoder_continues_connectionless_but_stops_connected_on_framing_error() {
2964 let recorder = TestRecorder::default();
2965 let _recorder_guard = metrics::set_default_local_recorder(&recorder);
2966 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Unavailable);
2967 let source = test_source_context();
2968 let mut decoder = DogStatsDDecoder::new(source.context, test_decoder_context(false));
2969
2970 let datagram_addr = ListenAddress::Unixgram("/tmp/dsd.sock".into());
2971 let datagram_metrics = build_metrics(&datagram_addr, &test_component_context(), false);
2972 let payload = b"missing.newline:1|c";
2973 let datagram_buffer = test_io_buffer(payload, payload.len());
2974 let mut datagram_context = BufferDecodeContext::new(
2975 &datagram_addr,
2976 true,
2977 &datagram_metrics,
2978 None,
2979 BufferDecodeMode::Connectionless,
2980 );
2981 let (received, _returned_datagram_buffer) =
2982 ReceivedBuffer::with_return(datagram_buffer, payload.len(), peer_addr.clone(), None);
2983 let datagram_outcome = decoder.decode_buffer(&mut datagram_context, received).await;
2984 assert_eq!(datagram_outcome, DecodeOutcome::Continue);
2985
2986 let stream_addr = ListenAddress::Unix("/tmp/dsd.socket".into());
2987 let stream_metrics = build_metrics(&stream_addr, &test_component_context(), false);
2988 let stream_buffer_capacity = 64;
2989 let oversized_frame = (stream_buffer_capacity as u32).to_le_bytes();
2990 let stream_buffer = test_io_buffer(&oversized_frame, stream_buffer_capacity);
2991 let mut stream_context =
2992 BufferDecodeContext::new(&stream_addr, false, &stream_metrics, None, BufferDecodeMode::Connected);
2993 let (received, _returned_stream_buffer) =
2994 ReceivedBuffer::with_return(stream_buffer, oversized_frame.len(), peer_addr, None);
2995 let stream_outcome = decoder.decode_buffer(&mut stream_context, received).await;
2996 assert_eq!(stream_outcome, DecodeOutcome::Stop);
2997
2998 for listener_type in ["unixgram", "unix"] {
2999 assert_eq!(
3000 recorder.counter((
3001 "component_errors_total",
3002 &[
3003 ("component_id", "dogstatsd_test"),
3004 ("component_type", "source"),
3005 ("listener_type", listener_type),
3006 ("error_type", "framing"),
3007 ],
3008 )),
3009 Some(1)
3010 );
3011 }
3012 assert_eq!(
3013 recorder.counter((
3014 "component_packets_received_total",
3015 &[
3016 ("component_id", "dogstatsd_test"),
3017 ("component_type", "source"),
3018 ("listener_type", "unixgram"),
3019 ("state", "ok"),
3020 ],
3021 )),
3022 Some(1)
3023 );
3024 assert_eq!(
3025 recorder.counter((
3026 "component_packets_received_total",
3027 &[
3028 ("component_id", "dogstatsd_test"),
3029 ("component_type", "source"),
3030 ("listener_type", "unix"),
3031 ("state", "ok"),
3032 ],
3033 )),
3034 Some(0)
3035 );
3036 }
3037
3038 #[test]
3039 fn origin_telemetry_does_not_resolve_origin_when_origin_detection_is_disabled() {
3040 let recorder = TestRecorder::default();
3041 let _recorder_guard = metrics::set_default_local_recorder(&recorder);
3042 let listen_addr = ListenAddress::Unixgram("/tmp/dsd.sock".into());
3043 let context = test_component_context();
3044 let metrics = build_metrics(&listen_addr, &context, true);
3045 let codec = DogStatsDCodec::from_configuration(DogStatsDCodecConfiguration::default());
3046 let mut context_resolvers = test_context_resolvers();
3047 let capture_entity_resolver = CaptureTestEntityResolver::with_pid_mapping(
3048 42,
3049 EntityId::from_local_data("ci-pid-container").expect("container entity"),
3050 );
3051 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Credentials(ProcessCredentials {
3052 pid: 42,
3053 uid: 0,
3054 gid: 0,
3055 }));
3056 let process_origin = resolve_process_origin(Some(&capture_entity_resolver), &peer_addr);
3057
3058 let event = handle_frame(
3059 b"test_metric:1|c",
3060 &codec,
3061 &mut context_resolvers,
3062 &metrics,
3063 false,
3064 process_origin.as_ref(),
3065 EnablePayloadsFilter::default(),
3066 &[],
3067 &MetaString::from_static("default-host"),
3068 )
3069 .expect("frame should parse");
3070
3071 assert!(event.is_some());
3072 assert_eq!(
3073 recorder.counter(processed_metric_key("unixgram", Some("container_id://pid-container"))),
3074 None
3075 );
3076 assert_eq!(recorder.counter(processed_metric_key("unixgram", Some(""))), Some(1));
3077 }
3078
3079 #[test]
3080 fn origin_telemetry_records_resolved_origin_when_origin_detection_is_enabled() {
3081 let recorder = TestRecorder::default();
3082 let _recorder_guard = metrics::set_default_local_recorder(&recorder);
3083 let listen_addr = ListenAddress::Unixgram("/tmp/dsd.sock".into());
3084 let context = test_component_context();
3085 let metrics = build_metrics(&listen_addr, &context, true);
3086 let codec = DogStatsDCodec::from_configuration(DogStatsDCodecConfiguration::default());
3087 let mut context_resolvers = test_context_resolvers();
3088 let capture_entity_resolver = CaptureTestEntityResolver::with_pid_mapping(
3089 42,
3090 EntityId::from_local_data("ci-pid-container").expect("container entity"),
3091 );
3092 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Credentials(ProcessCredentials {
3093 pid: 42,
3094 uid: 0,
3095 gid: 0,
3096 }));
3097 let process_origin = resolve_process_origin(Some(&capture_entity_resolver), &peer_addr);
3098
3099 let event = handle_frame(
3100 b"test_metric:1|c",
3101 &codec,
3102 &mut context_resolvers,
3103 &metrics,
3104 true,
3105 process_origin.as_ref(),
3106 EnablePayloadsFilter::default(),
3107 &[],
3108 &MetaString::from_static("default-host"),
3109 )
3110 .expect("frame should parse");
3111
3112 assert!(event.is_some());
3113 assert_eq!(
3114 recorder.counter(processed_metric_key("unixgram", Some("container_id://pid-container"))),
3115 Some(1)
3116 );
3117 assert_eq!(recorder.counter(processed_metric_key("unixgram", Some(""))), Some(0));
3118 }
3119
3120 #[test]
3121 fn no_metrics_when_interner_full_allocations_disallowed() {
3122 let codec = DogStatsDCodec::from_configuration(DogStatsDCodecConfiguration::default());
3130 let tags_resolver = TagsResolverBuilder::for_tests().build();
3131 let context_resolver = ContextResolverBuilder::for_tests()
3132 .with_heap_allocations(false)
3133 .with_tags_resolver(Some(tags_resolver.clone()))
3134 .build();
3135 let mut context_resolvers = ContextResolvers::manual(context_resolver.clone(), context_resolver, tags_resolver);
3136 let input = "big_metric_name_that_cant_possibly_be_inlined:1|c|#tag1:value1,tag2:value2,tag3:value3";
3137
3138 let Ok(ParsedPacket::Metric(packet)) = codec.decode_packet(input.as_bytes()) else {
3139 panic!("Failed to parse packet.");
3140 };
3141
3142 let maybe_metric = handle_metric_packet(
3143 packet,
3144 &mut context_resolvers,
3145 None,
3146 &[],
3147 &MetaString::from_static("default-host"),
3148 );
3149 assert!(maybe_metric.is_none());
3150 }
3151
3152 #[test]
3153 fn metric_host_tag_disambiguates_contexts_without_remaining_tag() {
3154 let codec = DogStatsDCodec::from_configuration(DogStatsDCodecConfiguration::default());
3155 let mut context_resolvers = test_context_resolvers();
3156 let default_hostname = MetaString::from_static("default-host");
3157
3158 let packets = [
3159 ("unset", b"test_metric_name:1|g".as_slice(), "default-host"),
3160 ("empty", b"test_metric_name:2|g|#host:".as_slice(), ""),
3161 (
3162 "explicit_default",
3163 b"test_metric_name:3|g|#host:default-host".as_slice(),
3164 "default-host",
3165 ),
3166 (
3167 "custom",
3168 b"test_metric_name:4|g|#host:custom-host".as_slice(),
3169 "custom-host",
3170 ),
3171 ];
3172
3173 let mut metrics = Vec::new();
3174 for (case, raw, expected_host) in packets {
3175 let Ok(ParsedPacket::Metric(packet)) = codec.decode_packet(raw) else {
3176 panic!("Failed to parse {case} packet.");
3177 };
3178 let metric = handle_metric_packet(packet, &mut context_resolvers, None, &[], &default_hostname)
3179 .unwrap_or_else(|| panic!("{case} metric should resolve"));
3180
3181 assert_eq!(metric.context().host(), Some(expected_host), "{case} context host");
3182 assert!(metric.context().tags().into_iter().all(|tag| tag.name() != "host"));
3183 metrics.push(metric);
3184 }
3185
3186 assert_eq!(metrics[0].context(), metrics[2].context());
3187 assert_ne!(metrics[0].context(), metrics[1].context());
3188 assert_ne!(metrics[0].context(), metrics[3].context());
3189 assert_ne!(metrics[1].context(), metrics[3].context());
3190 }
3191
3192 #[test]
3193 fn additional_tags_are_sorted_and_deduplicated() {
3194 let config = DogStatsDConfiguration {
3195 additional_tags: vec![
3196 "dogstatsd:configured".to_string(),
3197 "env:prod".to_string(),
3198 "provider_kind:autopilot".to_string(),
3199 "kube_distribution:eks".to_string(),
3200 "env:prod".to_string(),
3201 ],
3202 ..DogStatsDConfiguration::for_test()
3203 };
3204
3205 assert_eq!(
3206 config.additional_tags(),
3207 [
3208 "dogstatsd:configured",
3209 "env:prod",
3210 "kube_distribution:eks",
3211 "provider_kind:autopilot",
3212 ]
3213 );
3214 }
3215
3216 #[test]
3217 fn metric_with_additional_tags() {
3218 let codec = DogStatsDCodec::from_configuration(DogStatsDCodecConfiguration::default());
3219 let tags_resolver = TagsResolverBuilder::for_tests().build();
3220 let context_resolver = ContextResolverBuilder::for_tests()
3221 .with_heap_allocations(false)
3222 .with_tags_resolver(Some(tags_resolver.clone()))
3223 .build();
3224 let mut context_resolvers = ContextResolvers::manual(context_resolver.clone(), context_resolver, tags_resolver);
3225 let existing_tags = ["tag1:value1", "tag2:value2", "tag3:value3"];
3226 let existing_tags_str = existing_tags.join(",");
3227
3228 let input = format!("test_metric_name:1|c|#{}", existing_tags_str);
3229 let additional_tags = [
3230 "tag4:value4".to_string(),
3231 "tag5:value5".to_string(),
3232 "tag6:value6".to_string(),
3233 ];
3234
3235 let Ok(ParsedPacket::Metric(packet)) = codec.decode_packet(input.as_bytes()) else {
3236 panic!("Failed to parse packet.");
3237 };
3238 let maybe_metric = handle_metric_packet(
3239 packet,
3240 &mut context_resolvers,
3241 None,
3242 &additional_tags,
3243 &MetaString::from_static("default-host"),
3244 );
3245 assert!(maybe_metric.is_some());
3246
3247 let metric = maybe_metric.unwrap();
3248 let context = metric.context();
3249
3250 for tag in existing_tags {
3251 assert!(context.tags().has_tag(tag));
3252 }
3253
3254 for tag in additional_tags {
3255 assert!(context.tags().has_tag(tag));
3256 }
3257 }
3258
3259 fn udp_listen_address() -> ListenAddress {
3260 ListenAddress::udp_loopback(8125)
3261 }
3262
3263 fn tcp_listen_address() -> ListenAddress {
3264 ListenAddress::tcp_loopback(8125)
3265 }
3266
3267 fn named_pipe_listen_address() -> ListenAddress {
3268 ListenAddress::named_pipe_with_input_buffer_size(
3269 "datadog-dogstatsd",
3270 TEST_WINDOWS_PIPE_SECURITY_DESCRIPTOR,
3271 TEST_BUFFER_SIZE as u32,
3272 )
3273 }
3274
3275 #[test]
3276 fn build_addresses_includes_named_pipe_when_configured() {
3277 let config = DogStatsDConfiguration {
3278 port: 0,
3279 pipe_name: Some("datadog-dogstatsd".to_string()),
3280 ..DogStatsDConfiguration::for_test()
3281 };
3282
3283 let addresses = config.build_addresses(None);
3284
3285 assert_eq!(addresses, vec![named_pipe_listen_address()]);
3286 }
3287
3288 #[test]
3289 fn build_addresses_uses_buffer_size_for_named_pipe_input_buffer() {
3290 let config = DogStatsDConfiguration {
3291 port: 0,
3292 pipe_name: Some("datadog-dogstatsd".to_string()),
3293 buffer_size: 16384,
3294 ..DogStatsDConfiguration::for_test()
3295 };
3296
3297 let addresses = config.build_addresses(None);
3298
3299 let [ListenAddress::NamedPipe { input_buffer_size, .. }] = addresses.as_slice() else {
3300 panic!("expected only a named pipe listen address, got {addresses:?}");
3301 };
3302 assert_eq!(*input_buffer_size, Some(16_384));
3303 }
3304
3305 #[test]
3306 fn eol_required_matches_named_pipe_listener_type() {
3307 let config = DogStatsDConfiguration {
3308 eol_required: vec!["named_pipe".to_string()],
3309 ..DogStatsDConfiguration::for_test()
3310 };
3311 let eol_required = config.eol_required();
3312
3313 assert!(eol_required.for_listener(&named_pipe_listen_address()));
3314 assert!(!eol_required.for_listener(&udp_listen_address()));
3315 assert!(!eol_required.for_listener(&tcp_listen_address()));
3316 }
3317
3318 #[test]
3319 fn statsd_forward_no_host_disabled() {
3320 let config = DogStatsDConfiguration {
3321 statsd_forward_port: 9125,
3322 ..DogStatsDConfiguration::for_test()
3323 };
3324 assert!(config.statsd_forward_target().is_none());
3325 }
3326
3327 #[test]
3328 fn statsd_forward_zero_port_disabled() {
3329 let config = DogStatsDConfiguration {
3330 statsd_forward_host: Some(MetaString::from("127.0.0.1")),
3331 statsd_forward_port: 0,
3332 ..DogStatsDConfiguration::for_test()
3333 };
3334 assert!(config.statsd_forward_target().is_none());
3335 }
3336
3337 #[test]
3338 fn statsd_forward_host_and_port_enabled() {
3339 let config = DogStatsDConfiguration {
3340 statsd_forward_host: Some(MetaString::from("127.0.0.1")),
3341 statsd_forward_port: 9125,
3342 ..DogStatsDConfiguration::for_test()
3343 };
3344 let (host, port) = config.statsd_forward_target().expect("forwarding should be enabled");
3345 assert_eq!(host.as_ref(), "127.0.0.1");
3346 assert_eq!(port, 9125);
3347 }
3348
3349 #[test]
3350 fn statsd_forward_invalid_target_still_builds_forwarder_handle() {
3351 let config = DogStatsDConfiguration {
3352 statsd_forward_host: Some(MetaString::from("not a valid host")),
3353 statsd_forward_port: 9125,
3354 ..DogStatsDConfiguration::for_test()
3355 };
3356 assert!(config.packet_forwarder_target().is_some());
3357 }
3358
3359 #[test]
3360 fn unsupported_platform_process_credentials_do_not_count_as_origin_detection_telemetry_errors() {
3361 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Error(
3362 saluki_io::net::ProcessCredentialsError::UnsupportedPlatform,
3363 ));
3364
3365 assert!(origin_detection_error_for_telemetry(true, 1, &peer_addr).is_none());
3366 }
3367
3368 #[test]
3369 fn invalid_process_credentials_count_as_origin_detection_telemetry_errors() {
3370 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Error(
3371 saluki_io::net::ProcessCredentialsError::InvalidCredentials,
3372 ));
3373
3374 assert!(matches!(
3375 origin_detection_error_for_telemetry(true, 1, &peer_addr),
3376 Some(saluki_io::net::ProcessCredentialsError::InvalidCredentials)
3377 ));
3378 }
3379
3380 #[test]
3381 fn zero_pid_process_credentials_count_as_origin_detection_telemetry_errors() {
3382 let peer_addr =
3383 ConnectionAddress::ProcessLike(ProcessIdentity::Error(saluki_io::net::ProcessCredentialsError::ZeroPid));
3384
3385 assert!(matches!(
3386 origin_detection_error_for_telemetry(true, 1, &peer_addr),
3387 Some(saluki_io::net::ProcessCredentialsError::ZeroPid)
3388 ));
3389 }
3390
3391 #[test]
3392 fn origin_detection_telemetry_errors_require_origin_detection_and_received_bytes() {
3393 let peer_addr =
3394 ConnectionAddress::ProcessLike(ProcessIdentity::Error(saluki_io::net::ProcessCredentialsError::ZeroPid));
3395
3396 assert!(origin_detection_error_for_telemetry(false, 1, &peer_addr).is_none());
3397 assert!(origin_detection_error_for_telemetry(true, 0, &peer_addr).is_none());
3398 }
3399
3400 #[test]
3401 fn autoscaling_disabled_yields_a_single_udp_stream() {
3402 let config = DogStatsDConfiguration {
3403 autoscale_udp_listeners: false,
3404 ..DogStatsDConfiguration::for_test()
3405 };
3406 assert!(config.udp_streams_to_yield().is_none());
3407 }
3408
3409 #[test]
3410 fn effective_max_buffer_count_never_below_baseline() {
3411 fn buffer_counts(buffer_count: usize, buffer_count_max: usize) -> DogStatsDConfiguration {
3412 DogStatsDConfiguration {
3413 buffer_count,
3414 buffer_count_max,
3415 ..DogStatsDConfiguration::for_test()
3416 }
3417 }
3418
3419 assert_eq!(buffer_counts(65536, 32_768).effective_max_buffer_count(), 65536);
3421
3422 assert_eq!(buffer_counts(128, 512).effective_max_buffer_count(), 512);
3424
3425 assert_eq!(buffer_counts(200, 64).effective_max_buffer_count(), 200);
3427 }
3428
3429 #[test]
3430 fn decoder_worker_count_matches_core_agent_defaults() {
3431 assert_eq!(default_decoder_worker_count(1), 2);
3432 assert_eq!(default_decoder_worker_count(4), 2);
3433 assert_eq!(default_decoder_worker_count(8), 6);
3434 }
3435
3436 #[test]
3437 fn decoder_worker_count_honors_explicit_override() {
3438 let config = DogStatsDConfiguration {
3439 workers_count: 1,
3440 ..DogStatsDConfiguration::for_test()
3441 };
3442
3443 assert_eq!(config.decoder_worker_count().get(), 1);
3444 }
3445
3446 #[tokio::test]
3447 async fn global_datagram_receiver_distributes_packets_to_workers() {
3448 let (sender, receiver) = mpsc::channel(2);
3449 let receiver = Arc::new(Mutex::new(receiver));
3450 let first_worker = receiver.clone();
3451 let second_worker = receiver;
3452 let socket_context = test_datagram_socket_context(udp_listen_address());
3453
3454 sender
3455 .send(QueuedDatagram {
3456 result: Err(std::io::Error::other("first")),
3457 socket_context: socket_context.clone(),
3458 })
3459 .await
3460 .expect("first packet should be queued");
3461 sender
3462 .send(QueuedDatagram {
3463 result: Err(std::io::Error::other("second")),
3464 socket_context,
3465 })
3466 .await
3467 .expect("second packet should be queued");
3468
3469 let (first, second) = tokio::join!(async { first_worker.lock().await.recv().await }, async {
3470 second_worker.lock().await.recv().await
3471 },);
3472
3473 assert!(first.expect("first worker should receive a packet").result.is_err());
3474 assert!(second.expect("second worker should receive a packet").result.is_err());
3475 }
3476
3477 #[tokio::test]
3478 async fn dogstatsd_io_buffer_pool_grows_on_demand_until_limit() {
3479 let min_buffers = 2;
3480 let max_buffers = 3;
3481 let (pool, shrinker) = build_io_buffer_pool(min_buffers, max_buffers, TEST_BUFFER_SIZE);
3482
3483 let mut initial_buffers = Vec::with_capacity(min_buffers);
3484 for _ in 0..min_buffers {
3485 initial_buffers.push(
3486 timeout(Duration::from_secs(1), pool.acquire())
3487 .await
3488 .expect("initial buffer should be available"),
3489 );
3490 }
3491 let on_demand_buffer = timeout(Duration::from_secs(1), pool.acquire())
3492 .await
3493 .expect("pool should grow on demand before hitting the limit");
3494
3495 let capped_acquire = timeout(Duration::from_millis(25), pool.acquire()).await;
3496 assert!(capped_acquire.is_err(), "pool should wait once it reaches the limit");
3497
3498 drop(initial_buffers.pop().expect("initial buffer should still be held"));
3499 timeout(Duration::from_secs(1), pool.acquire())
3500 .await
3501 .expect("returned buffer should unblock acquisition");
3502
3503 drop(on_demand_buffer);
3504 drop(shrinker);
3505 }
3506
3507 #[cfg(unix)]
3508 #[tokio::test]
3509 async fn uds_datagram_reader_is_bounded_by_io_buffer_pool() {
3510 let temp_dir = tempfile::tempdir().expect("temp directory should be created");
3511 let socket_path = temp_dir.path().join("dogstatsd.socket");
3512 let receiver = UnixDatagram::bind(&socket_path).expect("receiver should bind");
3513 let sender = UnixDatagram::unbound().expect("sender should be created");
3514 let (pool, shrinker) = build_io_buffer_pool(2, 2, TEST_BUFFER_SIZE);
3515 let (packets_tx, mut packets_rx) = mpsc::channel(3);
3516 let listen_addr = ListenAddress::Unixgram(socket_path.clone());
3517 let socket_context = Arc::new(DatagramSocketContext {
3518 metrics: build_metrics(&listen_addr, &test_component_context(), false),
3519 listen_addr,
3520 eol_required: false,
3521 packet_forwarder: None,
3522 });
3523 let reader = tokio::spawn(receive_connectionless_stream(
3524 Stream::from(receiver),
3525 pool,
3526 MemoryLimiter::noop(),
3527 false,
3528 TrafficCapture::new(PathBuf::new(), 1),
3529 None,
3530 packets_tx,
3531 socket_context,
3532 ));
3533 let payloads: [&[u8]; 3] = [b"first", b"second", b"third"];
3534
3535 for payload in payloads {
3536 sender
3537 .send_to(payload, &socket_path)
3538 .await
3539 .expect("payload should send");
3540 }
3541
3542 timeout(Duration::from_secs(1), async {
3543 while packets_rx.len() < 2 {
3544 yield_now().await;
3545 }
3546 })
3547 .await
3548 .expect("reader should fill the two-buffer pool");
3549 assert_eq!(packets_rx.len(), 2);
3550
3551 let first = packets_rx
3552 .recv()
3553 .await
3554 .expect("first packet should be queued")
3555 .result
3556 .expect("first receive should succeed");
3557 assert_eq!(received_payload(first.buffer(), first.bytes_read), payloads[0]);
3558 drop(first);
3559
3560 timeout(Duration::from_secs(1), async {
3561 while packets_rx.len() < 2 {
3562 yield_now().await;
3563 }
3564 })
3565 .await
3566 .expect("returning a buffer should allow the third packet to be read");
3567
3568 for expected in &payloads[1..] {
3569 let received = packets_rx
3570 .recv()
3571 .await
3572 .expect("packet should be queued")
3573 .result
3574 .expect("receive should succeed");
3575 assert_eq!(received_payload(received.buffer(), received.bytes_read), *expected);
3576 }
3577
3578 drop(packets_rx);
3579 sender
3580 .send_to(b"shutdown", &socket_path)
3581 .await
3582 .expect("shutdown payload should send");
3583 timeout(Duration::from_secs(1), reader)
3584 .await
3585 .expect("reader should stop after observing the closed queue")
3586 .expect("reader task should not panic");
3587 drop(shrinker);
3588 }
3589
3590 #[cfg(target_os = "linux")]
3591 #[tokio::test]
3592 async fn uds_datagram_reader_pins_origin_before_decode() {
3593 let temp_dir = tempfile::tempdir().expect("temp directory should be created");
3594 let socket_path = temp_dir.path().join("dogstatsd.socket");
3595 let listen_addr = ListenAddress::Unixgram(socket_path.clone());
3596 let mut listener = Listener::from_listen_address(listen_addr.clone(), None)
3597 .await
3598 .expect("listener should bind");
3599 let stream = listener.accept().await.expect("listener should yield its socket");
3600 let sender = UnixDatagram::unbound().expect("sender should be created");
3601 let process_id = std::process::id();
3602 let original_entity = EntityId::from_local_data("ci-original-container").expect("container entity");
3603 let reused_entity = EntityId::from_local_data("ci-reused-container").expect("container entity");
3604 let capture_entity_resolver = Arc::new(CaptureTestEntityResolver::with_pid_mapping(
3605 process_id,
3606 original_entity.clone(),
3607 ));
3608 let (pool, shrinker) = build_io_buffer_pool(1, 1, TEST_BUFFER_SIZE);
3609 let (packets_tx, mut packets_rx) = mpsc::channel(1);
3610 let reader = tokio::spawn(receive_connectionless_stream(
3611 stream,
3612 pool,
3613 MemoryLimiter::noop(),
3614 true,
3615 TrafficCapture::new(PathBuf::new(), 1),
3616 Some(capture_entity_resolver.clone()),
3617 packets_tx,
3618 test_datagram_socket_context(listen_addr),
3619 ));
3620
3621 sender
3622 .send_to(b"test.metric:1|c", &socket_path)
3623 .await
3624 .expect("payload should send");
3625 let received = timeout(Duration::from_secs(1), packets_rx.recv())
3626 .await
3627 .expect("packet should be received")
3628 .expect("reader should remain active")
3629 .result
3630 .expect("receive should succeed");
3631 assert_eq!(
3632 received.process_origin,
3633 Some(ProcessOrigin::Pinned(Some(original_entity.clone())))
3634 );
3635 assert_eq!(capture_entity_resolver.resolution_count(), 1);
3636
3637 capture_entity_resolver.set_pid_mapping(process_id, reused_entity.clone());
3639
3640 let mut workload_provider = TestWorkloadProvider::new();
3641 workload_provider.add_entity(original_entity, &["container:original"]);
3642 workload_provider.add_entity(reused_entity, &["container:reused"]);
3643 let origin_config = OriginEnrichmentConfiguration {
3644 enabled: true,
3645 ..OriginEnrichmentConfiguration::for_test()
3646 };
3647 let origin_resolver = DogStatsDOriginTagResolver::new(
3648 origin_config,
3649 Arc::new(workload_provider),
3650 super::CapturedTaggerHandle::new(),
3651 );
3652 let tags_resolver = TagsResolverBuilder::for_tests().build();
3653 let context_resolver = ContextResolverBuilder::for_tests()
3654 .with_tags_resolver(Some(tags_resolver.clone()))
3655 .build();
3656 let mut context_resolvers = ContextResolvers::manual_with_origin(
3657 context_resolver.clone(),
3658 context_resolver,
3659 tags_resolver,
3660 origin_resolver,
3661 );
3662 let codec = DogStatsDCodec::from_configuration(DogStatsDCodecConfiguration::default());
3663 let Ok(ParsedPacket::Metric(packet)) = codec.decode_packet(b"test.metric:1|c") else {
3664 panic!("metric should parse");
3665 };
3666 let metric = handle_metric_packet(
3667 packet,
3668 &mut context_resolvers,
3669 received.process_origin.as_ref(),
3670 &[],
3671 &MetaString::from_static("default-host"),
3672 )
3673 .expect("metric context should resolve");
3674
3675 assert!(metric.context().origin_tags().has_tag("container:original"));
3676 assert!(!metric.context().origin_tags().has_tag("container:reused"));
3677
3678 drop(received);
3679 drop(packets_rx);
3680 sender
3681 .send_to(b"shutdown", &socket_path)
3682 .await
3683 .expect("shutdown payload should send");
3684 timeout(Duration::from_secs(1), reader)
3685 .await
3686 .expect("reader should stop after observing the closed queue")
3687 .expect("reader task should not panic");
3688 drop(shrinker);
3689 }
3690
3691 #[cfg(unix)]
3692 #[tokio::test]
3693 async fn connection_oriented_reader_preserves_partial_frames() {
3694 let (mut sender, receiver) = UnixStream::pair().expect("stream pair should be created");
3695 let (pool, shrinker) = build_io_buffer_pool(1, 1, TEST_BUFFER_SIZE);
3696 let (packets_tx, mut packets_rx) = mpsc::channel(1);
3697 let reader = tokio::spawn(receive_connected_stream(
3698 Stream::from(receiver),
3699 pool,
3700 MemoryLimiter::noop(),
3701 false,
3702 TrafficCapture::new(PathBuf::new(), 1),
3703 None,
3704 packets_tx,
3705 ));
3706
3707 sender.write_all(b"partial").await.expect("first payload should send");
3708 let first = timeout(Duration::from_secs(1), packets_rx.recv())
3709 .await
3710 .expect("first read should finish")
3711 .expect("reader should remain active")
3712 .expect("first read should succeed");
3713 assert_eq!(first.buffer().chunk(), b"partial");
3714 drop(first);
3715
3716 sender.write_all(b"-frame").await.expect("second payload should send");
3717 let second = timeout(Duration::from_secs(1), packets_rx.recv())
3718 .await
3719 .expect("second read should finish")
3720 .expect("reader should remain active")
3721 .expect("second read should succeed");
3722 assert_eq!(second.bytes_read, b"-frame".len());
3723 assert_eq!(second.buffer().chunk(), b"partial-frame");
3724 drop(second);
3725
3726 drop(packets_rx);
3727 sender
3728 .write_all(b"shutdown")
3729 .await
3730 .expect("shutdown payload should send");
3731 timeout(Duration::from_secs(1), reader)
3732 .await
3733 .expect("reader should stop after observing the closed queue")
3734 .expect("reader task should not panic");
3735 drop(shrinker);
3736 }
3737
3738 #[cfg(unix)]
3739 #[tokio::test]
3740 async fn connection_oriented_reader_releases_drained_buffer_before_reacquiring() {
3741 let (mut sender, receiver) = UnixStream::pair().expect("stream pair should be created");
3742 let (pool, shrinker) = build_io_buffer_pool(1, 1, TEST_BUFFER_SIZE);
3743 let (packets_tx, mut packets_rx) = mpsc::channel(1);
3744 let reader = tokio::spawn(receive_connected_stream(
3745 Stream::from(receiver),
3746 pool,
3747 MemoryLimiter::noop(),
3748 false,
3749 TrafficCapture::new(PathBuf::new(), 1),
3750 None,
3751 packets_tx,
3752 ));
3753
3754 sender.write_all(b"first").await.expect("first payload should send");
3755 let mut first = timeout(Duration::from_secs(1), packets_rx.recv())
3756 .await
3757 .expect("first read should finish")
3758 .expect("reader should remain active")
3759 .expect("first read should succeed");
3760 let bytes_read = first.bytes_read;
3761 first.buffer_mut().advance(bytes_read);
3762 drop(first);
3763
3764 sender.write_all(b"second").await.expect("second payload should send");
3765 let mut second = timeout(Duration::from_secs(1), packets_rx.recv())
3766 .await
3767 .expect("reader should reacquire the released buffer")
3768 .expect("reader should remain active")
3769 .expect("second read should succeed");
3770 let bytes_read = second.bytes_read;
3771 assert_eq!(received_payload(second.buffer(), bytes_read), b"second");
3772 second.buffer_mut().advance(bytes_read);
3773 drop(second);
3774
3775 drop(packets_rx);
3776 sender
3777 .write_all(b"shutdown")
3778 .await
3779 .expect("shutdown payload should send");
3780 timeout(Duration::from_secs(1), reader)
3781 .await
3782 .expect("reader should stop after observing the closed queue")
3783 .expect("reader task should not panic");
3784 drop(shrinker);
3785 }
3786
3787 #[test]
3788 #[cfg(target_os = "linux")]
3789 fn autoscale_udp_listeners_yields_multiple_streams_on_linux() {
3790 let config = DogStatsDConfiguration {
3791 autoscale_udp_listeners: true,
3792 ..DogStatsDConfiguration::for_test()
3793 };
3794
3795 let streams = config
3796 .udp_streams_to_yield()
3797 .expect("autoscale yields at least 1 stream");
3798 let n = streams.get();
3799 assert!(
3800 (1..=4).contains(&n),
3801 "expected 1..=4 streams from vCPU formula, got {n}"
3802 );
3803 }
3804
3805 #[test]
3806 #[cfg(not(target_os = "linux"))]
3807 fn warns_for_uds_origin_detection_on_non_linux() {
3808 let config = DogStatsDConfiguration {
3809 port: 0,
3810 socket_path: Some("/tmp/dsd.sock".to_string()),
3811 origin_enrichment: OriginEnrichmentConfiguration {
3812 enabled: true,
3813 ..OriginEnrichmentConfiguration::for_test()
3814 },
3815 ..DogStatsDConfiguration::for_test()
3816 };
3817 let addresses = config.build_addresses(None);
3818
3819 assert!(config.uds_origin_detection_unsupported_on_platform(&addresses));
3820 }
3821
3822 #[test]
3823 #[cfg(not(target_os = "linux"))]
3824 fn does_not_warn_for_udp_origin_detection_on_non_linux() {
3825 let config = DogStatsDConfiguration {
3826 origin_enrichment: OriginEnrichmentConfiguration {
3827 enabled: true,
3828 ..OriginEnrichmentConfiguration::for_test()
3829 },
3830 ..DogStatsDConfiguration::for_test()
3831 };
3832 let addresses = config.build_addresses(None);
3833
3834 assert!(!config.uds_origin_detection_unsupported_on_platform(&addresses));
3835 }
3836
3837 #[test]
3838 #[cfg(not(target_os = "linux"))]
3839 fn autoscale_udp_listeners_yields_a_single_stream_on_non_linux() {
3840 let config = DogStatsDConfiguration {
3841 autoscale_udp_listeners: true,
3842 ..DogStatsDConfiguration::for_test()
3843 };
3844
3845 assert_eq!(None, config.udp_streams_to_yield());
3846 }
3847
3848 #[test]
3849 fn no_eol_required_listener_types_requires_no_newline() {
3850 let config = DogStatsDConfiguration {
3851 eol_required: Vec::new(),
3852 ..DogStatsDConfiguration::for_test()
3853 };
3854 let eol_required = config.eol_required();
3855
3856 assert!(!eol_required.for_listener(&udp_listen_address()));
3857 assert!(!eol_required.for_listener(&tcp_listen_address()));
3858 }
3859
3860 #[test]
3861 fn eol_required_matches_configured_listener_types() {
3862 let config = DogStatsDConfiguration {
3863 eol_required: vec!["udp".to_string(), "uds".to_string()],
3864 ..DogStatsDConfiguration::for_test()
3865 };
3866 let eol_required = config.eol_required();
3867
3868 assert!(eol_required.for_listener(&udp_listen_address()));
3869 assert!(!eol_required.for_listener(&tcp_listen_address()));
3870
3871 #[cfg(unix)]
3872 {
3873 assert!(eol_required.for_listener(&ListenAddress::Unixgram("/tmp/dsd.sock".into())));
3874 assert!(eol_required.for_listener(&ListenAddress::Unix("/tmp/dsd-stream.sock".into())));
3875 }
3876 }
3877
3878 #[test]
3879 fn eol_required_ignores_unrecognized_listener_types() {
3880 let config = DogStatsDConfiguration {
3881 eol_required: vec!["udp".to_string(), "carrier_pigeon".to_string()],
3882 ..DogStatsDConfiguration::for_test()
3883 };
3884 let eol_required = config.eol_required();
3885
3886 assert!(eol_required.for_listener(&udp_listen_address()));
3887 assert!(!eol_required.for_listener(&named_pipe_listen_address()));
3888 }
3889
3890 #[test]
3891 fn drops_full_named_pipe_buffer_without_newline() {
3892 let named_pipe_stream = named_pipe_listen_address();
3893 let mut buffer = get_pooled_object_via_builder::<_, BytesBuffer>(|| FixedSizeVec::with_capacity(8));
3894 buffer.put_slice(b"12345678");
3895
3896 assert!(super::should_drop_oversized_named_pipe_frame(
3897 &named_pipe_stream,
3898 &buffer
3899 ));
3900 }
3901
3902 #[test]
3903 fn keeps_named_pipe_partial_frame_when_buffer_has_capacity() {
3904 let named_pipe_stream = named_pipe_listen_address();
3905 let mut buffer = get_pooled_object_via_builder::<_, BytesBuffer>(|| FixedSizeVec::with_capacity(9));
3906 buffer.put_slice(b"12345678");
3907
3908 assert!(!super::should_drop_oversized_named_pipe_frame(
3909 &named_pipe_stream,
3910 &buffer
3911 ));
3912 }
3913
3914 #[test]
3915 fn keeps_full_named_pipe_buffer_with_newline() {
3916 let named_pipe_stream = named_pipe_listen_address();
3917 let mut buffer = get_pooled_object_via_builder::<_, BytesBuffer>(|| FixedSizeVec::with_capacity(8));
3918 buffer.put_slice(b"1234567\n");
3919
3920 assert!(!super::should_drop_oversized_named_pipe_frame(
3921 &named_pipe_stream,
3922 &buffer
3923 ));
3924 }
3925
3926 #[test]
3927 fn stream_log_too_big_warns_for_enabled_length_delimited_stream_invalid_frames() {
3928 let uds_stream = ListenAddress::Unix("/tmp/dsd-stream.sock".into());
3929 let named_pipe_stream = named_pipe_listen_address();
3930 let tcp_stream = ListenAddress::Tcp(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8125)));
3931 let error = saluki_io::deser::framing::FramingError::InvalidFrame {
3932 frame_len: 8193,
3933 reason: "frame length exceeds buffer capacity",
3934 };
3935
3936 assert!(super::should_warn_stream_log_too_big(&uds_stream, &error, true));
3937 assert!(!super::should_warn_stream_log_too_big(&uds_stream, &error, false));
3938 assert!(!super::should_warn_stream_log_too_big(&named_pipe_stream, &error, true));
3939 assert!(!super::should_warn_stream_log_too_big(&tcp_stream, &error, true));
3940 }
3941
3942 #[test]
3943 fn interner_size_from_entry_count() {
3944 let config = DogStatsDConfiguration {
3946 context_string_interner_entry_count: 4096,
3947 ..DogStatsDConfiguration::for_test()
3948 };
3949 assert_eq!(config.effective_context_string_interner_bytes(), ByteSize::mib(2));
3950 }
3951
3952 #[test]
3953 fn interner_size_from_explicit_bytes() {
3954 let config = DogStatsDConfiguration {
3955 context_string_interner_size_bytes: Some(ByteSize::b(4194304)),
3956 ..DogStatsDConfiguration::for_test()
3957 };
3958 assert_eq!(config.effective_context_string_interner_bytes(), ByteSize::b(4194304));
3959 }
3960
3961 #[test]
3962 fn interner_size_explicit_bytes_takes_priority() {
3963 let config = DogStatsDConfiguration {
3964 context_string_interner_entry_count: 4096,
3965 context_string_interner_size_bytes: Some(ByteSize::b(8388608)),
3966 ..DogStatsDConfiguration::for_test()
3967 };
3968 assert_eq!(config.effective_context_string_interner_bytes(), ByteSize::b(8388608));
3970 }
3971
3972 #[test]
3973 fn interner_size_custom_entry_count() {
3974 let config = DogStatsDConfiguration {
3975 context_string_interner_entry_count: 8192,
3976 ..DogStatsDConfiguration::for_test()
3977 };
3978 assert_eq!(config.effective_context_string_interner_bytes(), ByteSize::mib(4));
3980 }
3981
3982 fn address_list_eq(expected: &mut [ListenAddress], actual: &mut [ListenAddress]) -> Result<(), String> {
3984 if expected.len() != actual.len() {
3985 return Err(format!(
3986 "length mismatch: expected {} addresses, got {}",
3987 expected.len(),
3988 actual.len()
3989 ));
3990 }
3991
3992 expected.sort_by_key(|a| a.to_string());
3993 actual.sort_by_key(|a| a.to_string());
3994
3995 for (e, a) in expected.iter().zip(actual.iter()) {
3996 let (es, as_) = (e.to_string(), a.to_string());
3997 if es != as_ {
3998 return Err(format!("address mismatch: expected {}, got {}", es, as_));
3999 }
4000 }
4001
4002 Ok(())
4003 }
4004
4005 #[test]
4008 fn build_addresses_assertion_function_works() {
4009 let config = DogStatsDConfiguration {
4010 port: 0,
4011 tcp_port: 123,
4012 socket_path: None,
4013 socket_stream_path: None,
4014 non_local_traffic: false,
4015 ..DogStatsDConfiguration::for_test()
4016 };
4017 let mut expected = vec![ListenAddress::Tcp(SocketAddr::V4(SocketAddrV4::new(
4018 Ipv4Addr::new(127, 0, 0, 2),
4020 123,
4021 )))];
4022 let mut actual = config.build_addresses(None);
4023 assert!(address_list_eq(&mut expected, &mut actual).is_err())
4024 }
4025
4026 #[test]
4028 fn build_addresses_no_listeners() {
4029 let config = DogStatsDConfiguration {
4030 port: 0,
4031 tcp_port: 0,
4032 socket_path: None,
4033 socket_stream_path: None,
4034 non_local_traffic: false,
4035 ..DogStatsDConfiguration::for_test()
4036 };
4037 let mut expected = vec![];
4038 let mut actual = config.build_addresses(None);
4039 address_list_eq(&mut expected, &mut actual).unwrap();
4040 }
4041
4042 #[test]
4044 fn build_addresses_udp_local_only() {
4045 let config = DogStatsDConfiguration {
4046 port: 8125,
4047 tcp_port: 0,
4048 socket_path: None,
4049 socket_stream_path: None,
4050 non_local_traffic: false,
4051 ..DogStatsDConfiguration::for_test()
4052 };
4053 let mut expected = vec![ListenAddress::udp_loopback(8125)];
4054 let mut actual = config.build_addresses(None);
4055 address_list_eq(&mut expected, &mut actual).unwrap();
4056 }
4057
4058 #[test]
4060 fn build_addresses_udp_non_local_only() {
4061 let config = DogStatsDConfiguration {
4062 port: 8125,
4063 tcp_port: 0,
4064 socket_path: None,
4065 socket_stream_path: None,
4066 non_local_traffic: true,
4067 ..DogStatsDConfiguration::for_test()
4068 };
4069 let mut expected = vec![ListenAddress::udp_any(8125)];
4070 let mut actual = config.build_addresses(None);
4071 address_list_eq(&mut expected, &mut actual).unwrap();
4072 }
4073
4074 #[test]
4076 fn build_addresses_tcp_local_only() {
4077 let config = DogStatsDConfiguration {
4078 port: 0,
4079 tcp_port: 9000,
4080 socket_path: None,
4081 socket_stream_path: None,
4082 non_local_traffic: false,
4083 ..DogStatsDConfiguration::for_test()
4084 };
4085 let mut expected = vec![ListenAddress::Tcp(SocketAddr::V4(SocketAddrV4::new(
4086 Ipv4Addr::new(127, 0, 0, 1),
4087 9000,
4088 )))];
4089 let mut actual = config.build_addresses(None);
4090 address_list_eq(&mut expected, &mut actual).unwrap();
4091 }
4092
4093 #[test]
4095 fn build_addresses_tcp_non_local_only() {
4096 let config = DogStatsDConfiguration {
4097 port: 0,
4098 tcp_port: 9000,
4099 socket_path: None,
4100 socket_stream_path: None,
4101 non_local_traffic: true,
4102 ..DogStatsDConfiguration::for_test()
4103 };
4104 let mut expected = vec![ListenAddress::Tcp(SocketAddr::V4(SocketAddrV4::new(
4105 Ipv4Addr::new(0, 0, 0, 0),
4106 9000,
4107 )))];
4108 let mut actual = config.build_addresses(None);
4109 address_list_eq(&mut expected, &mut actual).unwrap();
4110 }
4111
4112 #[test]
4114 fn build_addresses_unixgram_only() {
4115 let config = DogStatsDConfiguration {
4116 port: 0,
4117 tcp_port: 0,
4118 socket_path: Some("/tmp/dsd.sock".to_string()),
4119 socket_stream_path: None,
4120 non_local_traffic: false,
4121 ..DogStatsDConfiguration::for_test()
4122 };
4123 let mut expected = vec![ListenAddress::Unixgram("/tmp/dsd.sock".into())];
4124 let mut actual = config.build_addresses(None);
4125 address_list_eq(&mut expected, &mut actual).unwrap();
4126 }
4127
4128 #[test]
4130 fn build_addresses_unix_stream_only() {
4131 let config = DogStatsDConfiguration {
4132 port: 0,
4133 tcp_port: 0,
4134 socket_path: None,
4135 socket_stream_path: Some("/tmp/dsd-stream.sock".to_string()),
4136 non_local_traffic: false,
4137 ..DogStatsDConfiguration::for_test()
4138 };
4139 let mut expected = vec![ListenAddress::Unix("/tmp/dsd-stream.sock".into())];
4140 let mut actual = config.build_addresses(None);
4141 address_list_eq(&mut expected, &mut actual).unwrap();
4142 }
4143
4144 #[test]
4146 fn build_addresses_all_four_non_local() {
4147 let config = DogStatsDConfiguration {
4148 port: 8125,
4149 tcp_port: 9000,
4150 socket_path: Some("/tmp/dsd.sock".to_string()),
4151 socket_stream_path: Some("/tmp/dsd-stream.sock".to_string()),
4152 non_local_traffic: true,
4153 ..DogStatsDConfiguration::for_test()
4154 };
4155 let mut expected = vec![
4156 ListenAddress::udp_any(8125),
4157 ListenAddress::tcp_any(9000),
4158 ListenAddress::Unixgram("/tmp/dsd.sock".into()),
4159 ListenAddress::Unix("/tmp/dsd-stream.sock".into()),
4160 ];
4161 let mut actual = config.build_addresses(None);
4162 address_list_eq(&mut expected, &mut actual).unwrap();
4163 }
4164
4165 #[test]
4167 fn build_addresses_all_four_local() {
4168 let config = DogStatsDConfiguration {
4169 port: 8125,
4170 tcp_port: 9000,
4171 socket_path: Some("/tmp/dsd.sock".to_string()),
4172 socket_stream_path: Some("/tmp/dsd-stream.sock".to_string()),
4173 non_local_traffic: false,
4174 ..DogStatsDConfiguration::for_test()
4175 };
4176 let mut expected = vec![
4177 ListenAddress::udp_loopback(8125),
4178 ListenAddress::tcp_loopback(9000),
4179 ListenAddress::Unixgram("/tmp/dsd.sock".into()),
4180 ListenAddress::Unix("/tmp/dsd-stream.sock".into()),
4181 ];
4182 let mut actual = config.build_addresses(None);
4183 address_list_eq(&mut expected, &mut actual).unwrap();
4184 }
4185
4186 #[test]
4189 fn build_addresses_bind_host_applies_to_udp_and_tcp() {
4190 let config = DogStatsDConfiguration {
4191 port: 8125,
4192 tcp_port: 9000,
4193 socket_path: Some("/tmp/dsd.sock".to_string()),
4194 socket_stream_path: None,
4195 non_local_traffic: false,
4196 ..DogStatsDConfiguration::for_test()
4197 };
4198 let bind_host = Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 50)));
4199 let mut expected = vec![
4200 ListenAddress::Udp(([192, 168, 1, 50], 8125).into()),
4201 ListenAddress::Tcp(([192, 168, 1, 50], 9000).into()),
4202 ListenAddress::Unixgram("/tmp/dsd.sock".into()),
4203 ];
4204 let mut actual = config.build_addresses(bind_host);
4205 address_list_eq(&mut expected, &mut actual).unwrap();
4206 }
4207
4208 #[test]
4212 fn build_addresses_non_local_clobbers_bind_host() {
4213 let config = DogStatsDConfiguration {
4214 port: 8125,
4215 tcp_port: 9000,
4216 socket_path: None,
4217 socket_stream_path: Some("/tmp/dsd-stream.sock".to_string()),
4218 non_local_traffic: true,
4219 ..DogStatsDConfiguration::for_test()
4220 };
4221 let bind_host = Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 50)));
4222 let mut expected = vec![
4223 ListenAddress::udp_any(8125),
4224 ListenAddress::tcp_any(9000),
4225 ListenAddress::Unix("/tmp/dsd-stream.sock".into()),
4226 ];
4227 let mut actual = config.build_addresses(bind_host);
4228 address_list_eq(&mut expected, &mut actual).unwrap();
4229 }
4230
4231 #[test]
4232 fn non_finite_metric_values_are_silently_dropped() {
4233 let codec = DogStatsDCodec::from_configuration(DogStatsDCodecConfiguration::default());
4238 for input in &[b"my.gauge:NaN|g" as &[u8], b"my.gauge:inf|g", b"my.gauge:-inf|g"] {
4239 match codec.decode_packet(input).expect("should decode without error") {
4240 ParsedPacket::Metric(packet) => assert_eq!(
4241 packet.num_points, 0,
4242 "non-finite value should be dropped, leaving 0 valid points"
4243 ),
4244 _ => panic!("expected Metric packet"),
4245 }
4246 }
4247 }
4248
4249 #[test]
4250 fn resolve_process_origin_pins_live_entity() {
4251 let capture_entity_resolver = CaptureTestEntityResolver::with_pid_mapping(
4252 42,
4253 EntityId::from_local_data("ci-pid-container").expect("container entity"),
4254 );
4255 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Credentials(ProcessCredentials {
4256 pid: 42,
4257 uid: 0,
4258 gid: 0,
4259 }));
4260
4261 assert_eq!(
4262 resolve_process_origin(Some(&capture_entity_resolver), &peer_addr),
4263 Some(ProcessOrigin::Pinned(Some(
4264 EntityId::from_local_data("ci-pid-container").expect("container entity")
4265 )))
4266 );
4267 }
4268
4269 #[test]
4270 fn resolve_process_origin_skips_live_lookup_when_unused() {
4271 let capture_entity_resolver = CaptureTestEntityResolver::with_pid_mapping(
4272 42,
4273 EntityId::from_local_data("ci-pid-container").expect("container entity"),
4274 );
4275 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Credentials(ProcessCredentials {
4276 pid: 42,
4277 uid: 0,
4278 gid: 0,
4279 }));
4280 let traffic_capture = TrafficCapture::new(PathBuf::new(), 1);
4281
4282 assert_eq!(
4283 resolve_process_origin_if_needed(false, &traffic_capture, Some(&capture_entity_resolver), &peer_addr),
4284 None
4285 );
4286 assert_eq!(capture_entity_resolver.resolution_count(), 0);
4287 }
4288
4289 #[tokio::test]
4290 async fn resolve_process_origin_pins_live_entity_during_capture() {
4291 let capture_entity_resolver = CaptureTestEntityResolver::with_pid_mapping(
4292 42,
4293 EntityId::from_local_data("ci-pid-container").expect("container entity"),
4294 );
4295 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Credentials(ProcessCredentials {
4296 pid: 42,
4297 uid: 0,
4298 gid: 0,
4299 }));
4300 let capture_dir = tempfile::tempdir().expect("capture directory should be created");
4301 let traffic_capture = TrafficCapture::new(capture_dir.path().to_path_buf(), 1);
4302 traffic_capture
4303 .start_capture(None, Duration::from_secs(30), false)
4304 .expect("capture should start");
4305
4306 assert_eq!(
4307 resolve_process_origin_if_needed(false, &traffic_capture, Some(&capture_entity_resolver), &peer_addr),
4308 Some(ProcessOrigin::Pinned(Some(
4309 EntityId::from_local_data("ci-pid-container").expect("container entity")
4310 )))
4311 );
4312 assert_eq!(capture_entity_resolver.resolution_count(), 1);
4313
4314 traffic_capture.stop_capture();
4315 timeout(Duration::from_secs(1), async {
4316 while traffic_capture.is_ongoing() {
4317 yield_now().await;
4318 }
4319 })
4320 .await
4321 .expect("capture should stop");
4322 }
4323
4324 #[test]
4325 fn build_capture_record_ignores_payload_local_data() {
4326 let record = super::build_capture_record(None, None, b"test.metric:1|c|c:ci-local-container\n");
4327
4328 assert_eq!(record.container_id, None);
4329 assert!(record.ancillary.is_empty());
4330 }
4331
4332 #[test]
4333 fn stream_capture_state_preserves_last_pid_without_new_creds() {
4334 let mut stream_capture = super::StreamCaptureState::new();
4335
4336 stream_capture.update_peer_metadata(&ConnectionAddress::ProcessLike(ProcessIdentity::Credentials(
4337 ProcessCredentials {
4338 pid: 42,
4339 uid: 0,
4340 gid: 0,
4341 },
4342 )));
4343 stream_capture.update_peer_metadata(&ConnectionAddress::ProcessLike(ProcessIdentity::Unavailable));
4344
4345 assert_eq!(stream_capture.last_pid, Some(42));
4346 }
4347
4348 #[test]
4349 fn resolve_process_origin_preserves_live_pid_without_entity_resolver() {
4350 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Credentials(ProcessCredentials {
4351 pid: 12345,
4352 uid: 1000,
4353 gid: 1000,
4354 }));
4355
4356 assert_eq!(
4357 resolve_process_origin(None, &peer_addr),
4358 Some(ProcessOrigin::Unpinned(12345))
4359 );
4360 }
4361
4362 #[test]
4363 fn resolve_process_origin_unpacks_captured_pid_when_replay_gid_present() {
4364 let captured_pid: u32 = 99887766;
4365 let peer_addr = ConnectionAddress::ProcessLike(ProcessIdentity::Credentials(ProcessCredentials {
4366 pid: 12345, uid: captured_pid, gid: super::REPLAY_CREDENTIALS_GID,
4369 }));
4370
4371 assert_eq!(
4372 resolve_process_origin(None, &peer_addr),
4373 Some(ProcessOrigin::Replay(captured_pid))
4374 );
4375 }
4376}
4377
4378#[cfg(test)]
4384mod supervision {
4385 use std::net::SocketAddr;
4386 use std::num::NonZeroUsize;
4387 use std::path::PathBuf;
4388 use std::time::Duration;
4389
4390 use bytes::BytesMut;
4391 use saluki_core::{
4392 components::test_util::{
4393 ComponentControl, DriverOptions, NoInput, Outputs, PrebuiltSource, TestComponentDriver,
4394 },
4395 data_model::event::metric::context::{ContextResolverBuilder, TagsResolverBuilder},
4396 runtime::{state::ResourceRegistry, NodeSnapshot, NodeState, SupervisorError},
4397 support::SubsystemIdentifier,
4398 topology::EventsBuffer,
4399 };
4400 use saluki_io::net::listener::Listener;
4401 use saluki_io::net::{BoundListenAddress, ListenAddress};
4402 use stringtheory::MetaString;
4403 use tokio::net::UdpSocket;
4404 use tokio::time::timeout;
4405
4406 use super::*;
4407
4408 const RUN_TIMEOUT: Duration = Duration::from_secs(10);
4410
4411 const COMPONENT_ID: &str = "dogstatsd";
4413
4414 const DECODER_WORKERS: usize = 2;
4418
4419 const BACKPRESSURE_SETTLE: Duration = Duration::from_millis(250);
4421
4422 const DRAIN_HOLD: Duration = Duration::from_secs(6);
4427
4428 const DRAIN_COMPONENT_TIMEOUT: Duration = Duration::from_secs(30);
4430
4431 const fn udp_child_count() -> usize {
4443 1 + DECODER_WORKERS + 1 + 1
4444 }
4445
4446 const fn udp_descendant_count() -> usize {
4450 1 + udp_child_count()
4451 }
4452
4453 async fn run_source(
4455 options: DriverOptions, source: Box<DogStatsD>,
4456 ) -> (ComponentControl<NoInput>, Outputs<EventsBuffer>) {
4457 let outputs = DogStatsDConfiguration::for_test().outputs().to_vec();
4458 options
4459 .with_component_id(COMPONENT_ID)
4460 .source(PrebuiltSource::new(source, outputs))
4461 .await
4462 .expect("the pre-built source should build")
4463 }
4464
4465 fn running_beneath(node: &NodeSnapshot) -> usize {
4467 node.children
4468 .iter()
4469 .map(|child| usize::from(child.state == NodeState::Running) + running_beneath(child))
4470 .sum()
4471 }
4472
4473 fn find_node<'a>(node: &'a NodeSnapshot, name: &str) -> Option<&'a NodeSnapshot> {
4475 node.children.iter().find_map(|child| {
4476 if child.name == name {
4477 Some(child)
4478 } else {
4479 find_node(child, name)
4480 }
4481 })
4482 }
4483
4484 async fn build_source() -> (Box<DogStatsD>, SocketAddr) {
4489 let listener = leased_listener(ListenAddress::udp_loopback(0)).await;
4490 let listen_addr = match listener.bound_listen_address() {
4491 BoundListenAddress::Udp(addr) => addr,
4492 other => panic!("expected a bound UDP address, got {other:?}"),
4493 };
4494
4495 let io_buffer_count = 16;
4496 let (io_buffer_pool, io_buffer_pool_shrinker) = build_io_buffer_pool(
4497 io_buffer_count,
4498 io_buffer_count,
4499 DogStatsDConfiguration::for_test().buffer_size,
4500 );
4501
4502 let tags_resolver = TagsResolverBuilder::for_tests().build();
4503 let context_resolver = ContextResolverBuilder::for_tests()
4504 .with_tags_resolver(Some(tags_resolver.clone()))
4505 .build();
4506
4507 let source = Box::new(DogStatsD {
4508 listeners: vec![listener],
4509 decoder_worker_count: NonZeroUsize::new(DECODER_WORKERS).expect("decoder count should be non-zero"),
4510 io_buffer_pool,
4511 io_buffer_queue_capacity: 16,
4512 io_buffer_pool_shrinker: Box::pin(io_buffer_pool_shrinker),
4513 codec: DogStatsDCodec::from_configuration(DogStatsDCodecConfiguration::default()),
4514 context_resolvers: ContextResolvers::manual(context_resolver.clone(), context_resolver, tags_resolver),
4515 default_hostname: MetaString::from_static("test-host"),
4516 enabled_filter: EnablePayloadsFilter::default(),
4517 origin_detection_enabled: false,
4518 origin_telemetry_enabled: false,
4519 stream_log_too_big: false,
4520 disable_verbose_logs: false,
4521 eol_required: EolRequired::default(),
4522 additional_tags: Vec::<String>::new().into(),
4523 capture_entity_resolver: None,
4524 traffic_capture: TrafficCapture::new(PathBuf::new(), 1),
4525 packet_forwarder_target: None,
4526 });
4527
4528 (source, listen_addr)
4529 }
4530
4531 async fn leased_listener(address: ListenAddress) -> ResourceLease<Listener> {
4536 leased_listener_from(&ResourceRegistry::new(), address).await
4537 }
4538
4539 async fn leased_listener_from(registry: &ResourceRegistry, address: ListenAddress) -> ResourceLease<Listener> {
4541 registry
4542 .acquire(
4543 &SubsystemIdentifier::from_segments(["test", COMPONENT_ID]),
4544 SocketSpecification::new(address),
4545 )
4546 .await
4547 .expect("listener should bind")
4548 }
4549
4550 #[tokio::test]
4551 async fn background_work_runs_as_the_sources_own_children() {
4552 let (source, _) = build_source().await;
4556 let (control, _outputs) = run_source(TestComponentDriver::options(), source).await;
4557 control
4558 .supervisor()
4559 .wait_for_running_descendants(udp_descendant_count())
4560 .await;
4561
4562 let root = control.supervisor().tree_handle().snapshot().root;
4563 let source_node = find_node(&root, "sources.dogstatsd").expect("the source is running");
4564 assert_eq!(running_beneath(source_node), udp_child_count());
4565
4566 control
4569 .shutdown()
4570 .await
4571 .expect("every child should have stopped on its own");
4572 }
4573
4574 #[tokio::test]
4575 async fn source_does_not_finish_until_the_decoders_have_drained() {
4576 let (source, listen_addr) = build_source().await;
4586 let options = TestComponentDriver::options()
4587 .with_shutdown_timeout(DRAIN_COMPONENT_TIMEOUT)
4588 .with_interconnect_capacity(NonZeroUsize::MIN);
4589 let (mut control, mut outputs) = run_source(options, source).await;
4590 let mut metrics = outputs.take_named_output("metrics");
4591 control
4592 .supervisor()
4593 .wait_for_running_descendants(udp_descendant_count())
4594 .await;
4595
4596 let client = UdpSocket::bind("127.0.0.1:0").await.expect("client should bind");
4597
4598 client
4600 .send_to(b"first.metric:1|c", listen_addr)
4601 .await
4602 .expect("client should send");
4603 let buffer = metrics.next().await.expect("the metrics output should stay open");
4604 assert_eq!(buffer.len(), 1);
4605
4606 for payload in [b"second.metric:1|c".as_slice(), b"third.metric:1|c".as_slice()] {
4608 client.send_to(payload, listen_addr).await.expect("client should send");
4609 }
4610 tokio::time::sleep(BACKPRESSURE_SETTLE).await;
4611
4612 tokio::time::pause();
4615 control.signal_shutdown();
4616
4617 tokio::time::sleep(DRAIN_HOLD).await;
4622 assert!(
4623 !control.supervisor().is_finished(),
4624 "the source finished while a decoder was still mid-dispatch"
4625 );
4626
4627 let drained = 1 + metrics.collect().await.iter().map(EventsBuffer::len).sum::<usize>();
4630
4631 assert_eq!(drained, 3, "every queued datagram should have been dispatched");
4633
4634 control
4635 .wait()
4636 .await
4637 .expect("every child should have stopped on its own");
4638 }
4639
4640 #[tokio::test]
4641 async fn a_source_ignoring_shutdown_is_halted_with_its_children() {
4642 let (source, _) = build_source().await;
4649 let options = TestComponentDriver::options()
4650 .with_shutdown_timeout(Duration::from_millis(500))
4651 .with_shutdown_withheld();
4652 let (control, _outputs) = run_source(options, source).await;
4653 control
4654 .supervisor()
4655 .wait_for_running_descendants(udp_descendant_count())
4656 .await;
4657
4658 let error = control
4659 .shutdown()
4660 .await
4661 .expect_err("the source should be halted once its timeout runs out");
4662 match error.downcast_ref::<SupervisorError>() {
4663 Some(SupervisorError::ShutdownTimedOut { aborted }) => assert_eq!(*aborted, udp_descendant_count() - 1),
4666 other => panic!("expected the source to be halted and counted, got {other:?}"),
4667 }
4668 }
4669
4670 #[tokio::test]
4671 async fn stream_handlers_are_the_listeners_own_children() {
4672 let tcp_listener = leased_listener(ListenAddress::Tcp("127.0.0.1:0".parse().unwrap())).await;
4678 let listen_addr = match tcp_listener.bound_listen_address() {
4679 BoundListenAddress::Tcp(addr) => addr,
4680 other => panic!("expected a bound TCP address, got {other:?}"),
4681 };
4682
4683 let (mut source, _) = build_source().await;
4684 source.listeners = vec![tcp_listener];
4685 let (control, _outputs) = run_source(TestComponentDriver::options(), source).await;
4686
4687 let baseline = 1 + 1 + DECODER_WORKERS + 1;
4689 control.supervisor().wait_for_running_descendants(baseline).await;
4690
4691 let _client = tokio::net::TcpStream::connect(listen_addr)
4692 .await
4693 .expect("client should connect");
4694
4695 control.supervisor().wait_for_running_descendants(baseline + 2).await;
4698 let root = control.supervisor().tree_handle().snapshot().root;
4699 let listener_node = find_node(&root, "listener_tcp").expect("the listener is running");
4700 let handler_node = find_node(listener_node, "conn_tcp")
4701 .expect("the handler should hang beneath the listener that accepted its connection");
4702 assert!(
4703 find_node(handler_node, "stream_reader").is_some(),
4704 "the stream reader should hang beneath the handler that started it"
4705 );
4706
4707 control
4708 .shutdown()
4709 .await
4710 .expect("every child should have stopped on its own");
4711 }
4712
4713 #[tokio::test]
4714 async fn the_registry_keeps_the_listener_bound_after_the_source_stops() {
4715 let registry = ResourceRegistry::new();
4725 let address = ListenAddress::udp_loopback(0);
4726
4727 let (mut source, _) = build_source().await;
4728 source.listeners = vec![leased_listener_from(®istry, address.clone()).await];
4729 let bound = source.listeners[0].bound_listen_address();
4730 let listen_addr = match bound {
4731 BoundListenAddress::Udp(addr) => addr,
4732 other => panic!("expected a bound UDP address, got {other:?}"),
4733 };
4734
4735 let (control, _outputs) = run_source(TestComponentDriver::options(), source).await;
4736 control
4737 .supervisor()
4738 .wait_for_running_descendants(udp_descendant_count())
4739 .await;
4740 control.shutdown().await.expect("the source should stop cleanly");
4741
4742 let mut listener = leased_listener_from(®istry, address).await;
4744 assert_eq!(listener.bound_listen_address(), bound);
4745
4746 let mut stream = timeout(RUN_TIMEOUT, listener.accept())
4749 .await
4750 .expect("accept should not hang once the source has released its lease")
4751 .expect("listener should yield its socket");
4752
4753 let client = UdpSocket::bind("127.0.0.1:0").await.expect("client should bind");
4754 client
4755 .send_to(b"after.teardown:1|c", listen_addr)
4756 .await
4757 .expect("client should send");
4758
4759 let mut buf = BytesMut::with_capacity(64);
4760 let (read, _) = timeout(RUN_TIMEOUT, stream.receive(&mut buf))
4761 .await
4762 .expect("receive should not time out")
4763 .expect("receive should succeed");
4764 assert_eq!(&buf[..read], b"after.teardown:1|c");
4765 }
4766
4767 #[cfg(unix)]
4773 struct ListenerPaths {
4774 _temp_dir: tempfile::TempDir,
4775 datagram: PathBuf,
4776 stream: PathBuf,
4777 }
4778
4779 #[cfg(unix)]
4780 impl ListenerPaths {
4781 fn new() -> Self {
4782 let temp_dir = tempfile::tempdir().expect("temp directory should be created");
4783 Self {
4784 datagram: temp_dir.path().join("dogstatsd.socket"),
4785 stream: temp_dir.path().join("dogstatsd-stream.socket"),
4786 _temp_dir: temp_dir,
4787 }
4788 }
4789
4790 fn configure(&self, requirement: ListenerRequirement) -> DogStatsDConfiguration {
4792 let mut config = listenerless_config(requirement);
4793 config.socket_path = Some(self.datagram.to_string_lossy().into_owned());
4794 config.socket_stream_path = Some(self.stream.to_string_lossy().into_owned());
4795 config
4796 }
4797 }
4798
4799 #[cfg(unix)]
4800 const LISTENER_REQUIREMENTS: [ListenerRequirement; 3] = [
4801 ListenerRequirement::All,
4802 ListenerRequirement::AtLeastOne,
4803 ListenerRequirement::Optional,
4804 ];
4805
4806 #[cfg(unix)]
4807 fn listenerless_config(requirement: ListenerRequirement) -> DogStatsDConfiguration {
4808 let mut config = DogStatsDConfiguration::for_test();
4809 config.port = 0;
4811 config.listener_requirement = requirement;
4812 config
4813 }
4814
4815 #[cfg(unix)]
4816 type DriveResult = Result<(ComponentControl<NoInput>, Outputs<EventsBuffer>), GenericError>;
4817
4818 #[cfg(unix)]
4819 async fn drive_source(registry: ResourceRegistry, config: DogStatsDConfiguration) -> DriveResult {
4820 TestComponentDriver::options()
4821 .with_component_id(COMPONENT_ID)
4822 .with_resource_registry(registry)
4823 .source(config)
4824 .await
4825 }
4826
4827 #[cfg(unix)]
4828 async fn assert_runs_idle(result: DriveResult, requirement: ListenerRequirement) {
4829 let (control, _outputs) = result
4830 .unwrap_or_else(|error| panic!("the source should build without listeners ({requirement:?}): {error:#}"));
4831 control.wait_until_ready().await;
4832 assert!(
4833 !control.is_finished(),
4834 "the source should keep running without listeners ({requirement:?})"
4835 );
4836 control.shutdown().await.expect("the source should stop cleanly");
4837 }
4838
4839 #[cfg(unix)]
4840 fn assert_fails_to_start(result: DriveResult, requirement: ListenerRequirement, expected: &str) {
4841 let Err(error) = result else {
4842 panic!("the component build should fail ({requirement:?})");
4843 };
4844 let error = format!("{error:#}");
4845 assert!(error.contains(expected), "unexpected error ({requirement:?}): {error}");
4846 }
4847
4848 #[cfg(unix)]
4849 const NO_LISTENERS: &str = "No listeners could be created";
4850 #[cfg(unix)]
4851 const ALREADY_LEASED: &str = "already leased";
4852
4853 #[cfg(unix)]
4854 #[tokio::test]
4855 async fn zero_io_buffer_capacity_fails_to_start_without_listeners() {
4856 let mut config = listenerless_config(ListenerRequirement::Optional);
4859 config.buffer_count = 0;
4860 config.buffer_count_max = 0;
4861
4862 let result = drive_source(ResourceRegistry::new(), config).await;
4863 assert_fails_to_start(
4864 result,
4865 ListenerRequirement::Optional,
4866 "maximum I/O buffer count must be greater than zero",
4867 );
4868 }
4869
4870 #[cfg(unix)]
4871 #[tokio::test]
4872 async fn no_configured_listener_runs_idle_only_when_listeners_are_optional() {
4873 for requirement in LISTENER_REQUIREMENTS {
4874 let result = drive_source(ResourceRegistry::new(), listenerless_config(requirement)).await;
4875 match requirement {
4876 ListenerRequirement::All | ListenerRequirement::AtLeastOne => {
4877 assert_fails_to_start(result, requirement, NO_LISTENERS)
4878 }
4879 ListenerRequirement::Optional => assert_runs_idle(result, requirement).await,
4880 }
4881 }
4882 }
4883
4884 #[cfg(unix)]
4885 #[tokio::test]
4886 async fn no_created_listener_runs_idle_only_when_listeners_are_optional() {
4887 for requirement in LISTENER_REQUIREMENTS {
4889 let paths = ListenerPaths::new();
4890 let registry = ResourceRegistry::new();
4891 let _held_datagram = leased_listener_from(®istry, ListenAddress::Unixgram(paths.datagram.clone())).await;
4892 let _held_stream = leased_listener_from(®istry, ListenAddress::Unix(paths.stream.clone())).await;
4893
4894 let result = drive_source(registry, paths.configure(requirement)).await;
4895 match requirement {
4896 ListenerRequirement::All => assert_fails_to_start(result, requirement, ALREADY_LEASED),
4897 ListenerRequirement::AtLeastOne => assert_fails_to_start(result, requirement, NO_LISTENERS),
4898 ListenerRequirement::Optional => assert_runs_idle(result, requirement).await,
4899 }
4900 }
4901 }
4902
4903 #[cfg(unix)]
4904 #[tokio::test]
4905 async fn listener_that_cannot_be_created_is_skipped_unless_all_are_required() {
4906 for requirement in LISTENER_REQUIREMENTS {
4908 let paths = ListenerPaths::new();
4909 let registry = ResourceRegistry::new();
4910 let _held_stream = leased_listener_from(®istry, ListenAddress::Unix(paths.stream.clone())).await;
4911
4912 let result = drive_source(registry, paths.configure(requirement)).await;
4913 if requirement == ListenerRequirement::All {
4914 assert_fails_to_start(result, requirement, ALREADY_LEASED);
4915 continue;
4916 }
4917
4918 let (control, mut outputs) = result.unwrap_or_else(|error| {
4919 panic!("the source should build with the listener it could create ({requirement:?}): {error:#}")
4920 });
4921 control.wait_until_ready().await;
4922 assert_datagram_listener_receives(&mut outputs, &paths.datagram).await;
4923 control.shutdown().await.expect("the source should stop cleanly");
4924 }
4925 }
4926
4927 #[cfg(unix)]
4928 #[tokio::test]
4929 async fn unresolvable_bind_host_skips_only_udp_and_tcp_unless_all_are_required() {
4930 for requirement in LISTENER_REQUIREMENTS {
4932 let paths = ListenerPaths::new();
4933 let mut config = listenerless_config(requirement);
4934 config.port = 8125;
4935 config.bind_host = Some("unresolvable.invalid".to_string());
4936 config.socket_path = Some(paths.datagram.to_string_lossy().into_owned());
4937
4938 let result = drive_source(ResourceRegistry::new(), config).await;
4939 if requirement == ListenerRequirement::All {
4940 assert_fails_to_start(result, requirement, "Could not resolve bind_host");
4941 continue;
4942 }
4943
4944 let (control, mut outputs) = result.unwrap_or_else(|error| {
4945 panic!("the source should build with the socket listener ({requirement:?}): {error:#}")
4946 });
4947 control.wait_until_ready().await;
4948 assert_datagram_listener_receives(&mut outputs, &paths.datagram).await;
4949 control.shutdown().await.expect("the source should stop cleanly");
4950 }
4951 }
4952
4953 #[cfg(unix)]
4955 async fn assert_datagram_listener_receives(outputs: &mut Outputs<EventsBuffer>, path: &std::path::Path) {
4956 let sender = std::os::unix::net::UnixDatagram::unbound().expect("sender should be created");
4957 sender
4958 .send_to(b"survivor:1|c", path)
4959 .expect("the datagram listener should accept a metric");
4960 let events = timeout(RUN_TIMEOUT, outputs.named_output("metrics").next())
4961 .await
4962 .expect("the metric should arrive")
4963 .expect("the metrics output should stay open");
4964 let metric = events
4965 .into_iter()
4966 .next()
4967 .and_then(|event| event.try_into_metric())
4968 .expect("the output should carry the metric");
4969 assert_eq!(metric.context().name(), "survivor");
4970 }
4971}