Skip to content

Commit

Permalink
Merge pull request #206 from metrics-rs/tlawrence_prom_matcher_fix
Browse files Browse the repository at this point in the history
metrics-exporter-prometheus: sanitize matchers the same as input keys
  • Loading branch information
tobz committed May 17, 2021
2 parents 53a36b2 + 597fad5 commit 6fad083
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 9 deletions.
12 changes: 6 additions & 6 deletions metrics-exporter-prometheus/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ impl PrometheusBuilder {
/// It only affects matching metrics if set_buckets was not used.
pub fn set_buckets_for_metric(mut self, matcher: Matcher, values: &[f64]) -> Self {
let buckets = self.bucket_overrides.get_or_insert_with(HashMap::new);
buckets.insert(matcher, values.to_vec());
buckets.insert(matcher.sanitized(), values.to_vec());
self
}

Expand Down Expand Up @@ -349,27 +349,27 @@ mod tests {

let recorder = PrometheusBuilder::new()
.set_buckets_for_metric(
Matcher::Full("metrics_testing_foo".to_owned()),
Matcher::Full("metrics.testing foo".to_owned()),
&FULL_VALUES[..],
)
.set_buckets_for_metric(
Matcher::Prefix("metrics_testing".to_owned()),
Matcher::Prefix("metrics.testing".to_owned()),
&PREFIX_VALUES[..],
)
.set_buckets_for_metric(Matcher::Suffix("foo".to_owned()), &SUFFIX_VALUES[..])
.set_buckets(&DEFAULT_VALUES[..])
.build();

let full_key = Key::from_name("metrics_testing_foo");
let full_key = Key::from_name("metrics.testing_foo");
recorder.record_histogram(&full_key, FULL_VALUES[0]);

let prefix_key = Key::from_name("metrics_testing_bar");
let prefix_key = Key::from_name("metrics.testing_bar");
recorder.record_histogram(&prefix_key, PREFIX_VALUES[1]);

let suffix_key = Key::from_name("metrics_testin_foo");
recorder.record_histogram(&suffix_key, SUFFIX_VALUES[2]);

let default_key = Key::from_name("metrics_wee");
let default_key = Key::from_name("metrics.wee");
recorder.record_histogram(&default_key, DEFAULT_VALUES[2] + 1.0);

let full_data = concat!(
Expand Down
35 changes: 35 additions & 0 deletions metrics-exporter-prometheus/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ impl Matcher {
Matcher::Full(full) => key == full,
}
}

/// Creates a sanitized version of this matcher.
pub(crate) fn sanitized(self) -> Matcher {
match self {
Matcher::Prefix(prefix) => Matcher::Prefix(sanitize_key_name(prefix.as_str())),
Matcher::Suffix(suffix) => Matcher::Suffix(sanitize_key_name(suffix.as_str())),
Matcher::Full(full) => Matcher::Full(sanitize_key_name(full.as_str())),
}
}
}

/// Errors that could occur while installing a Prometheus recorder/exporter.
Expand All @@ -53,3 +62,29 @@ pub struct Snapshot {
pub gauges: HashMap<String, HashMap<Vec<String>, f64>>,
pub distributions: HashMap<String, HashMap<Vec<String>, Distribution>>,
}

pub fn sanitize_key_name(key: &str) -> String {
// Replace anything that isn't [a-zA-Z0-9_:].
let sanitize = |c: char| !(c.is_alphanumeric() || c == '_' || c == ':');
key.to_string().replace(sanitize, "_")
}

#[cfg(test)]
mod tests {
use super::sanitize_key_name;

#[test]
fn test_sanitize_key_name() {
let test_cases = vec![
("____", "____"),
("foo bar", "foo_bar"),
("abcd:efgh", "abcd:efgh"),
("lars.andersen", "lars_andersen"),
];

for (input, expected) in test_cases {
let result = sanitize_key_name(input);
assert_eq!(expected, result);
}
}
}
5 changes: 2 additions & 3 deletions metrics-exporter-prometheus/src/recorder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use parking_lot::RwLock;
use metrics::{GaugeValue, Key, Recorder, Unit};
use metrics_util::{Handle, MetricKind, Recency, Registry};

use crate::common::Snapshot;
use crate::common::{sanitize_key_name, Snapshot};
use crate::distribution::{Distribution, DistributionBuilder};

pub(crate) struct Inner {
Expand Down Expand Up @@ -294,8 +294,7 @@ impl PrometheusHandle {
}

fn key_to_parts(key: &Key, defaults: &HashMap<String, String>) -> (String, Vec<String>) {
let sanitize = |c| c == '.' || c == '=' || c == '{' || c == '}' || c == '+' || c == '-';
let name = key.name().to_string().replace(sanitize, "_");
let name = sanitize_key_name(key.name());
let mut values = defaults.clone();
key.labels().into_iter().for_each(|label| {
values.insert(label.key().into(), label.value().into());
Expand Down

0 comments on commit 6fad083

Please sign in to comment.