Skip to content

Commit 587fccc

Browse files
committed
adjust payload size limit per transport + change default flush interval per agg mode
1 parent 1ce169e commit 587fccc

File tree

2 files changed

+146
-19
lines changed

2 files changed

+146
-19
lines changed

metrics-exporter-dogstatsd/src/builder.rs

Lines changed: 110 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use std::{net::SocketAddr, sync::Arc, time::Duration};
1+
use std::{fmt, net::SocketAddr, sync::Arc, time::Duration};
22

33
use thiserror::Error;
4+
use tracing::debug;
45

56
use crate::{
67
forwarder::{self, ForwarderConfiguration, RemoteAddr},
@@ -10,13 +11,13 @@ use crate::{
1011

1112
// Maximum data length for a UDP datagram.
1213
//
13-
// Realistically, users should basically never send payloads anywhere _near_ this large, but we're only trying to ensure
14-
// we're not about to do anything that we _know_ is technically invalid.
14+
// Realistically, users should never send payloads anywhere _near_ this large, but we're only trying to ensure we're not
15+
// about to do anything that we _know_ is technically invalid.
1516
const UDP_DATAGRAM_MAX_PAYLOAD_LEN: usize = (u16::MAX as usize) - 8;
1617

1718
const DEFAULT_WRITE_TIMEOUT: Duration = Duration::from_secs(1);
18-
const DEFAULT_MAX_PAYLOAD_LEN: usize = 8192;
19-
const DEFAULT_FLUSH_INTERVAL: Duration = Duration::from_secs(3);
19+
const DEFAULT_FLUSH_INTERVAL_CONSERVATIVE: Duration = Duration::from_secs(3);
20+
const DEFAULT_FLUSH_INTERVAL_AGGRESSIVE: Duration = Duration::from_secs(10);
2021
const DEFAULT_HISTOGRAM_RESERVOIR_SIZE: usize = 1024;
2122

2223
/// Errors that could occur while building or installing a DogStatsD recorder/exporter.
@@ -65,13 +66,31 @@ pub enum AggregationMode {
6566
Aggressive,
6667
}
6768

69+
impl AggregationMode {
70+
fn default_flush_interval(&self) -> Duration {
71+
match self {
72+
AggregationMode::Conservative => DEFAULT_FLUSH_INTERVAL_CONSERVATIVE,
73+
AggregationMode::Aggressive => DEFAULT_FLUSH_INTERVAL_AGGRESSIVE,
74+
}
75+
}
76+
}
77+
78+
impl fmt::Display for AggregationMode {
79+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80+
match self {
81+
AggregationMode::Conservative => write!(f, "conservative"),
82+
AggregationMode::Aggressive => write!(f, "aggressive"),
83+
}
84+
}
85+
}
86+
6887
/// Builder for a DogStatsD exporter.
6988
#[derive(Debug)]
7089
pub struct DogStatsDBuilder {
7190
remote_addr: RemoteAddr,
7291
write_timeout: Duration,
73-
max_payload_len: usize,
74-
flush_interval: Duration,
92+
max_payload_len: Option<usize>,
93+
flush_interval: Option<Duration>,
7594
synchronous: bool,
7695
agg_mode: AggregationMode,
7796
telemetry: bool,
@@ -81,20 +100,30 @@ pub struct DogStatsDBuilder {
81100
}
82101

83102
impl DogStatsDBuilder {
103+
fn get_max_payload_len(&self) -> usize {
104+
self.max_payload_len.unwrap_or_else(|| self.remote_addr.default_max_payload_len())
105+
}
106+
107+
fn get_flush_interval(&self) -> Duration {
108+
self.flush_interval.unwrap_or_else(|| self.agg_mode.default_flush_interval())
109+
}
110+
84111
fn validate_max_payload_len(&self) -> Result<(), BuildError> {
112+
let max_payload_len = self.get_max_payload_len();
113+
85114
if let RemoteAddr::Udp(_) = &self.remote_addr {
86-
if self.max_payload_len > UDP_DATAGRAM_MAX_PAYLOAD_LEN {
115+
if max_payload_len > UDP_DATAGRAM_MAX_PAYLOAD_LEN {
87116
return Err(BuildError::InvalidConfiguration {
88-
reason: format!("maximum payload length ({} bytes) exceeds UDP datagram maximum length ({} bytes)", self.max_payload_len, UDP_DATAGRAM_MAX_PAYLOAD_LEN),
117+
reason: format!("maximum payload length ({} bytes) exceeds UDP datagram maximum length ({} bytes)", max_payload_len, UDP_DATAGRAM_MAX_PAYLOAD_LEN),
89118
});
90119
}
91120
}
92121

93-
if self.max_payload_len > u32::MAX as usize {
122+
if max_payload_len > u32::MAX as usize {
94123
return Err(BuildError::InvalidConfiguration {
95124
reason: format!(
96125
"maximum payload length ({} bytes) exceeds theoretical upper bound ({} bytes)",
97-
self.max_payload_len,
126+
max_payload_len,
98127
u32::MAX
99128
),
100129
});
@@ -146,7 +175,7 @@ impl DogStatsDBuilder {
146175
/// Setting a higher value is likely to lead to invalid metric payloads that are discarded by the Datadog Agent when
147176
/// received.
148177
///
149-
/// Defaults to 8192 bytes.
178+
/// Defaults to 1432 bytes for UDP, and 8192 bytes for Unix domain sockets.
150179
///
151180
/// # Errors
152181
///
@@ -155,7 +184,7 @@ impl DogStatsDBuilder {
155184
mut self,
156185
max_payload_len: usize,
157186
) -> Result<Self, BuildError> {
158-
self.max_payload_len = max_payload_len;
187+
self.max_payload_len = Some(max_payload_len);
159188
self.validate_max_payload_len()?;
160189

161190
Ok(self)
@@ -193,10 +222,10 @@ impl DogStatsDBuilder {
193222
/// aggregation. A shorter interval will provide more frequent updates to the remote server, but will result in more
194223
/// network traffic and processing overhead.
195224
///
196-
/// Defaults to 3 seconds.
225+
/// Defaults to 3 seconds in conservative mode, and 10 seconds in aggressive mode.
197226
#[must_use]
198227
pub fn with_flush_interval(mut self, flush_interval: Duration) -> Self {
199-
self.flush_interval = flush_interval;
228+
self.flush_interval = Some(flush_interval);
200229
self
201230
}
202231

@@ -276,25 +305,45 @@ impl DogStatsDBuilder {
276305
pub fn build(self) -> Result<DogStatsDRecorder, BuildError> {
277306
self.validate_max_payload_len()?;
278307

308+
let max_payload_len = self.get_max_payload_len();
309+
let flush_interval = self.get_flush_interval();
310+
311+
debug!(
312+
agg_mode = %self.agg_mode,
313+
histogram_sampling = self.histogram_sampling,
314+
histogram_reservoir_size = self.histogram_reservoir_size,
315+
histograms_as_distributions = self.histograms_as_distributions,
316+
"Building DogStatsD exporter."
317+
);
279318
let state_config = StateConfiguration {
280319
agg_mode: self.agg_mode,
281320
telemetry: self.telemetry,
282321
histogram_sampling: self.histogram_sampling,
283322
histogram_reservoir_size: self.histogram_reservoir_size,
284323
histograms_as_distributions: self.histograms_as_distributions,
285324
};
325+
286326
let state = Arc::new(State::new(state_config));
287327

288328
let recorder = DogStatsDRecorder::new(Arc::clone(&state));
289329

330+
debug!(
331+
remote_addr = %self.remote_addr,
332+
max_payload_len,
333+
?flush_interval,
334+
write_timeout = ?self.write_timeout,
335+
"Building DogStatsD forwarder."
336+
);
290337
let forwarder_config = ForwarderConfiguration {
291338
remote_addr: self.remote_addr,
292-
max_payload_len: self.max_payload_len,
293-
flush_interval: self.flush_interval,
339+
max_payload_len,
340+
flush_interval,
294341
write_timeout: self.write_timeout,
295342
};
296343

297344
if self.synchronous {
345+
debug!("Spawning synchronous forwarder backend.");
346+
298347
let forwarder = forwarder::sync::Forwarder::new(forwarder_config, state);
299348

300349
std::thread::Builder::new()
@@ -330,8 +379,8 @@ impl Default for DogStatsDBuilder {
330379
DogStatsDBuilder {
331380
remote_addr: RemoteAddr::Udp(vec![SocketAddr::from(([127, 0, 0, 1], 8125))]),
332381
write_timeout: DEFAULT_WRITE_TIMEOUT,
333-
max_payload_len: DEFAULT_MAX_PAYLOAD_LEN,
334-
flush_interval: DEFAULT_FLUSH_INTERVAL,
382+
max_payload_len: None,
383+
flush_interval: None,
335384
synchronous: true,
336385
agg_mode: AggregationMode::Conservative,
337386
telemetry: true,
@@ -346,6 +395,31 @@ impl Default for DogStatsDBuilder {
346395
mod tests {
347396
use super::*;
348397

398+
#[test]
399+
fn default_flush_interval_agg_mode() {
400+
let builder =
401+
DogStatsDBuilder::default().with_aggregation_mode(AggregationMode::Conservative);
402+
assert_eq!(builder.get_flush_interval(), DEFAULT_FLUSH_INTERVAL_CONSERVATIVE);
403+
404+
let builder =
405+
DogStatsDBuilder::default().with_aggregation_mode(AggregationMode::Aggressive);
406+
assert_eq!(builder.get_flush_interval(), DEFAULT_FLUSH_INTERVAL_AGGRESSIVE);
407+
408+
let custom_flush_interval = Duration::from_millis(123456789);
409+
let builder = DogStatsDBuilder::default().with_flush_interval(custom_flush_interval);
410+
assert_eq!(builder.get_flush_interval(), custom_flush_interval);
411+
}
412+
413+
#[test]
414+
fn default_max_payload_len_udp() {
415+
let builder = DogStatsDBuilder::default()
416+
.with_remote_address("127.0.0.1:9999")
417+
.expect("address should be valid");
418+
419+
assert_eq!(builder.get_max_payload_len(), 1432);
420+
assert!(builder.build().is_ok());
421+
}
422+
349423
#[test]
350424
fn max_payload_len_exceeds_udp_max_len() {
351425
let builder =
@@ -367,6 +441,23 @@ mod tests {
367441
mod linux {
368442
use super::*;
369443

444+
#[test]
445+
fn default_max_payload_len_uds() {
446+
let builder = DogStatsDBuilder::default()
447+
.with_remote_address("unix:///tmp/dogstatsd.sock")
448+
.expect("address should be valid");
449+
450+
assert_eq!(builder.get_max_payload_len(), 8192);
451+
assert!(builder.build().is_ok());
452+
453+
let builder = DogStatsDBuilder::default()
454+
.with_remote_address("unixgram:///tmp/dogstatsd.sock")
455+
.expect("address should be valid");
456+
457+
assert_eq!(builder.get_max_payload_len(), 8192);
458+
assert!(builder.build().is_ok());
459+
}
460+
370461
#[test]
371462
fn max_payload_len_exceeds_udp_max_len_transport_change() {
372463
let builder = DogStatsDBuilder::default()

metrics-exporter-dogstatsd/src/forwarder/mod.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#[cfg(target_os = "linux")]
22
use std::path::PathBuf;
33
use std::{
4+
fmt,
45
net::{SocketAddr, ToSocketAddrs as _},
56
time::Duration,
67
};
@@ -32,6 +33,14 @@ impl RemoteAddr {
3233
RemoteAddr::Unixgram(_) => "uds",
3334
}
3435
}
36+
37+
pub(crate) fn default_max_payload_len(&self) -> usize {
38+
match self {
39+
RemoteAddr::Udp(_) => 1432,
40+
#[cfg(target_os = "linux")]
41+
RemoteAddr::Unix(_) | RemoteAddr::Unixgram(_) => 8192,
42+
}
43+
}
3544
}
3645

3746
impl<'a> TryFrom<&'a str> for RemoteAddr {
@@ -61,6 +70,33 @@ impl<'a> TryFrom<&'a str> for RemoteAddr {
6170
}
6271
}
6372

73+
impl fmt::Display for RemoteAddr {
74+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75+
match self {
76+
RemoteAddr::Udp(addrs) => {
77+
if addrs.len() == 1 {
78+
write!(f, "udp://{}", addrs[0])
79+
} else {
80+
write!(f, "udp://[")?;
81+
82+
for (idx, addr) in addrs.iter().enumerate() {
83+
if idx == 0 {
84+
write!(f, "{}", addr)?;
85+
} else {
86+
write!(f, ",{}", addr)?;
87+
}
88+
}
89+
write!(f, "]")
90+
}
91+
}
92+
#[cfg(target_os = "linux")]
93+
RemoteAddr::Unix(path) | RemoteAddr::Unixgram(path) => {
94+
write!(f, "unixgram://{}", path.display())
95+
}
96+
}
97+
}
98+
}
99+
64100
fn unknown_scheme_error_str(scheme: &str) -> String {
65101
format!("invalid scheme '{scheme}' (expected 'udp', 'unix', or 'unixgram')")
66102
}

0 commit comments

Comments
 (0)