harness/payload/dogstatsd/
common.rs

1//! Shared `DogStatsD` payload sampling: vibe, segment and number builders, tags.
2
3use rand::distr::Distribution;
4use rand::seq::IndexedRandom;
5use rand::{Rng, RngExt};
6
7use crate::rand::Boundary;
8
9/// Clean by-the-book output, or feral.
10#[derive(Clone, Copy, Debug)]
11pub enum Vibe {
12    /// Well-formed.
13    Clean,
14    /// Aberrant.
15    Feral,
16}
17
18/// Sample a per-line vibe, evenly, from `rng`.
19pub fn sample_vibe<R: Rng + ?Sized>(rng: &mut R) -> Vibe {
20    match [Vibe::Clean, Vibe::Feral].choose(rng) {
21        Some(Vibe::Feral) => Vibe::Feral,
22        _ => Vibe::Clean,
23    }
24}
25
26/// The Agent's name-legal separators, for joining name-like segments.
27pub(crate) const NAME_SEPARATORS: &[u8] = b"._- ";
28
29/// Compliant identifier segments: names, hosts, keys, source types.
30pub(crate) const COMPLIANT_WORD: &[&[u8]] = &[
31    b"adp",
32    b"dogstatsd",
33    b"requests",
34    b"latency",
35    b"errors",
36    b"count",
37    b"total",
38    b"bytes",
39    b"queue",
40    b"workers",
41];
42
43/// Aberrant identifier segments: whitespace, NUL, ill-formed and non-conforming
44/// UTF-8, and exotic Unicode. Omits the framing breakers `:` `|` `,` `#` `@`,
45/// the message-type prefixes and the empty segment.
46pub(crate) const ABERRANT_WORD: &[&[u8]] = &[
47    b" ",
48    b"\t",
49    b"\0",
50    b"\x80",                // lone continuation byte
51    b"\xc3",                // truncated two-byte lead
52    b"\xed\xa0\x80",        // UTF-16 surrogate, ill-formed UTF-8
53    b"\xc0\x80",            // overlong NUL
54    b"\xff\xfe",            // non-character bytes
55    "café".as_bytes(),      // non-conforming but valid UTF-8
56    "Ωμέγα".as_bytes(),     // Greek
57    "日本語".as_bytes(),    // CJK
58    "🦆".as_bytes(),        // emoji, non-ASCII multi-byte
59    "a\u{0301}".as_bytes(), // combining acute accent
60    "\u{200d}".as_bytes(),  // zero-width joiner
61    "\u{202e}".as_bytes(),  // right-to-left override
62    "\u{feff}".as_bytes(),  // byte-order mark / zero-width no-break space
63];
64
65/// Aberrant metric values: confirmed to parse with Go's `ParseFloat`.
66pub(crate) const ABERRANT_VALUE: &[&[u8]] = &[
67    b"0",
68    b"-0",
69    b"inf",
70    b"-inf",
71    b"+inf",
72    b"nan",
73    b"infinity",
74    b"0x1p4",
75    b"1_000",
76    b"1.",
77    b".5",
78    b"00000000000000000000000000000000000000000000000000000001.5",
79    b"3.141592653589793115997963468544185161590576171875000000000000000000000000",
80];
81
82// NOTE I have intentionally avoided TS for the time being.
83
84// NOTE `host` is excluded. `DogStatsD` promotes a `host` tag to the metric host
85// resource, emitting varying `host` instances plays hell with Pyld17
86// host-consistency check.
87const COMPLIANT_TAG_KEYS: &[&[u8]] = &[b"env", b"service", b"region", b"version", b"team", b"shard"];
88const ABERRANT_TAG_KEYS: &[&[u8]] = &[b" ", b"\0", b"\x80", b"\xc3", "café".as_bytes(), "🦆".as_bytes()];
89const COMPLIANT_TAG_VALUES: &[&[u8]] = &[
90    b"prod",
91    b"staging",
92    b"adp",
93    b"us-east-1",
94    b"eu-west-1",
95    b"1.2.3",
96    b"web01",
97    b"0",
98];
99const ABERRANT_TAG_VALUES: &[&[u8]] = &[
100    b"",
101    b":",
102    b"\xff",
103    b"\xed\xa0\x80",
104    "café".as_bytes(),
105    "🦆".as_bytes(),
106    "\u{202e}".as_bytes(),
107];
108
109/// Compact, or a cursed-but-equivalent padded encoding.
110#[derive(Clone, Copy)]
111enum Form {
112    Compact,
113    Expanded,
114}
115
116/// Extend `buf` with one item sampled from `rng`. Clean draws from `compliant`;
117/// feral chooses between compliant and aberrant.
118pub(crate) fn extend_choice<R: Rng + ?Sized>(
119    rng: &mut R, buf: &mut Vec<u8>, vibe: Vibe, compliant: &[&[u8]], aberrant: &[&[u8]],
120) {
121    let pools: &[&[&[u8]]] = match vibe {
122        Vibe::Clean => &[compliant],
123        Vibe::Feral => &[compliant, aberrant],
124    };
125    if let Some(&pool) = pools.choose(rng) {
126        if let Some(&item) = pool.choose(rng) {
127            buf.extend_from_slice(item);
128        }
129    }
130}
131
132/// Repeated-element counts (segments, tags) for clean payloads: a small body, no boundary cases.
133const ELEMENT_COUNTS_CLEAN: &[u8] = &[1, 1, 2, 2, 3, 3, 4, 5, 6];
134
135/// Repeated-element counts for feral payloads: the clean body plus a `0`/large boundary tail.
136const ELEMENT_COUNTS_FERAL: &[u8] = &[1, 1, 2, 2, 3, 3, 4, 5, 6, 0, 127, 255];
137
138fn sample_count<R: Rng + ?Sized>(rng: &mut R, vibe: Vibe) -> u8 {
139    let counts = match vibe {
140        Vibe::Clean => ELEMENT_COUNTS_CLEAN,
141        Vibe::Feral => ELEMENT_COUNTS_FERAL,
142    };
143    counts[rng.random_range(0..counts.len())]
144}
145
146/// Sample a count of segments and join them with sampled `separators`. A pool of
147/// `N` segments over a count `c` gives `N^c` results.
148pub(crate) fn write_segments<R: Rng + ?Sized>(
149    rng: &mut R, buf: &mut Vec<u8>, vibe: Vibe, compliant: &[&[u8]], aberrant: &[&[u8]], separators: &[u8],
150) {
151    let count = sample_count(rng, vibe);
152    for i in 0..count {
153        if i > 0 {
154            if let Some(&sep) = separators.choose(rng) {
155                buf.push(sep);
156            }
157        }
158        extend_choice(rng, buf, vibe, compliant, aberrant);
159    }
160}
161
162/// An identifier (name, host, key, source) built from word segments. Always at
163/// least one segment: a zero-segment count would yield an empty identifier, and
164/// the Agent rejects an empty metric name.
165pub(crate) fn write_words<R: Rng + ?Sized>(rng: &mut R, buf: &mut Vec<u8>, vibe: Vibe) {
166    let start = buf.len();
167    write_segments(rng, buf, vibe, COMPLIANT_WORD, ABERRANT_WORD, NAME_SEPARATORS);
168    if buf.len() == start {
169        extend_choice(rng, buf, vibe, COMPLIANT_WORD, ABERRANT_WORD);
170    }
171}
172
173/// Append `|<prefix><item>`, the item chosen from `rng` for the vibe.
174pub(crate) fn write_field<R: Rng + ?Sized>(
175    rng: &mut R, buf: &mut Vec<u8>, vibe: Vibe, prefix: &[u8], compliant: &[&[u8]], aberrant: &[&[u8]],
176) {
177    buf.push(b'|');
178    buf.extend_from_slice(prefix);
179    extend_choice(rng, buf, vibe, compliant, aberrant);
180}
181
182/// A vibe-sampled count of `key:value` tags joined by ','. Feral can sample a
183/// count of zero (no tags) or a large boundary count; clean stays in the small
184/// body. Clean draws compliant keys and values; feral mixes aberrant ones in,
185/// key and value independently.
186pub(crate) fn write_tags<R: Rng + ?Sized>(rng: &mut R, buf: &mut Vec<u8>, vibe: Vibe) {
187    let count = sample_count(rng, vibe);
188    for t in 0..count {
189        if t == 0 {
190            buf.extend_from_slice(b"|#");
191        } else {
192            buf.push(b',');
193        }
194        write_segments(rng, buf, vibe, COMPLIANT_TAG_KEYS, ABERRANT_TAG_KEYS, NAME_SEPARATORS);
195        buf.push(b':');
196        write_segments(
197            rng,
198            buf,
199            vibe,
200            COMPLIANT_TAG_VALUES,
201            ABERRANT_TAG_VALUES,
202            NAME_SEPARATORS,
203        );
204    }
205}
206
207/// Write `digits` to `buf` as-is, or padded with equivalent leading zeros (and
208/// trailing zeros when there is a fractional part). Same value, cursed encoding.
209pub(crate) fn write_number<R: Rng + ?Sized>(rng: &mut R, buf: &mut Vec<u8>, digits: &[u8]) {
210    match [Form::Compact, Form::Expanded].choose(rng) {
211        Some(Form::Expanded) => {
212            let (sign, rest) = match digits.first() {
213                Some(&(b'-' | b'+')) => (&digits[..1], &digits[1..]),
214                _ => (&digits[..0], digits),
215            };
216            buf.extend_from_slice(sign);
217            pad_zeros(rng, buf);
218            buf.extend_from_slice(rest);
219            let fractional = rest.contains(&b'.') && !rest.iter().any(|&c| c == b'e' || c == b'E');
220            if fractional {
221                pad_zeros(rng, buf);
222            }
223        }
224        _ => buf.extend_from_slice(digits),
225    }
226}
227
228/// Append a boundary-sampled run of '0' bytes to `buf`.
229fn pad_zeros<R: Rng + ?Sized>(rng: &mut R, buf: &mut Vec<u8>) {
230    let zeros = usize::from(Boundary::<u8>::new().sample(rng));
231    buf.resize(buf.len() + zeros, b'0');
232}