Skip to content

Commit b1105c3

Browse files
authored
Introduce DogStatsD payload generation (#432)
* Introduce DogStatsD payload generation This commit introduces DogStatsD payload generation into lading. It's not quite done yet but the basic arbitrary definitions are in place. Signed-off-by: Brian L. Troutwine <brian.troutwine@datadoghq.com> * Actually serialize Signed-off-by: Brian L. Troutwine <brian.troutwine@datadoghq.com> * `dog_stats_d` -> `dogstatsd` Signed-off-by: Brian L. Troutwine <brian.troutwine@datadoghq.com> * intro changelog Signed-off-by: Brian L. Troutwine <brian.troutwine@datadoghq.com> * feedback on Arbitrary use Signed-off-by: Brian L. Troutwine <brian.troutwine@datadoghq.com> Signed-off-by: Brian L. Troutwine <brian.troutwine@datadoghq.com>
1 parent e99034b commit b1105c3

9 files changed

Lines changed: 951 additions & 61 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## Fixed
99
- Fix OpenTelemetry message length calculation for some messages.
1010

11+
## [0.12.0-rc3]
12+
## Changed
13+
- **Breaking change:** Added support for DogStatsD payload.
14+
1115
## [0.12.0]
1216
## Changed
1317
- **Breaking change:** Support for Kafka generator is removed.

src/block.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ where
131131
block_chunks,
132132
labels,
133133
),
134+
payload::Config::DogStatsD => construct_block_cache_inner(
135+
&mut rng,
136+
&payload::DogStatsD::default(),
137+
block_chunks,
138+
labels,
139+
),
134140
payload::Config::Fluent => {
135141
construct_block_cache_inner(&mut rng, &payload::Fluent::default(), block_chunks, labels)
136142
}

src/payload.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use serde::Deserialize;
99
pub(crate) use apache_common::ApacheCommon;
1010
pub(crate) use ascii::Ascii;
1111
pub(crate) use datadog_logs::DatadogLog;
12+
pub(crate) use dogstatsd::DogStatsD;
1213
pub(crate) use fluent::Fluent;
1314
pub(crate) use foundationdb::FoundationDb;
1415
pub(crate) use json::Json;
@@ -23,6 +24,7 @@ mod apache_common;
2324
mod ascii;
2425
mod common;
2526
mod datadog_logs;
27+
mod dogstatsd;
2628
mod fluent;
2729
mod foundationdb;
2830
mod json;
@@ -115,6 +117,9 @@ pub enum Config {
115117
OpentelemetryLogs,
116118
/// Generates OpenTelemetry metrics
117119
OpentelemetryMetrics,
120+
/// Generates DogStatsD
121+
#[serde(rename = "dogstatsd")]
122+
DogStatsD,
118123
}
119124

120125
#[derive(Debug)]
@@ -136,6 +141,7 @@ pub(crate) enum Payload {
136141
OtelTraces(OpentelemetryTraces),
137142
OtelLogs(OpentelemetryLogs),
138143
OtelMetrics(OpentelemetryMetrics),
144+
DogStatsdD(DogStatsD),
139145
}
140146

141147
impl Payload {}
@@ -159,6 +165,7 @@ impl Serialize for Payload {
159165
Payload::OtelTraces(ser) => ser.to_bytes(rng, max_bytes, writer),
160166
Payload::OtelLogs(ser) => ser.to_bytes(rng, max_bytes, writer),
161167
Payload::OtelMetrics(ser) => ser.to_bytes(rng, max_bytes, writer),
168+
Payload::DogStatsdD(ser) => ser.to_bytes(rng, max_bytes, writer),
162169
}
163170
}
164171
}

src/payload/datadog_logs.rs

Lines changed: 5 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,19 @@
11
use std::io::Write;
22

3-
use arbitrary::{size_hint, Unstructured};
3+
use arbitrary::{size_hint, Arbitrary, Unstructured};
44
use rand::Rng;
55

66
use crate::payload::{common::AsciiStr, Error, Serialize};
77

8-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
8+
#[derive(Debug, serde::Serialize, serde::Deserialize, Arbitrary)]
99
#[serde(rename_all = "lowercase")]
1010
enum Status {
1111
Notice,
1212
Info,
1313
Warning,
1414
}
1515

16-
impl<'a> arbitrary::Arbitrary<'a> for Status {
17-
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
18-
let choice = u.arbitrary::<u8>()?;
19-
let res = match choice % 3 {
20-
0 => Status::Notice,
21-
1 => Status::Info,
22-
2 => Status::Warning,
23-
_ => unreachable!(),
24-
};
25-
Ok(res)
26-
}
27-
}
28-
29-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
16+
#[derive(Debug, serde::Serialize, serde::Deserialize, Arbitrary)]
3017
#[serde(rename_all = "lowercase")]
3118
enum Hostname {
3219
Alpha,
@@ -35,42 +22,15 @@ enum Hostname {
3522
Localhost,
3623
}
3724

38-
impl<'a> arbitrary::Arbitrary<'a> for Hostname {
39-
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
40-
let choice = u.arbitrary::<u8>()?;
41-
let res = match choice % 4 {
42-
0 => Hostname::Alpha,
43-
1 => Hostname::Beta,
44-
2 => Hostname::Gamma,
45-
3 => Hostname::Localhost,
46-
_ => unreachable!(),
47-
};
48-
Ok(res)
49-
}
50-
}
51-
52-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
25+
#[derive(Debug, serde::Serialize, serde::Deserialize, Arbitrary)]
5326
#[serde(rename_all = "lowercase")]
5427
enum Service {
5528
Vector,
5629
Lading,
5730
Cernan,
5831
}
5932

