Group prometheus series by type (#62)
This groups metrics by their root name instead of also using their labels. This is because Prometheus expects a single TYPE declaration per metric name, with all versions of that metric -- different labels, etc -- under that one TYPE declaration. Also fixes issues with properly escaping disallowed characters in the output.
This commit is contained in:
parent
d61f27aa69
commit
75d76e812f
|
@ -38,6 +38,8 @@ impl Builder for PrometheusBuilder {
|
|||
quantiles: self.quantiles.clone(),
|
||||
histos: HashMap::new(),
|
||||
output: get_prom_expo_header(),
|
||||
counters: HashMap::new(),
|
||||
gauges: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,37 +53,48 @@ impl Default for PrometheusBuilder {
|
|||
/// Records metrics in the Prometheus exposition format.
|
||||
pub struct PrometheusObserver {
|
||||
pub(crate) quantiles: Vec<Quantile>,
|
||||
pub(crate) histos: HashMap<Key, (u64, Histogram<u64>)>,
|
||||
pub(crate) histos: HashMap<String, HashMap<Vec<String>, (u64, Histogram<u64>)>>,
|
||||
pub(crate) output: String,
|
||||
pub(crate) counters: HashMap<String, HashMap<Vec<String>, u64>>,
|
||||
pub(crate) gauges: HashMap<String, HashMap<Vec<String>, i64>>,
|
||||
}
|
||||
|
||||
impl Observer for PrometheusObserver {
|
||||
fn observe_counter(&mut self, key: Key, value: u64) {
|
||||
let (name, labels) = key_to_parts(key);
|
||||
let full_name = render_labeled_name(&name, &labels);
|
||||
self.output.push_str("\n# TYPE ");
|
||||
self.output.push_str(name.as_str());
|
||||
self.output.push_str(" counter\n");
|
||||
self.output.push_str(full_name.as_str());
|
||||
self.output.push_str(" ");
|
||||
self.output.push_str(value.to_string().as_str());
|
||||
self.output.push_str("\n");
|
||||
|
||||
let entry = self
|
||||
.counters
|
||||
.entry(name)
|
||||
.or_insert_with(|| HashMap::new())
|
||||
.entry(labels)
|
||||
.or_insert_with(|| 0);
|
||||
|
||||
*entry += value;
|
||||
}
|
||||
|
||||
fn observe_gauge(&mut self, key: Key, value: i64) {
|
||||
let (name, labels) = key_to_parts(key);
|
||||
let full_name = render_labeled_name(&name, &labels);
|
||||
self.output.push_str("\n# TYPE ");
|
||||
self.output.push_str(name.as_str());
|
||||
self.output.push_str(" gauge\n");
|
||||
self.output.push_str(full_name.as_str());
|
||||
self.output.push_str(" ");
|
||||
self.output.push_str(value.to_string().as_str());
|
||||
self.output.push_str("\n");
|
||||
|
||||
let entry = self
|
||||
.gauges
|
||||
.entry(name)
|
||||
.or_insert_with(|| HashMap::new())
|
||||
.entry(labels)
|
||||
.or_insert_with(|| 0);
|
||||
|
||||
*entry = value;
|
||||
}
|
||||
|
||||
fn observe_histogram(&mut self, key: Key, values: &[u64]) {
|
||||
let entry = self.histos.entry(key).or_insert_with(|| {
|
||||
let (name, labels) = key_to_parts(key);
|
||||
|
||||
let entry = self
|
||||
.histos
|
||||
.entry(name)
|
||||
.or_insert_with(|| HashMap::new())
|
||||
.entry(labels)
|
||||
.or_insert_with(|| {
|
||||
let h = Histogram::<u64>::new(3).expect("failed to create histogram");
|
||||
(0, h)
|
||||
});
|
||||
|
@ -98,13 +111,40 @@ impl Drain<String> for PrometheusObserver {
|
|||
fn drain(&mut self) -> String {
|
||||
let mut output: String = self.output.drain(..).collect();
|
||||
|
||||
for (key, sh) in self.histos.drain() {
|
||||
let (sum, hist) = sh;
|
||||
let (name, labels) = key_to_parts(key);
|
||||
for (name, mut by_labels) in self.counters.drain() {
|
||||
output.push_str("\n# TYPE ");
|
||||
output.push_str(name.as_str());
|
||||
output.push_str(" counter\n");
|
||||
for (labels, value) in by_labels.drain() {
|
||||
let full_name = render_labeled_name(&name, &labels);
|
||||
output.push_str(full_name.as_str());
|
||||
output.push_str(" ");
|
||||
output.push_str(value.to_string().as_str());
|
||||
output.push_str("\n");
|
||||
}
|
||||
}
|
||||
|
||||
for (name, mut by_labels) in self.gauges.drain() {
|
||||
output.push_str("\n# TYPE ");
|
||||
output.push_str(name.as_str());
|
||||
output.push_str(" gauge\n");
|
||||
for (labels, value) in by_labels.drain() {
|
||||
let full_name = render_labeled_name(&name, &labels);
|
||||
output.push_str(full_name.as_str());
|
||||
output.push_str(" ");
|
||||
output.push_str(value.to_string().as_str());
|
||||
output.push_str("\n");
|
||||
}
|
||||
}
|
||||
|
||||
for (name, mut by_labels) in self.histos.drain() {
|
||||
output.push_str("\n# TYPE ");
|
||||
output.push_str(name.as_str());
|
||||
output.push_str(" summary\n");
|
||||
|
||||
for (labels, sh) in by_labels.drain() {
|
||||
let (sum, hist) = sh;
|
||||
|
||||
for quantile in &self.quantiles {
|
||||
let value = hist.value_at_quantile(quantile.value());
|
||||
let mut labels = labels.clone();
|
||||
|
@ -128,6 +168,7 @@ impl Drain<String> for PrometheusObserver {
|
|||
output.push_str(hist.len().to_string().as_str());
|
||||
output.push_str("\n");
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
@ -140,7 +181,7 @@ fn key_to_parts(key: Key) -> (String, Vec<String>) {
|
|||
let labels = labels
|
||||
.into_iter()
|
||||
.map(Label::into_parts)
|
||||
.map(|(k, v)| format!("{}=\"{}\"", k, v))
|
||||
.map(|(k, v)| format!("{}=\"{}\"", k, v.escape_default().collect::<String>()))
|
||||
.collect();
|
||||
|
||||
(name, labels)
|
||||
|
|
Loading…
Reference in New Issue