Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: correctly handle empty family labels #175

Closed

Conversation

mxinden
Copy link
Member

@mxinden mxinden commented Oct 31, 2023

No description provided.

Copy link
Member Author

@mxinden mxinden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @tylerlevine for reporting and providing a patch!

Comment on lines +752 to +933
let family =
Family::new_with_constructor(|| Histogram::new(exponential_buckets(1.0, 2.0, 10)));
registry.register("my_histogram", "My histogram", family.clone());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure we are on the same page, you don't need to wrap a Histogram in a Family.

Suggested change
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?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

registry.register("my_histogram", "My histogram", family.clone());

family
.get_or_create(&())
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ()?

client_rust/src/encoding.rs

Lines 352 to 356 in 23c31bd

impl EncodeLabelSet for () {
fn encode(&self, _encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
Ok(())
}
}

Copy link

@tylerlevine tylerlevine Oct 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

self.encode_labels::<()>(None)?;

self.encode_labels::<()>(None)?;

self.encode_labels::<()>(None)?;

self.encode_labels::<()>(None)?;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Yes, let me give it a try.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Friendly ping @tylerlevine. What do you think of the suggestion above?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this second test to give a more realistic use case of empty family labels.

@tylerlevine tylerlevine force-pushed the histogram-empty-family-labels-fix branch 2 times, most recently from cf8671e to baadd90 Compare January 8, 2024 21:11
Comment on lines 378 to 520
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())?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Fail to derive EncodeLabelSet on an empty struct. This is a partial fix only as one could still go through e.g. a Vec<(String, String)>.
  2. Wrap self.writer with a thin wrapper, that simply slips in a , in case any of its std::fmt::Write methods are called. In other words that adds a , at very first in case family_labels actually attempts to write anything.

Does the above make sense? I am in favor of (2). What do you think?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@mxinden
Copy link
Member Author

mxinden commented Jul 17, 2024

Friendly ping @tylerlevine.

@tylerlevine tylerlevine force-pushed the histogram-empty-family-labels-fix branch 2 times, most recently from 372291e to f4af9cd Compare August 5, 2024 20:32
@tylerlevine tylerlevine force-pushed the histogram-empty-family-labels-fix branch from f4af9cd to 8f1b569 Compare August 5, 2024 20:33
@tylerlevine
Copy link

Friendly ping @tylerlevine.

Thanks for the ping @mxinden - I've updated the PR to implement suggestion 2 from your earlier comment.

Copy link
Member Author

@mxinden mxinden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Thank you!

Just missing a changelog entry, otherwise this is good to go.

@mxinden
Copy link
Member Author

mxinden commented Sep 6, 2024

Merged through #224. Thank you @tylerlevine for the work!

@mxinden mxinden closed this Sep 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants