1use prometheus::proto::{
16 Bucket, Counter, Gauge, Histogram, LabelPair, Metric, MetricFamily, MetricType, Quantile,
17 Summary,
18};
19use std::collections::BTreeMap;
20
21#[cfg(test)]
22use serde::Deserialize;
23
24#[cfg(test)]
27fn de_f64<'de, D: serde::Deserializer<'de>>(d: D) -> Result<f64, D::Error> {
28 use serde::Deserialize as _;
29 #[derive(Deserialize)]
30 #[serde(untagged)]
31 enum Repr {
32 Text(String),
33 Num(f64),
34 }
35 match Repr::deserialize(d)? {
36 Repr::Text(t) => t.parse().map_err(serde::de::Error::custom),
37 Repr::Num(v) => Ok(v),
38 }
39}
40
41#[derive(Debug)]
42#[cfg_attr(test, derive(Deserialize))]
43pub struct TypedSample {
44 pub name: String,
45 pub labels: BTreeMap<String, String>,
46 #[cfg_attr(test, serde(deserialize_with = "de_f64"))]
50 pub value: f64,
51 #[cfg_attr(test, serde(default))]
55 pub timestamp: Option<f64>,
56}
57
58#[derive(Debug)]
59#[cfg_attr(test, derive(Deserialize))]
60pub struct TypedFamily {
61 pub name: String,
62 pub help: String,
63 #[cfg_attr(test, serde(rename = "type"))]
64 pub kind: String,
65 #[cfg_attr(test, serde(default))]
68 pub unit: String,
69 pub samples: Vec<TypedSample>,
70}
71
72pub struct BuiltFamily {
78 pub family: MetricFamily,
79 pub unit: String,
80 pub prom_type: String,
85}
86
87pub fn build_families(typed: Vec<TypedFamily>) -> Vec<BuiltFamily> {
94 let mut out: Vec<BuiltFamily> = Vec::new();
95 for mut family in typed {
96 let unit = family.unit.clone();
97 let prom_type = family.kind.clone();
98 let (created, rest) = family
99 .samples
100 .drain(..)
101 .partition(|s| s.name.ends_with("_created"));
102 family.samples = rest;
103 out.extend(
104 promote_created(&family.help, created)
105 .into_iter()
106 .map(|family| {
107 BuiltFamily {
110 family,
111 unit: String::new(),
112 prom_type: "gauge".to_string(),
113 }
114 }),
115 );
116 if let Some(family) = build_one(family) {
117 out.push(BuiltFamily {
118 family,
119 unit,
120 prom_type,
121 });
122 }
123 }
124 out.sort_by(|a, b| a.family.name().cmp(b.family.name()));
125 out
126}
127
128fn promote_created(help: &str, samples: Vec<TypedSample>) -> Vec<MetricFamily> {
132 let mut by_name: BTreeMap<String, MetricFamily> = BTreeMap::new();
133 for sample in samples {
134 let entry = by_name.entry(sample.name.clone()).or_insert_with(|| {
135 let mut f = MetricFamily::new();
136 f.set_name(sample.name.clone());
137 f.set_help(help.to_string());
138 f.set_field_type(MetricType::GAUGE);
139 f
140 });
141 let mut metric = Metric::new();
142 metric.set_label(label_pairs(sample.labels.into_iter().collect()));
143 let mut gauge = Gauge::new();
144 gauge.set_value(sample.value);
145 metric.set_gauge(gauge);
146 entry.mut_metric().push(metric);
147 }
148 by_name.into_values().collect()
149}
150
151fn build_one(family: TypedFamily) -> Option<MetricFamily> {
152 let (metric_type, rendered_suffix) = match family.kind.as_str() {
157 "counter" => (MetricType::COUNTER, None),
158 "gauge" => (MetricType::GAUGE, None),
159 "histogram" => (MetricType::HISTOGRAM, None),
160 "summary" => (MetricType::SUMMARY, None),
161 "info" => (MetricType::GAUGE, Some("_info")),
164 "unknown" => (MetricType::GAUGE, None),
168 other => {
169 tracing::debug!(
170 metric_name = %family.name,
171 kind = %other,
172 "unrecognised metric type; treating as gauge"
173 );
174 (MetricType::GAUGE, None)
175 }
176 };
177
178 let samples = family.samples;
179 if samples.is_empty() {
180 return None;
181 }
182
183 let name = rendered_suffix
184 .and_then(|suffix| samples.iter().find(|s| s.name.ends_with(suffix)))
185 .map_or_else(|| family.name.clone(), |s| s.name.clone());
186
187 let mut out = MetricFamily::new();
188 out.set_name(name);
189 out.set_help(family.help);
190 out.set_field_type(metric_type);
191
192 match metric_type {
193 MetricType::HISTOGRAM | MetricType::SUMMARY => {
194 let point_label = if metric_type == MetricType::HISTOGRAM {
195 "le"
196 } else {
197 "quantile"
198 };
199 struct Series {
202 labels: Vec<(String, String)>,
203 points: Vec<(f64, f64)>,
204 sum: f64,
205 count: u64,
206 saw_count: bool,
210 timestamp: Option<f64>,
213 }
214 let mut series: Vec<Series> = Vec::new();
215 for sample in samples {
216 let key: Vec<(String, String)> = sample
217 .labels
218 .iter()
219 .filter(|(k, _)| k.as_str() != point_label)
220 .map(|(k, v)| (k.clone(), v.clone()))
221 .collect();
222 let entry = match series.iter_mut().position(|s| s.labels == key) {
223 Some(i) => &mut series[i],
224 None => {
225 series.push(Series {
226 labels: key,
227 points: Vec::new(),
228 sum: 0.0,
229 count: 0,
230 saw_count: false,
231 timestamp: None,
232 });
233 series.last_mut()?
234 }
235 };
236 if entry.timestamp.is_none() {
237 entry.timestamp = sample.timestamp;
238 }
239 match sample.labels.get(point_label) {
240 Some(point) => {
241 let bound = parse_bound(point);
242 if bound.is_finite() {
245 entry.points.push((bound, sample.value));
246 }
247 }
248 None if sample.name.ends_with("_sum") => entry.sum = sample.value,
249 None if sample.name.ends_with("_count") => {
250 entry.count = sample.value as u64;
251 entry.saw_count = true;
252 }
253 None => {}
254 }
255 }
256
257 for Series {
258 labels,
259 mut points,
260 sum,
261 count,
262 saw_count,
263 timestamp,
264 } in series
265 {
266 if !saw_count {
267 tracing::debug!(
268 metric_name = %out.name(),
269 "dropping histogram/summary series with no _count sample"
270 );
271 continue;
272 }
273 points.sort_by(|a, b| a.0.total_cmp(&b.0));
274 let mut metric = Metric::new();
275 metric.set_label(label_pairs(labels));
276 if metric_type == MetricType::HISTOGRAM {
277 let mut h = Histogram::new();
278 h.set_bucket(
279 points
280 .into_iter()
281 .map(|(bound, cumulative)| {
282 let mut bucket = Bucket::new();
283 bucket.set_upper_bound(bound);
284 bucket.set_cumulative_count(cumulative as u64);
285 bucket
286 })
287 .collect(),
288 );
289 h.set_sample_sum(sum);
290 h.set_sample_count(count);
291 metric.set_histogram(h);
292 } else {
293 let mut s = Summary::new();
294 s.set_quantile(
295 points
296 .into_iter()
297 .map(|(q, value)| {
298 let mut quantile = Quantile::new();
299 quantile.set_quantile(q);
300 quantile.set_value(value);
301 quantile
302 })
303 .collect(),
304 );
305 s.set_sample_sum(sum);
306 s.set_sample_count(count);
307 metric.set_summary(s);
308 }
309 if let Some(seconds) = timestamp {
310 metric.set_timestamp_ms((seconds * 1_000.0) as i64);
311 }
312 out.mut_metric().push(metric);
313 }
314 }
315 _ => {
316 for sample in samples {
317 let mut metric = Metric::new();
318 metric.set_label(label_pairs(sample.labels.into_iter().collect::<Vec<_>>()));
319 if metric_type == MetricType::COUNTER {
320 let mut c = Counter::new();
321 c.set_value(sample.value);
322 metric.set_counter(c);
323 } else {
324 let mut g = Gauge::new();
325 g.set_value(sample.value);
326 metric.set_gauge(g);
327 }
328 if let Some(seconds) = sample.timestamp {
329 metric.set_timestamp_ms((seconds * 1_000.0) as i64);
330 }
331 out.mut_metric().push(metric);
332 }
333 }
334 }
335
336 (!out.get_metric().is_empty()).then_some(out)
337}
338
339fn parse_bound(text: &str) -> f64 {
340 match text {
341 "+Inf" => f64::INFINITY,
342 "-Inf" => f64::NEG_INFINITY,
343 other => other.parse().unwrap_or(f64::NAN),
344 }
345}
346
347fn label_pairs(mut labels: Vec<(String, String)>) -> Vec<LabelPair> {
348 labels.sort();
349 labels
350 .into_iter()
351 .map(|(k, v)| {
352 let mut pair = LabelPair::new();
353 pair.set_name(k);
354 pair.set_value(v);
355 pair
356 })
357 .collect()
358}