Skip to main content

vector/sources/statsd/
mod.rs

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/// Configuration for the `statsd` source.
50#[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)] // just used for configuration
55pub enum StatsdConfig {
56    /// Listen on TCP.
57    Tcp(TcpConfig),
58
59    /// Listen on UDP.
60    Udp(UdpConfig),
61
62    /// Listen on a Unix domain Socket (UDS).
63    #[cfg(unix)]
64    Unix(UnixConfig),
65}
66
67/// Specifies the target unit for converting incoming StatsD timing values. When set to "seconds" (the default), timing values in milliseconds (`ms`) are converted to seconds (`s`). When set to "milliseconds", the original timing values are preserved.
68#[configurable_component]
69#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)]
70#[serde(rename_all = "lowercase")]
71pub enum ConversionUnit {
72    /// Convert to seconds.
73    #[default]
74    Seconds,
75
76    /// Convert to milliseconds.
77    Milliseconds,
78}
79
80/// UDP configuration for the `statsd` source.
81#[configurable_component]
82#[derive(Clone, Debug)]
83pub struct UdpConfig {
84    #[configurable(derived)]
85    address: SocketListenAddr,
86
87    /// The size of the receive buffer used for each connection.
88    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/// TCP configuration for the `statsd` source.
111#[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    /// The timeout before a connection is forcefully closed during shutdown.
129    #[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    /// The size of the receive buffer used for each connection.
135    #[configurable(metadata(docs::type_unit = "bytes"))]
136    receive_buffer_bytes: Option<usize>,
137
138    /// The maximum number of TCP connections that are allowed at any given time.
139    #[configurable(metadata(docs::type_unit = "connections"))]
140    connection_limit: Option<u32>,
141
142    ///	Whether or not to sanitize incoming statsd key names. When "true", keys are sanitized by:
143    /// - "/" is replaced with "-"
144    /// - All whitespace is replaced with "_"
145    /// - All non alphanumeric characters (A-Z, a-z, 0-9, _, or -) are removed.
146    #[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_reloader: not wired for this source
223                    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            // The other modes emit a different `EventsReceived`.
269            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        // The other modes already emit BytesReceived
299        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        // Build our statsd source and then spawn it.  We use a big pipeline buffer because each
581        // packet we send has a lot of metrics per packet.  We could technically count them all up
582        // and have a more accurate number here, but honestly, who cares?  This is big enough.
583        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        // Wait like 250ms to give the sink time to start running and become ready to handle
596        // traffic.
597        //
598        // TODO: It'd be neat if we could make `ShutdownSignal` track when it was polled at least once,
599        // and then surface that (via one of the related types, maybe) somehow so we could use it as
600        // a signal for "the sink is ready, it's polled the shutdown future at least once, which
601        // means it's trying to accept connections, etc" and would be far more deterministic than this.
602        sleep(Duration::from_millis(250)).await;
603
604        // Send all of the messages.
605        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            // Space things out slightly to try to avoid dropped packets.
611            sleep(Duration::from_millis(10)).await;
612        }
613
614        // Now wait for another small period of time to make sure we've processed the messages.
615        // After that, trigger shutdown so our source closes and allows us to deterministically read
616        // everything that was in up without having to know the exact count.
617        sleep(Duration::from_millis(250)).await;
618        shutdown
619            .shutdown_all(Some(Instant::now() + Duration::from_millis(100)))
620            .await;
621
622        // Read all the events into a `MetricState`, which handles normalizing metrics and tracking
623        // cumulative values for incremental metrics, etc.  This will represent the final/cumulative
624        // values for each metric sent by the source into the pipeline.
625        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        // Build our statsd source and then spawn it.  We use a big pipeline buffer because each
675        // packet we send has a lot of metrics per packet.  We could technically count them all up
676        // and have a more accurate number here, but honestly, who cares?  This is big enough.
677        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        // Wait like 250ms to give the sink time to start running and become ready to handle
690        // traffic.
691        //
692        // TODO: It'd be neat if we could make `ShutdownSignal` track when it was polled at least once,
693        // and then surface that (via one of the related types, maybe) somehow so we could use it as
694        // a signal for "the sink is ready, it's polled the shutdown future at least once, which
695        // means it's trying to accept connections, etc" and would be far more deterministic than this.
696        sleep(Duration::from_millis(250)).await;
697
698        // Send 10 invalid statsd messages
699        for _ in 0..10 {
700            sender.send(b"invalid statsd message").await.unwrap();
701
702            // Space things out slightly to try to avoid dropped packets.
703            sleep(Duration::from_millis(10)).await;
704        }
705
706        // Now wait for another small period of time to make sure we've processed the messages.
707        // After that, trigger shutdown so our source closes and allows us to deterministically read
708        // everything that was in up without having to know the exact count.
709        sleep(Duration::from_millis(250)).await;
710        shutdown
711            .shutdown_all(Some(Instant::now() + Duration::from_millis(100)))
712            .await;
713    }
714}