60-
impl<'a> arbitrary::Arbitrary<'a> for Service {
61-
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
62-
let choice = u.arbitrary::<u8>()?;
63-
let res = match choice % 3 {
64-
0 => Service::Vector,
65-
1 => Service::Lading,
66-
2 => Service::Cernan,
67-
_ => unreachable!(),
68-
};
69-
Ok(res)
70-
}
71-
}
72-
73-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
33+
#[derive(Debug, serde::Serialize, serde::Deserialize, Arbitrary)]
7434
#[serde(rename_all = "lowercase")]
7535
enum Source {
7636
Bergman,
@@ -81,22 +41,6 @@ enum Source {
8141
Tarkovsky,
8242
}
8343

84-
impl<'a> arbitrary::Arbitrary<'a> for Source {
85-
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
86-
let choice = u.arbitrary::<u8>()?;
87-
let res = match choice % 6 {
88-
0 => Source::Bergman,
89-
1 => Source::Keaton,
90-
2 => Source::Kurosawa,
91-
3 => Source::Lynch,
92-
4 => Source::Waters,
93-
5 => Source::Tarkovsky,
94-
_ => unreachable!(),
95-
};
96-
Ok(res)
97-
}
98-
}
99-
10044
const TAG_OPTIONS: [&str; 4] = ["", "env:prod", "env:dev", "env:prod,version:1.1"];
10145

10246
#[derive(Debug, serde::Serialize, serde::Deserialize)]

src/payload/dogstatsd.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use std::{fmt, io::Write};
2+
3+
use arbitrary::{Arbitrary, Unstructured};
4+
use rand::Rng;
5+
6+
use crate::payload::{Error, Serialize};
7+
8+
mod common;
9+
mod event;
10+
mod metric;
11+
mod service_check;
12+
13+
// https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/
14+
#[derive(Arbitrary)]
15+
enum Member {
16+
Metric(metric::Metric),
17+
Event(event::Event),
18+
ServiceCheck(service_check::ServiceCheck),
19+
}
20+
21+
impl fmt::Display for Member {
22+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23+
match self {
24+
Self::Metric(ref m) => write!(f, "{m}"),
25+
Self::Event(ref e) => write!(f, "{e}"),
26+
Self::ServiceCheck(ref sc) => write!(f, "{sc}"),
27+
}
28+
}
29+
}
30+
31+
#[derive(Debug, Default, Clone, Copy)]
32+
#[allow(clippy::module_name_repetitions)]
33+
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
34+
pub(crate) struct DogStatsD {}
35+
36+
impl Serialize for DogStatsD {
37+
fn to_bytes<W, R>(&self, mut rng: R, max_bytes: usize, writer: &mut W) -> Result<(), Error>
38+
where
39+
R: Rng + Sized,
40+
W: Write,
41+
{
42+
let mut entropy: Vec<u8> = vec![0; max_bytes];
43+
rng.fill_bytes(&mut entropy);
44+
let mut unstructured = Unstructured::new(&entropy);
45+
46+
let mut bytes_remaining = max_bytes;
47+
while let Ok(member) = unstructured.arbitrary::<Member>() {
48+
let encoding = format!("{member}");
49+
let line_length = encoding.len() + 1; // add one for the newline
50+
match bytes_remaining.checked_sub(line_length) {
51+
Some(remainder) => {
52+
writeln!(writer, "{encoding}")?;
53+
bytes_remaining = remainder;
54+
}
55+
None => break,
56+
}
57+
}
58+
Ok(())
59+
}
60+
}
61+
62+
#[cfg(test)]
63+
mod test {
64+
use proptest::prelude::*;
65+
use rand::{rngs::SmallRng, SeedableRng};
66+
67+
use crate::payload::{DogStatsD, Serialize};
68+
69+
// We want to be sure that the serialized size of the payload does not
70+
// exceed `max_bytes`.
71+
proptest! {
72+
#[test]
73+
fn payload_not_exceed_max_bytes(seed: u64, max_bytes: u16) {
74+
let max_bytes = max_bytes as usize;
75+
let rng = SmallRng::seed_from_u64(seed);
76+
let dogstatsd = DogStatsD::default();
77+
78+
let mut bytes = Vec::with_capacity(max_bytes);
79+
dogstatsd.to_bytes(rng, max_bytes, &mut bytes).unwrap();
80+
debug_assert!(
81+
bytes.len() <= max_bytes,
82+
"{:?}",
83+
std::str::from_utf8(&bytes).unwrap()
84+
);
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)