fix: correctly handle empty family labels#175
fix: correctly handle empty family labels#175mxinden wants to merge 3 commits intoprometheus:masterfrom
Conversation
mxinden
left a comment
There was a problem hiding this comment.
Thank you @tylerlevine for reporting and providing a patch!
| let family = | ||
| Family::new_with_constructor(|| Histogram::new(exponential_buckets(1.0, 2.0, 10))); | ||
| registry.register("my_histogram", "My histogram", family.clone()); |
There was a problem hiding this comment.
Just to make sure we are on the same page, you don't need to wrap a Histogram in a Family.
| let family = | |
| Family::new_with_constructor(|| Histogram::new(exponential_buckets(1.0, 2.0, 10))); | |
| registry.register("my_histogram", "My histogram", family.clone()); | |
| let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10)); | |
| registry.register("my_histogram", "My histogram", historgam.clone()); |
This should fix the encoding failure, correct?
There was a problem hiding this comment.
Ah - this makes more sense now. I didn't realize that the Family wrapper was optional. Using the histogram directly indeed does fix the encoding failure.
src/encoding/text.rs
Outdated
| registry.register("my_histogram", "My histogram", family.clone()); | ||
|
|
||
| family | ||
| .get_or_create(&()) |
There was a problem hiding this comment.
In what use-case would you want a Family with a () as a label set?
To remove this footgun, shall we remove the EncodeLabelSet implementation for ()?
Lines 352 to 356 in 23c31bd
There was a problem hiding this comment.
Now that I understand that the Family wrapper is optional, I agree that using () as the label set doesn't make much sense.
However, when I originally ran into this issue I was using an empty struct with the #[derive(EncodeLabelSet)] attribute, which has the same erroneous behavior. I was intending to go back and add any needed labels to the struct later but noticed this issue before I could do that. I switched to () when I was simplifying my code for posting here.
Removing the EncodeLabelSet implementation from () seems like a good start, but ideally a fix would cover the empty struct case as well.
There was a problem hiding this comment.
I took a look at removing the EncodeLabelSet implementation for (), but it looks like that impl is relied on in at least these four places:
client_rust/src/encoding/text.rs
Line 189 in 1f3923a
client_rust/src/encoding/text.rs
Line 213 in 1f3923a
client_rust/src/encoding/text.rs
Line 269 in 1f3923a
client_rust/src/encoding/text.rs
Line 276 in 1f3923a
There was a problem hiding this comment.
I would assume that we can replace () with a private uninhabited type that implements EncodeLabelSet. Something along the lines of:
enum Never {}
impl EncodeLabelSet for Never {
// ...
}
// ...
// Then in the above cases:
self.encode_labels::<Never>(None)?;Want to give this a shot @tylerlevine ?
There was a problem hiding this comment.
Good idea. Yes, let me give it a try.
There was a problem hiding this comment.
Friendly ping @tylerlevine. What do you think of the suggestion above?
There was a problem hiding this comment.
@mxinden Looks like that works fine. I cleaned up a bit and updated the PR.
| } | ||
|
|
||
| #[test] | ||
| fn encode_histogram_family_with_empty_struct_family_labels() { |
There was a problem hiding this comment.
Added this second test to give a more realistic use case of empty family labels.
cf8671e to
baadd90
Compare
| if !self.const_labels.is_empty() || additional_labels.is_some() { | ||
| self.writer.write_str(",")?; | ||
| let mut string_writer = String::new(); | ||
| labels.encode(LabelSetEncoder::new(&mut string_writer).into())?; | ||
|
|
||
| if !string_writer.is_empty() { | ||
| if !self.const_labels.is_empty() || additional_labels.is_some() { | ||
| self.writer.write_str(",")?; | ||
| } | ||
| self.writer.write_str(string_writer.as_str())?; | ||
| } | ||
|
|
||
| labels.encode(LabelSetEncoder::new(self.writer).into())?; |
There was a problem hiding this comment.
Sorry for only raising this now.
This change is not ideal, as it doubles the allocations required for self.family_labels, i.e. adds unnecessary allocations to the happy path.
I see multiple ways to still remove the bug (i.e. an invalid dangling ,) without the allocation:
- Fail to derive
EncodeLabelSeton an emptystruct. This is a partial fix only as one could still go through e.g. aVec<(String, String)>. - Wrap
self.writerwith a thin wrapper, that simply slips in a,in case any of itsstd::fmt::Writemethods are called. In other words that adds a,at very first in casefamily_labelsactually attempts to write anything.
Does the above make sense? I am in favor of (2). What do you think?
There was a problem hiding this comment.
Yes, that makes perfect sense to me - thanks for the suggestions. Apologies for my inactivity on this PR, but I'll go for option 2 once I get some time to work on this again. The approach seems pretty straightforward to me.
|
Friendly ping @tylerlevine. |
372291e to
f4af9cd
Compare
Signed-off-by: Tyler Levine <tyler.levine@falconx.io>
…te uninhabited type Signed-off-by: Tyler Levine <tyler.levine@falconx.io>
Signed-off-by: Tyler Levine <tyler.levine@falconx.io>
f4af9cd to
8f1b569
Compare
Thanks for the ping @mxinden - I've updated the PR to implement suggestion 2 from your earlier comment. |
mxinden
left a comment
There was a problem hiding this comment.
Looks good to me. Thank you!
Just missing a changelog entry, otherwise this is good to go.
|
Merged through #224. Thank you @tylerlevine for the work! |
No description provided.