1use std::{
2 net::{Ipv4Addr, SocketAddr, SocketAddrV4},
3 time::Duration,
4};
5
6use bytes::Bytes;
7use futures::{StreamExt, TryFutureExt};
8use listenfd::ListenFd;
9use serde_with::serde_as;
10use smallvec::{SmallVec, smallvec};
11use tokio_util::udp::UdpFramed;
12use vector_lib::{
13 EstimatedJsonEncodedSizeOf,
14 codecs::{
15 NewlineDelimitedDecoder,
16 decoding::{self, Deserializer, Framer},
17 },
18 configurable::configurable_component,
19 internal_event::{CountByteSize, InternalEventHandle as _, Registered},
20 ipallowlist::IpAllowlistConfig,
21};
22
23use self::parser::ParseError;
24use super::util::net::{SocketListenAddr, TcpNullAcker, TcpSource, try_bind_udp_socket};
25use crate::{
26 SourceSender,
27 codecs::Decoder,
28 config::{GenerateConfig, Resource, SourceConfig, SourceContext, SourceOutput},
29 event::Event,
30 internal_events::{
31 EventsReceived, SocketBindError, SocketBytesReceived, SocketMode, SocketReceiveError,
32 StreamClosedError,
33 },
34 net,
35 shutdown::ShutdownSignal,
36 tcp::TcpKeepaliveConfig,
37 tls::{MaybeTlsSettings, TlsSourceConfig},
38};
39
40pub mod parser;
41#[cfg(unix)]
42mod unix;
43
44use parser::Parser;
45#[cfg(unix)]
46use unix::{UnixConfig, statsd_unix};
47use vector_lib::config::LogNamespace;
48
49#[configurable_component(source("statsd", "Collect metrics emitted by the StatsD aggregator."))]
51#[derive(Clone, Debug)]
52#[serde(tag = "mode", rename_all = "snake_case")]
53#[configurable(metadata(docs::enum_tag_description = "The type of socket to use."))]
54#[allow(clippy::large_enum_variant)] pub enum StatsdConfig {
56 Tcp(TcpConfig),
58
59 Udp(UdpConfig),
61
62 #[cfg(unix)]
64 Unix(UnixConfig),
65}
66
67#[configurable_component]
69#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)]
70#[serde(rename_all = "lowercase")]
71pub enum ConversionUnit {
72 #[default]
74 Seconds,
75
76 Milliseconds,
78}
79
80#[configurable_component]
82#[derive(Clone, Debug)]
83pub struct UdpConfig {
84 #[configurable(derived)]
85 address: SocketListenAddr,
86
87 receive_buffer_bytes: Option<usize>,
89
90 #[serde(default = "default_sanitize")]
91 #[configurable(derived)]
92 sanitize: bool,
93
94 #[serde(default = "default_convert_to")]
95 #[configurable(derived)]
96 convert_to: ConversionUnit,
97}
98
99impl UdpConfig {
100 pub const fn from_address(address: SocketListenAddr) -> Self {
101 Self {
102 address,
103 receive_buffer_bytes: None,
104 sanitize: default_sanitize(),
105 convert_to: default_convert_to(),
106 }
107 }
108}
109
110#[serde_as]
112#[configurable_component]
113#[derive(Clone, Debug)]
114pub struct TcpConfig {
115 #[configurable(derived)]
116 address: SocketListenAddr,
117
118 #[configurable(derived)]
119 keepalive: Option<TcpKeepaliveConfig>,
120
121 #[configurable(derived)]
122 pub permit_origin: Option<IpAllowlistConfig>,
123
124 #[configurable(derived)]
125 #[serde(default)]
126 tls: Option<TlsSourceConfig>,
127
128 #[serde(default = "default_shutdown_timeout_secs")]
130 #[serde_as(as = "serde_with::DurationSeconds<u64>")]
131 #[configurable(metadata(docs::human_name = "Shutdown Timeout"))]
132 shutdown_timeout_secs: Duration,
133
134 #[configurable(metadata(docs::type_unit = "bytes"))]
136 receive_buffer_bytes: Option<usize>,
137
138 #[configurable(metadata(docs::type_unit = "connections"))]
140 connection_limit: Option<u32>,
141
142 #[serde(default = "default_sanitize")]
147 #[configurable(derived)]
148 sanitize: bool,
149
150 #[serde(default = "default_convert_to")]
151 #[configurable(derived)]
152 convert_to: ConversionUnit,
153}
154
155impl TcpConfig {
156 #[cfg(test)]
157 pub const fn from_address(address: SocketListenAddr) -> Self {
158 Self {
159 address,
160 keepalive: None,
161 permit_origin: None,
162 tls: None,
163 shutdown_timeout_secs: default_shutdown_timeout_secs(),
164 receive_buffer_bytes: None,
165 connection_limit: None,
166 sanitize: default_sanitize(),
167 convert_to: default_convert_to(),
168 }
169 }
170}
171
172const fn default_shutdown_timeout_secs() -> Duration {
173 Duration::from_secs(30)
174}
175
176const fn default_sanitize() -> bool {
177 true
178}
179
180const fn default_convert_to() -> ConversionUnit {
181 ConversionUnit::Seconds
182}
183
184impl GenerateConfig for StatsdConfig {
185 fn generate_config() -> toml::Value {
186 toml::Value::try_from(Self::Udp(UdpConfig::from_address(
187 SocketListenAddr::SocketAddr(SocketAddr::V4(SocketAddrV4::new(
188 Ipv4Addr::LOCALHOST,
189 8125,
190 ))),
191 )))
192 .unwrap()
193 }
194}
195
196#[async_trait::async_trait]
197#[typetag::serde(name = "statsd")]
198impl SourceConfig for StatsdConfig {
199 async fn build(&self, cx: SourceContext) -> crate::Result<super::Source> {
200 match self {
201 StatsdConfig::Udp(config) => {
202 Ok(Box::pin(statsd_udp(config.clone(), cx.shutdown, cx.out)))
203 }
204 StatsdConfig::Tcp(config) => {
205 let tls_config = config.tls.as_ref().map(|tls| tls.tls_config.clone());
206 let tls_client_metadata_key = config
207 .tls
208 .as_ref()
209 .and_then(|tls| tls.client_metadata_key.clone())
210 .and_then(|k| k.path);
211 let tls = MaybeTlsSettings::from_config(tls_config.as_ref(), true)?;
212 let statsd_tcp_source = StatsdTcpSource {
213 sanitize: config.sanitize,
214 convert_to: config.convert_to,
215 };
216
217 statsd_tcp_source.run(
218 config.address,
219 config.keepalive,
220 config.shutdown_timeout_secs,
221 tls,
222 None, tls_client_metadata_key,
224 config.receive_buffer_bytes,
225 None,
226 cx,
227 false.into(),
228 config.connection_limit,
229 config.permit_origin.clone().map(Into::into),
230 StatsdConfig::NAME,
231 LogNamespace::Legacy,
232 )
233 }
234 #[cfg(unix)]
235 StatsdConfig::Unix(config) => statsd_unix(config.clone(), cx.shutdown, cx.out),
236 }
237 }
238
239 fn outputs(&self, _global_log_namespace: LogNamespace) -> Vec<SourceOutput> {
240 vec![SourceOutput::new_metrics()]
241 }
242
243 fn resources(&self) -> Vec<Resource> {
244 match self.clone() {
245 Self::Tcp(tcp) => vec![tcp.address.as_tcp_resource()],
246 Self::Udp(udp) => vec![udp.address.as_udp_resource()],
247 #[cfg(unix)]
248 Self::Unix(_) => vec![],
249 }
250 }
251
252 fn can_acknowledge(&self) -> bool {
253 false
254 }
255}
256
257#[derive(Clone)]
258pub(crate) struct StatsdDeserializer {
259 socket_mode: Option<SocketMode>,
260 events_received: Option<Registered<EventsReceived>>,
261 parser: Parser,
262}
263
264impl StatsdDeserializer {
265 pub fn udp(sanitize: bool, convert_to: ConversionUnit) -> Self {
266 Self {
267 socket_mode: Some(SocketMode::Udp),
268 events_received: Some(register!(EventsReceived)),
270 parser: Parser::new(sanitize, convert_to),
271 }
272 }
273
274 pub const fn tcp(sanitize: bool, convert_to: ConversionUnit) -> Self {
275 Self {
276 socket_mode: None,
277 events_received: None,
278 parser: Parser::new(sanitize, convert_to),
279 }
280 }
281
282 #[cfg(unix)]
283 pub const fn unix(sanitize: bool, convert_to: ConversionUnit) -> Self {
284 Self {
285 socket_mode: Some(SocketMode::Unix),
286 events_received: None,
287 parser: Parser::new(sanitize, convert_to),
288 }
289 }
290}
291
292impl decoding::format::Deserializer for StatsdDeserializer {
293 fn parse(
294 &self,
295 bytes: Bytes,
296 _log_namespace: LogNamespace,
297 ) -> crate::Result<SmallVec<[Event; 1]>> {
298 if let Some(mode) = self.socket_mode
300 && mode == SocketMode::Udp
301 {
302 emit!(SocketBytesReceived {
303 mode,
304 byte_size: bytes.len(),
305 });
306 }
307
308 match std::str::from_utf8(&bytes).map_err(ParseError::InvalidUtf8) {
309 Err(error) => Err(Box::new(error)),
310 Ok(s) => match self.parser.parse(s) {
311 Ok(metric) => {
312 let event = Event::Metric(metric);
313 if let Some(er) = &self.events_received {
314 let byte_size = event.estimated_json_encoded_size_of();
315 er.emit(CountByteSize(1, byte_size));
316 }
317 Ok(smallvec![event])
318 }
319 Err(error) => Err(Box::new(error)),
320 },
321 }
322 }
323}
324
325async fn statsd_udp(
326 config: UdpConfig,
327 shutdown: ShutdownSignal,
328 mut out: SourceSender,
329) -> Result<(), ()> {
330 let listenfd = ListenFd::from_env();
331 let socket = try_bind_udp_socket(config.address, listenfd)
332 .map_err(|error| {
333 emit!(SocketBindError {
334 mode: SocketMode::Udp,
335 error
336 })
337 })
338 .await?;
339
340 if let Some(receive_buffer_bytes) = config.receive_buffer_bytes
341 && let Err(error) = net::set_receive_buffer_size(&socket, receive_buffer_bytes)
342 {
343 warn!(message = "Failed configuring receive buffer size on UDP socket.", %error);
344 }
345
346 info!(
347 message = "Listening.",
348 addr = %config.address,
349 r#type = "udp"
350 );
351
352 let codec = Decoder::new(
353 Framer::NewlineDelimited(NewlineDelimitedDecoder::new()),
354 Deserializer::Boxed(Box::new(StatsdDeserializer::udp(
355 config.sanitize,
356 config.convert_to,
357 ))),
358 );
359 let mut stream = UdpFramed::new(socket, codec).take_until(shutdown);
360 while let Some(frame) = stream.next().await {
361 match frame {
362 Ok(((events, _byte_size), _sock)) => {
363 let count = events.len();
364 if (out.send_batch(events).await).is_err() {
365 emit!(StreamClosedError { count });
366 }
367 }
368 Err(error) => {
369 emit!(SocketReceiveError {
370 mode: SocketMode::Udp,
371 error
372 });
373 }
374 }
375 }
376
377 Ok(())
378}
379
380#[derive(Clone)]
381struct StatsdTcpSource {
382 sanitize: bool,
383 convert_to: ConversionUnit,
384}
385
386impl TcpSource for StatsdTcpSource {
387 type Error = vector_lib::codecs::decoding::Error;
388 type Item = SmallVec<[Event; 1]>;
389 type Decoder = Decoder;
390 type Acker = TcpNullAcker;
391
392 fn decoder(&self) -> Self::Decoder {
393 Decoder::new(
394 Framer::NewlineDelimited(NewlineDelimitedDecoder::new()),
395 Deserializer::Boxed(Box::new(StatsdDeserializer::tcp(
396 self.sanitize,
397 self.convert_to,
398 ))),
399 )
400 }
401
402 fn build_acker(&self, _: &[Self::Item]) -> Self::Acker {
403 TcpNullAcker
404 }
405}
406
407#[cfg(test)]
408mod test {
409 use futures::channel::mpsc;
410 use futures_util::SinkExt;
411 use tokio::{
412 io::AsyncWriteExt,
413 net::UdpSocket,
414 time::{Duration, Instant, sleep},
415 };
416 use vector_lib::{
417 config::ComponentKey,
418 event::{EventContainer, metric::TagValue},
419 };
420
421 use super::*;
422 use crate::{
423 series,
424 test_util::{
425 addr::next_addr,
426 collect_limited,
427 components::{
428 COMPONENT_ERROR_TAGS, SOCKET_PUSH_SOURCE_TAGS, assert_source_compliance,
429 assert_source_error,
430 },
431 metrics::{
432 AbsoluteMetricState, assert_counter, assert_distribution, assert_gauge, assert_set,
433 },
434 },
435 };
436
437 #[test]
438 fn generate_config() {
439 crate::test_util::test_generate_config::<StatsdConfig>();
440 }
441
442 #[tokio::test]
443 async fn test_statsd_udp() {
444 assert_source_compliance(&SOCKET_PUSH_SOURCE_TAGS, async move {
445 let (_guard, in_addr) = next_addr();
446 let config = StatsdConfig::Udp(UdpConfig::from_address(in_addr.into()));
447 let (sender, mut receiver) = mpsc::channel(200);
448 tokio::spawn(async move {
449 let (_guard, bind_addr) = next_addr();
450 let socket = UdpSocket::bind(bind_addr).await.unwrap();
451 socket.connect(in_addr).await.unwrap();
452 while let Some(bytes) = receiver.next().await {
453 socket.send(bytes).await.unwrap();
454 }
455 });
456 test_statsd(config, sender).await;
457 })
458 .await;
459 }
460
461 #[tokio::test]
462 async fn test_statsd_tcp() {
463 assert_source_compliance(&SOCKET_PUSH_SOURCE_TAGS, async move {
464 let (_guard, in_addr) = next_addr();
465 let config = StatsdConfig::Tcp(TcpConfig::from_address(in_addr.into()));
466 let (sender, mut receiver) = mpsc::channel(200);
467 tokio::spawn(async move {
468 while let Some(bytes) = receiver.next().await {
469 tokio::net::TcpStream::connect(in_addr)
470 .await
471 .unwrap()
472 .write_all(bytes)
473 .await
474 .unwrap();
475 }
476 });
477 test_statsd(config, sender).await;
478 })
479 .await;
480 }
481
482 #[tokio::test]
483 async fn test_statsd_error() {
484 assert_source_error(&COMPONENT_ERROR_TAGS, async move {
485 let (_guard, in_addr) = next_addr();
486 let config = StatsdConfig::Tcp(TcpConfig::from_address(in_addr.into()));
487 let (sender, mut receiver) = mpsc::channel(200);
488 tokio::spawn(async move {
489 while let Some(bytes) = receiver.next().await {
490 tokio::net::TcpStream::connect(in_addr)
491 .await
492 .unwrap()
493 .write_all(bytes)
494 .await
495 .unwrap();
496 }
497 });
498 test_invalid_statsd(config, sender).await;
499 })
500 .await;
501 }
502
503 #[cfg(unix)]
504 #[tokio::test]
505 async fn test_statsd_unix() {
506 assert_source_compliance(&SOCKET_PUSH_SOURCE_TAGS, async move {
507 let in_path = tempfile::tempdir().unwrap().keep().join("unix_test");
508 let config = StatsdConfig::Unix(UnixConfig {
509 path: in_path.clone(),
510 sanitize: true,
511 convert_to: ConversionUnit::Seconds,
512 });
513 let (sender, mut receiver) = mpsc::channel(200);
514 tokio::spawn(async move {
515 while let Some(bytes) = receiver.next().await {
516 tokio::net::UnixStream::connect(&in_path)
517 .await
518 .unwrap()
519 .write_all(bytes)
520 .await
521 .unwrap();
522 }
523 });
524 test_statsd(config, sender).await;
525 })
526 .await;
527 }
528
529 #[tokio::test]
530 async fn test_statsd_udp_conversion_disabled() {
531 let (_guard, in_addr) = next_addr();
532 let mut config = UdpConfig::from_address(in_addr.into());
533 config.convert_to = ConversionUnit::Milliseconds;
534 let statsd_config = StatsdConfig::Udp(config);
535 let (mut sender, mut receiver) = mpsc::channel(200);
536
537 tokio::spawn(async move {
538 let (_guard, bind_addr) = next_addr();
539 let socket = UdpSocket::bind(bind_addr).await.unwrap();
540 socket.connect(in_addr).await.unwrap();
541 while let Some(bytes) = receiver.next().await {
542 socket.send(bytes).await.unwrap();
543 }
544 });
545
546 let component_key = ComponentKey::from("statsd_conversion_disabled");
547 let (tx, rx) = SourceSender::new_test_sender_with_options(4096, None);
548 let (source_ctx, shutdown) = SourceContext::new_shutdown(&component_key, tx);
549 let sink = statsd_config
550 .build(source_ctx)
551 .await
552 .expect("failed to build source");
553
554 tokio::spawn(async move {
555 sink.await.expect("sink should not fail");
556 });
557
558 sleep(Duration::from_millis(250)).await;
559 sender.send(b"timer:320|ms|@0.1\n").await.unwrap();
560 sleep(Duration::from_millis(250)).await;
561 shutdown
562 .shutdown_all(Some(Instant::now() + Duration::from_millis(100)))
563 .await;
564 let state = collect_limited(rx)
565 .await
566 .into_iter()
567 .flat_map(EventContainer::into_events)
568 .collect::<AbsoluteMetricState>();
569 let metrics = state.finish();
570 assert_distribution(
571 &metrics,
572 series!("timer"),
573 3200.0,
574 10,
575 &[(1.0, 0), (2.0, 0), (4.0, 0), (f64::INFINITY, 10)],
576 );
577 }
578
579 async fn test_statsd(statsd_config: StatsdConfig, mut sender: mpsc::Sender<&'static [u8]>) {
580 let component_key = ComponentKey::from("statsd");
584 let (tx, rx) = SourceSender::new_test_sender_with_options(4096, None);
585 let (source_ctx, shutdown) = SourceContext::new_shutdown(&component_key, tx);
586 let sink = statsd_config
587 .build(source_ctx)
588 .await
589 .expect("failed to build statsd source");
590
591 tokio::spawn(async move {
592 sink.await.expect("sink should not fail");
593 });
594
595 sleep(Duration::from_millis(250)).await;
603
604 for _ in 0..100 {
606 sender.send(
607 b"foo:1|c|#a,b:b\nbar:42|g\nfoo:1|c|#a,b:c\nglork:3|h|@0.1\nmilliglork:3000|ms|@0.2\nset:0|s\nset:1|s\n"
608 ).await.unwrap();
609
610 sleep(Duration::from_millis(10)).await;
612 }
613
614 sleep(Duration::from_millis(250)).await;
618 shutdown
619 .shutdown_all(Some(Instant::now() + Duration::from_millis(100)))
620 .await;
621
622 let state = collect_limited(rx)
626 .await
627 .into_iter()
628 .flat_map(EventContainer::into_events)
629 .collect::<AbsoluteMetricState>();
630 let metrics = state.finish();
631
632 assert_counter(
633 &metrics,
634 series!(
635 "foo",
636 "a" => TagValue::Bare,
637 "b" => "b"
638 ),
639 100.0,
640 );
641
642 assert_counter(
643 &metrics,
644 series!(
645 "foo",
646 "a" => TagValue::Bare,
647 "b" => "c"
648 ),
649 100.0,
650 );
651
652 assert_gauge(&metrics, series!("bar"), 42.0);
653 assert_distribution(
654 &metrics,
655 series!("glork"),
656 3000.0,
657 1000,
658 &[(1.0, 0), (2.0, 0), (4.0, 1000), (f64::INFINITY, 1000)],
659 );
660 assert_distribution(
661 &metrics,
662 series!("milliglork"),
663 1500.0,
664 500,
665 &[(1.0, 0), (2.0, 0), (4.0, 500), (f64::INFINITY, 500)],
666 );
667 assert_set(&metrics, series!("set"), &["0", "1"]);
668 }
669
670 async fn test_invalid_statsd(
671 statsd_config: StatsdConfig,
672 mut sender: mpsc::Sender<&'static [u8]>,
673 ) {
674 let component_key = ComponentKey::from("statsd");
678 let (tx, _rx) = SourceSender::new_test_sender_with_options(4096, None);
679 let (source_ctx, shutdown) = SourceContext::new_shutdown(&component_key, tx);
680 let sink = statsd_config
681 .build(source_ctx)
682 .await
683 .expect("failed to build statsd source");
684
685 tokio::spawn(async move {
686 sink.await.expect("sink should not fail");
687 });
688
689 sleep(Duration::from_millis(250)).await;
697
698 for _ in 0..10 {
700 sender.send(b"invalid statsd message").await.unwrap();
701
702 sleep(Duration::from_millis(10)).await;
704 }
705
706 sleep(Duration::from_millis(250)).await;
710 shutdown
711 .shutdown_all(Some(Instant::now() + Duration::from_millis(100)))
712 .await;
713 }
714}