1use super::http;
12
13pub const PROTOBUF_CONTENT_TYPE: &str = "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited";
15
16pub fn accepts_protobuf(accept: &str) -> bool {
18 accept.split(',').any(|range| {
19 let mut params = range.split(';').map(str::trim);
20 if !params.next().is_some_and(|t| t.eq_ignore_ascii_case("application/vnd.google.protobuf")) {
21 return false;
22 }
23 let mut message = false;
24 let mut q = 1.0;
25 for param in params {
26 match param.split_once('=') {
27 Some(("proto", v)) => message = v == "io.prometheus.client.MetricFamily",
28 Some(("q", v)) => q = v.parse().unwrap_or(0.0),
29 _ => {}
30 }
31 }
32 message && q > 0.0
33 })
34}
35
36#[derive(Clone, Copy, Debug, PartialEq)]
37pub(crate) enum Kind {
38 Counter = 0,
39 Gauge = 1,
40 Untyped = 3,
41 Histogram = 4,
42}
43
44#[derive(Clone, Debug, PartialEq, Default)]
45pub(crate) struct ClassicHistogram {
46 pub(crate) count: u64,
47 pub(crate) sum: f64,
48 pub(crate) buckets: Vec<(f64, u64)>,
50}
51
52#[derive(Clone, Debug, PartialEq)]
53pub(crate) enum Value {
54 Number(f64),
55 Histogram(ClassicHistogram, Option<http::Histogram>),
56}
57
58#[derive(Clone, Debug, PartialEq)]
59pub(crate) struct Sample {
60 pub(crate) labels: Vec<(String, String)>,
61 pub(crate) value: Value,
62}
63
64#[derive(Clone, Debug, PartialEq)]
65pub(crate) struct Family {
66 pub(crate) name: String,
67 pub(crate) help: String,
68 pub(crate) kind: Kind,
69 pub(crate) samples: Vec<Sample>,
70}
71
72pub fn protobuf() -> Vec<u8> {
74 let mut families = parse_text(&super::prometheus_text_without_http());
75 families.push(http_family());
76 families.retain(|f| !f.samples.is_empty());
77 encode(&families)
78}
79
80fn http_family() -> Family {
81 let samples = http::snapshot()
82 .into_iter()
83 .map(|(series, histogram)| {
84 let mut cumulative = 0;
85 let buckets = http::BOUNDS
86 .iter()
87 .enumerate()
88 .map(|(i, bound)| {
89 cumulative += histogram.counts[i];
90 (*bound, cumulative)
91 })
92 .collect();
93 let classic = ClassicHistogram { count: histogram.count, sum: histogram.sum, buckets };
94 Sample { labels: http::labels(&series), value: Value::Histogram(classic, Some(histogram)) }
95 })
96 .collect();
97 Family { name: http::NAME.to_string(), help: http::HELP.to_string(), kind: Kind::Histogram, samples }
98}
99
100pub(crate) fn parse_text(text: &str) -> Vec<Family> {
105 let mut families: Vec<Family> = Vec::new();
106 for line in text.lines() {
107 let line = line.trim();
108 if line.is_empty() {
109 continue;
110 }
111 if let Some(rest) = line.strip_prefix("# HELP ") {
112 let (name, help) = rest.split_once(' ').unwrap_or((rest, ""));
113 family_named(&mut families, name).help = help.replace("\\n", "\n").replace("\\\\", "\\");
114 continue;
115 }
116 if let Some(rest) = line.strip_prefix("# TYPE ") {
117 let (name, kind) = rest.split_once(' ').unwrap_or((rest, ""));
118 family_named(&mut families, name).kind = match kind.trim() {
119 "counter" => Kind::Counter,
120 "gauge" => Kind::Gauge,
121 "histogram" => Kind::Histogram,
122 _ => Kind::Untyped,
123 };
124 continue;
125 }
126 if line.starts_with('#') {
127 continue;
128 }
129 let Some((name, labels, value)) = parse_sample(line) else { continue };
130 add_sample(&mut families, name, labels, value);
131 }
132 families
133}
134
135fn family_named<'a>(families: &'a mut Vec<Family>, name: &str) -> &'a mut Family {
136 match families.iter().position(|f| f.name == name) {
137 Some(i) => &mut families[i],
138 None => {
139 families.push(Family { name: name.to_string(), help: String::new(), kind: Kind::Untyped, samples: Vec::new() });
140 families.last_mut().unwrap()
141 }
142 }
143}
144
145fn add_sample(families: &mut Vec<Family>, name: &str, mut labels: Vec<(String, String)>, value: f64) {
146 for suffix in ["_bucket", "_sum", "_count"] {
148 let Some(base) = name.strip_suffix(suffix) else { continue };
149 let Some(family) = families.iter_mut().find(|f| f.name == base && f.kind == Kind::Histogram) else { continue };
150 let le = labels.iter().position(|(k, _)| k == "le").map(|i| labels.remove(i).1);
151 let sample = match family.samples.iter_mut().position(|s| s.labels == labels) {
152 Some(i) => &mut family.samples[i],
153 None => {
154 family.samples.push(Sample { labels, value: Value::Histogram(ClassicHistogram::default(), None) });
155 family.samples.last_mut().unwrap()
156 }
157 };
158 let Value::Histogram(classic, _) = &mut sample.value else { return };
159 match suffix {
160 "_sum" => classic.sum = value,
161 "_count" => classic.count = value as u64,
162 _ => {
163 let bound = match le.as_deref() {
164 Some("+Inf") | None => return, Some(le) => le.parse().unwrap_or(f64::INFINITY),
166 };
167 classic.buckets.push((bound, value as u64));
168 }
169 }
170 return;
171 }
172 let family = family_named(families, name);
173 family.samples.push(Sample { labels, value: Value::Number(value) });
174}
175
176fn parse_sample(line: &str) -> Option<(&str, Vec<(String, String)>, f64)> {
178 let name_end = line.find(['{', ' '])?;
179 let name = &line[..name_end];
180 let mut labels = Vec::new();
181 let mut rest = &line[name_end..];
182 if let Some(after) = rest.strip_prefix('{') {
183 let mut chars = after.char_indices();
184 let mut key = String::new();
185 let close;
186 loop {
187 let (i, c) = chars.next()?;
188 match c {
189 '}' => {
190 close = i;
191 break;
192 }
193 ',' | ' ' => {}
194 '=' => {
195 if chars.next()?.1 != '"' {
196 return None;
197 }
198 let mut value = String::new();
199 loop {
200 match chars.next()?.1 {
201 '"' => break,
202 '\\' => match chars.next()?.1 {
203 'n' => value.push('\n'),
204 other => value.push(other),
205 },
206 other => value.push(other),
207 }
208 }
209 labels.push((std::mem::take(&mut key), value));
210 }
211 other => key.push(other),
212 }
213 }
214 rest = &after[close + 1..];
215 }
216 let value = rest.split_whitespace().next()?;
217 let value = match value {
218 "+Inf" => f64::INFINITY,
219 "-Inf" => f64::NEG_INFINITY,
220 "NaN" => f64::NAN,
221 v => v.parse().ok()?,
222 };
223 Some((name, labels, value))
224}
225
226pub(crate) fn encode(families: &[Family]) -> Vec<u8> {
229 let mut out = Vec::new();
230 for family in families {
231 let mut message = Vec::new();
232 string_field(&mut message, 1, &family.name);
233 if !family.help.is_empty() {
234 string_field(&mut message, 2, &family.help);
235 }
236 varint_field(&mut message, 3, family.kind as u64);
237 for sample in &family.samples {
238 bytes_field(&mut message, 4, &metric(family.kind, sample));
239 }
240 varint(&mut out, message.len() as u64);
241 out.extend_from_slice(&message);
242 }
243 out
244}
245
246fn metric(kind: Kind, sample: &Sample) -> Vec<u8> {
247 let mut m = Vec::new();
248 for (name, value) in &sample.labels {
249 let mut pair = Vec::new();
250 string_field(&mut pair, 1, name);
251 string_field(&mut pair, 2, value);
252 bytes_field(&mut m, 1, &pair);
253 }
254 match &sample.value {
255 Value::Number(v) => {
256 let mut inner = Vec::new();
257 double_field(&mut inner, 1, *v);
258 let field = match kind {
259 Kind::Counter => 3,
260 Kind::Gauge => 2,
261 _ => 5, };
263 bytes_field(&mut m, field, &inner);
264 }
265 Value::Histogram(classic, native) => bytes_field(&mut m, 7, &histogram(classic, native.as_ref())),
266 }
267 m
268}
269
270fn histogram(classic: &ClassicHistogram, native: Option<&http::Histogram>) -> Vec<u8> {
271 let mut h = Vec::new();
272 varint_field(&mut h, 1, classic.count);
273 double_field(&mut h, 2, classic.sum);
274 for (bound, cumulative) in &classic.buckets {
275 let mut bucket = Vec::new();
276 varint_field(&mut bucket, 1, *cumulative);
277 double_field(&mut bucket, 2, *bound);
278 bytes_field(&mut h, 3, &bucket);
279 }
280 if let Some(native) = native {
281 varint_field(&mut h, 5, zigzag(http::SCHEMA as i64));
282 double_field(&mut h, 6, http::ZERO_THRESHOLD);
283 varint_field(&mut h, 7, native.zero_count);
284 let (spans, deltas) = native.native_spans();
285 for (offset, length) in &spans {
286 let mut span = Vec::new();
287 varint_field(&mut span, 1, zigzag(*offset as i64));
288 varint_field(&mut span, 2, *length as u64);
289 bytes_field(&mut h, 12, &span);
290 }
291 if !deltas.is_empty() {
292 let mut packed = Vec::new();
293 for delta in &deltas {
294 varint(&mut packed, zigzag(*delta));
295 }
296 bytes_field(&mut h, 13, &packed);
297 }
298 if spans.is_empty() && native.zero_count == 0 {
299 bytes_field(&mut h, 12, &[0x08, 0x00, 0x10, 0x00]);
302 }
303 }
304 h
305}
306
307fn zigzag(v: i64) -> u64 {
308 ((v << 1) ^ (v >> 63)) as u64
309}
310
311fn varint(out: &mut Vec<u8>, mut v: u64) {
312 while v >= 0x80 {
313 out.push((v as u8) | 0x80);
314 v >>= 7;
315 }
316 out.push(v as u8);
317}
318
319fn varint_field(out: &mut Vec<u8>, field: u32, v: u64) {
320 varint(out, (field as u64) << 3);
321 varint(out, v);
322}
323
324fn double_field(out: &mut Vec<u8>, field: u32, v: f64) {
325 varint(out, ((field as u64) << 3) | 1);
326 out.extend_from_slice(&v.to_le_bytes());
327}
328
329fn bytes_field(out: &mut Vec<u8>, field: u32, bytes: &[u8]) {
330 varint(out, ((field as u64) << 3) | 2);
331 varint(out, bytes.len() as u64);
332 out.extend_from_slice(bytes);
333}
334
335fn string_field(out: &mut Vec<u8>, field: u32, s: &str) {
336 bytes_field(out, field, s.as_bytes());
337}
338
339#[cfg(test)]
340mod tests;