1use rand::{Rng, RngExt};
4
5use super::{get_bytes, get_tags, get_u8, put_bytes, put_tags, put_u8};
6use crate::payload::dogstatsd::common;
7
8const METRIC_TYPES: [MetricType; 6] = [
10 MetricType::Count,
11 MetricType::Gauge,
12 MetricType::Timing,
13 MetricType::Histogram,
14 MetricType::Set,
15 MetricType::Distribution,
16];
17
18const EXT_COUNTS: &[usize] = &[0, 0, 0, 0, 1, 1, 2, 3, 127, 255];
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23pub enum MetricType {
24 Count,
26 Gauge,
28 Timing,
30 Histogram,
32 Set,
34 Distribution,
36}
37
38impl MetricType {
39 fn token(self) -> &'static [u8] {
41 match self {
42 MetricType::Count => b"c",
43 MetricType::Gauge => b"g",
44 MetricType::Timing => b"ms",
45 MetricType::Histogram => b"h",
46 MetricType::Set => b"s",
47 MetricType::Distribution => b"d",
48 }
49 }
50
51 fn to_byte(self) -> u8 {
53 match self {
54 MetricType::Count => 0,
55 MetricType::Gauge => 1,
56 MetricType::Timing => 2,
57 MetricType::Histogram => 3,
58 MetricType::Set => 4,
59 MetricType::Distribution => 5,
60 }
61 }
62
63 fn from_byte(b: u8) -> Option<MetricType> {
65 Some(match b {
66 0 => MetricType::Count,
67 1 => MetricType::Gauge,
68 2 => MetricType::Timing,
69 3 => MetricType::Histogram,
70 4 => MetricType::Set,
71 5 => MetricType::Distribution,
72 _ => return None,
73 })
74 }
75
76 fn is_set(self) -> bool {
78 matches!(self, MetricType::Set)
79 }
80}
81
82#[derive(Clone, Debug, PartialEq, Eq, Hash)]
84pub struct MetricContext {
85 pub kind: MetricType,
87 pub name: Vec<u8>,
89 pub tags: Vec<Vec<u8>>,
91}
92
93impl MetricContext {
94 pub(crate) fn mint_within(rng: &mut (impl Rng + ?Sized), budget: usize) -> Option<Self> {
98 let kind = METRIC_TYPES[rng.random_range(0..METRIC_TYPES.len())];
99 let reserved = 1 + common::min_value_token() + 1 + kind.token().len();
101 let name = common::identifier_within(rng, budget.checked_sub(reserved)?);
102 if name.is_empty() {
103 return None;
104 }
105 let tags = common::tags_within(rng, budget - reserved - name.len());
106 Some(Self { kind, name, tags })
107 }
108
109 pub(crate) fn floor(&self) -> usize {
112 self.fixed() + common::min_value_token()
113 }
114
115 fn fixed(&self) -> usize {
117 self.name.len() + 1 + 1 + self.kind.token().len() + common::tags_len(&self.tags)
118 }
119
120 pub(crate) fn render_within(
124 &self, rng: &mut (impl Rng + ?Sized), out: &mut Vec<u8>, budget: usize,
125 ) -> Option<usize> {
126 let fixed = self.fixed();
127 let value_room = budget.checked_sub(fixed)?;
128 if value_room < common::min_value_token() {
129 return None;
130 }
131 out.extend_from_slice(&self.name);
132 out.push(b':');
133 let mut used = 0;
134 let packed = if self.kind.is_set() {
135 let value = common::opaque_value_within(rng, value_room);
136 used += value.len();
137 out.extend_from_slice(&value);
138 0
139 } else {
140 let first = common::float_token_within(rng, value_room);
141 used += first.len();
142 out.extend_from_slice(&first);
143 let mut count = 1;
144 for _ in 1..value_count(rng) {
145 let room = value_room - used;
146 let Some(token_room) = room.checked_sub(1) else {
147 break;
148 };
149 let value = common::float_token_within(rng, token_room);
150 if value.is_empty() {
151 break;
152 }
153 out.push(b':');
154 out.extend_from_slice(&value);
155 used += 1 + value.len();
156 count += 1;
157 }
158 if count > 1 {
159 count
160 } else {
161 0
162 }
163 };
164 out.push(b'|');
165 out.extend_from_slice(self.kind.token());
166 common::serialize_tags(&self.tags, out);
167 let mut room = budget - (fixed + used);
168 let ext_count = EXT_COUNTS[rng.random_range(0..EXT_COUNTS.len())];
169 for _ in 0..ext_count {
170 let Some(chunk_room) = room.checked_sub(1) else {
171 break;
172 };
173 let Some(chunk) = ext_chunk_within(rng, chunk_room) else {
174 break;
175 };
176 out.push(b'|');
177 out.extend_from_slice(&chunk);
178 room -= 1 + chunk.len();
179 }
180 Some(packed)
181 }
182
183 pub(crate) fn render(&self, rng: &mut (impl Rng + ?Sized), out: &mut Vec<u8>) -> usize {
186 out.extend_from_slice(&self.name);
187 out.push(b':');
188 let packed = if self.kind.is_set() {
189 out.extend_from_slice(&common::opaque_value(rng));
190 0
191 } else {
192 let count = value_count(rng);
193 for i in 0..count {
194 if i > 0 {
195 out.push(b':');
196 }
197 out.extend_from_slice(&common::float_token(rng));
198 }
199 if count > 1 {
200 count
201 } else {
202 0
203 }
204 };
205 out.push(b'|');
206 out.extend_from_slice(self.kind.token());
207 common::serialize_tags(&self.tags, out);
208 let ext_count = EXT_COUNTS[rng.random_range(0..EXT_COUNTS.len())];
209 for _ in 0..ext_count {
210 out.push(b'|');
211 ext_chunk(rng, out);
212 }
213 packed
214 }
215
216 pub(crate) fn encode(&self, out: &mut Vec<u8>) {
218 put_u8(out, self.kind.to_byte());
219 put_bytes(out, &self.name);
220 put_tags(out, &self.tags);
221 }
222
223 pub(crate) fn decode(buf: &[u8], pos: &mut usize) -> Option<Self> {
225 let kind = MetricType::from_byte(get_u8(buf, pos)?)?;
226 let name = get_bytes(buf, pos)?.to_vec();
227 let tags = get_tags(buf, pos)?;
228 Some(Self { kind, name, tags })
229 }
230}
231
232fn value_count(rng: &mut (impl Rng + ?Sized)) -> usize {
234 match rng.random_range(0..800u16) {
235 0..792 => 1,
236 792..796 => 2,
237 796..798 => 3,
238 798 => 4,
239 _ => 5,
240 }
241}
242
243fn ext_chunk_within(rng: &mut (impl Rng + ?Sized), budget: usize) -> Option<Vec<u8>> {
245 let prefix: &[u8] = match rng.random_range(0..4u8) {
246 0 => b"@",
247 1 => b"c:",
248 2 => b"e:",
249 _ => b"card:",
250 };
251 let body_room = budget.checked_sub(prefix.len())?;
252 let body = if prefix == b"@" {
253 common::rate_token_within(rng, body_room)?
254 } else {
255 common::optional_text_within(rng, body_room)
256 };
257 let mut chunk = prefix.to_vec();
258 chunk.extend_from_slice(&body);
259 Some(chunk)
260}
261
262fn ext_chunk(rng: &mut (impl Rng + ?Sized), out: &mut Vec<u8>) {
265 let (prefix, body): (&[u8], Vec<u8>) = match rng.random_range(0..4u8) {
266 0 => (b"@", common::rate_token(rng)),
267 1 => (b"c:", common::optional_text(rng)),
268 2 => (b"e:", common::optional_text(rng)),
269 _ => (b"card:", common::optional_text(rng)),
270 };
271 out.extend_from_slice(prefix);
272 out.extend_from_slice(&body);
273}