feat(family): add ability to lookup metric by query type
Added ability to lookup metric by query type in Family::get_or_create
Example:
#[derive(Clone, Eq, Hash, PartialEq, EncodeLabelSet)]
struct Labels {
method: String,
url_path: String,
status_code: String,
}
let family = Family::<Labels, Counter>::default();
// Will create or get the metric with label `method="GET",url_path="/metrics",status_code="200"`
family.get_or_create(&Labels {
method: "GET".to_string(),
url_path: "/metrics".to_string(),
status_code: "200".to_string(),
}).inc();
// Will return a reference to the metric without unnecessary cloning and allocation.
family.get_or_create(&LabelsQ {
method: "GET",
url_path: "/metrics",
status_code: "200",
}).inc();
#[derive(Debug, Eq, Hash, PartialEq)]
struct LabelsQ<'a> {
method: &'a str,
url_path: &'a str,
status_code: &'a str,
}
impl CreateFromEquivalent<Labels> for LabelsQ<'_> {
fn create(&self) -> Labels {
Labels {
method: self.method.to_string(),
url_path: self.url_path.to_string(),
status_code: self.status_code.to_string(),
}
}
}
impl Equivalent<Labels> for LabelsQ<'_> {
fn equivalent(&self, key: &Labels) -> bool {
self.method == key.method &&
self.url_path == key.url_path &&
self.status_code == key.status_code
}
}
Performance difference:
- 61.247 ns - lookup by
&Labels {method: "GET".to_string(), url_path: "/metrics".to_string(), status_code: "200".to_string()} - 13.324 ns - lookup by
&LabelsQ {method: "GET", url_path: "/metrics", status_code: "200"}
counter family with custom type label set and direct lookup
time: [61.076 ns 61.247 ns 61.422 ns]
Found 9 outliers among 100 measurements (9.00%)
8 (8.00%) high mild
1 (1.00%) high severe
counter family with custom type label set and equivalent lookup
time: [13.291 ns 13.324 ns 13.358 ns]
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high mild
Fix: #157
Thank you for the work here.
I am hesitant to move forward.
- This pull request increases the maintenance surface of this crate.
- This patch ties us to the public interface of
hashbrown, i.e. a breaking release inhashbrownrequires a breaking release inprometheus-client. - While the benchmark shows a significant impact on the micro level, I wonder how much impact this has on the macro level. In other words, do you see this impacting your application?
I wonder whether keeping a Labels struct around across get_or_create calls would be a valid option for you. Or whether wrapping your strings in a Arc on the higher level.
Thank you for the answer. Concerns are reasonable.
While the benchmark shows a significant impact on the micro level, I wonder how much impact this has on the macro level. In other words, do you see this impacting your application? I don't see the critical impact of cloning a dozen strings on each processed message. But looking into code like this is enough to decide to do something.
self.family.get_or_create(&Labels {
one: one.to_owned(),
two: two.to_owned(),
three: three.to_owned(),
four: four.to_owned(),
five: five.to_owned(),
}).inc();
All my alternative solutions lead to having Arc<RwLock<hashbrown::HashMap<RealLabels, ...>>> which is very similar to Family :)
Anyway, you provide a good API. Thank you. Let me know if you need some help.
All my alternative solutions lead to having Arc<RwLock<hashbrown::HashMap<RealLabels, ...>>> which is very similar to Family :)
As you likely know, you can implement your own metrics, e.g. in this particular case it sounds like you should provide your own custom Family type that implements EncodeMetric.
Thank you for the answer. Concerns are reasonable.
Appreciate the understanding!
Anyway, you provide a good API. Thank you. Let me know if you need some help.
Always! Thank you for offering your help on https://github.com/prometheus/client_rust/pull/162#issuecomment-1709591211 @cheshirskycode!
Closing here given the concerns above. Please open a new pull request in case you want to continue the discussion.