-
Notifications
You must be signed in to change notification settings - Fork 1
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
feat: metrics NG #22
feat: metrics NG #22
Conversation
type Target = M; | ||
|
||
fn resolve_labels(&self, (label,): (EnumLabel<NAME, T>,)) -> &M { | ||
let debug_panic = || { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe move this outside the function body?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? It's only being used inside this function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know. Maybe it's just me, but it feels weird to have these embedded util functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An item should have the smallest possible scope so you don't have a question "where else this thing is being used"
{ | ||
// TODO: Switch to `[(T, M); T::VARIANT_COUNT]` once generic parameters are | ||
// allowed to be used in const expressions. | ||
type MetricCollection = Vec<(T, M)>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this PR is focused on micro-optimizations, we should consider stack-allocated SmallVec
for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It won't be on the stack anyway, as it's always static
. And it will only allocate once per metric during the initialization
I guess it will remove a single indirection (heap pointer) for happy-path scenarios
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the idea is to reduce indirection and allocations on push/resize.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did some benchmarking and the difference is around a rounding error, however Vec
allows to allocate exactly the amount we need, as we know all the label values beforehand.
}; | ||
|
||
let idx = label.0.ordinal(); | ||
let mut idx = if idx < 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use debug_assert!(idx >= 0)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I'm also doing idx = 0
in the branch, so the release build won't crash
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That can be replaced by idx.max(0)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to check that the index is in [0, vec.len())
range
idx as usize | ||
}; | ||
|
||
if idx >= self.collection.len() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same, I'd use debug_assert!()
instead of custom panic macro/function.
} | ||
|
||
pub struct StringCollection<T, M: 'static> { | ||
inner: ArcSwap<HashMap<T, &'static M>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that you're migrating the relay, can you guesstimate the highest cardinality that such StringCollection
can have? If it's pretty low, maybe it'd make sense to replace HashMap
with SmallVec
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would depend on the application and whether people ab(using) this instead of using proper EnumLabel
s.
But for the relay the only "valid" use-cases are HTTP routes and status codes, which makes the cardinality <10
Probably makes sense to implement both versions, but let's try SmallVec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But for the relay the only "valid" use-cases are HTTP routes and status codes, which makes the cardinality <10
If it's route_name * response_code
it'd likely be more than 10. Not sure if it's worth it. Can you double check the existing HTTP route metrics to confirm cardinality?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Different labels use separate collections, so it won't multiply them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We definitely have <10 http routes in the relay
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Benchmark results: Binary search on Vec
/SmallVec
is 2x faster for 10 elements, and the same as HashMap
lookup for 20 elements.
After that the performance falls significantly, so I suggest we leave HashMap
here, as StringCollection
is not the recommended way to implement labels anyway and it should support all other edge-cases.
let mut inner_clone: HashMap<_, _> = (**inner).clone(); | ||
|
||
let name = const { resolve_label_name::<NAME>() }; | ||
let label_ = Label::new(name, label.to_owned().to_string()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's up with the _
suffix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't have the brain power to come up with a different name, basically there are two labels in scope and both of them are being used later. It's similar to the math notation when you have variable a
and a'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks weird...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall, though I left a few suggestions.
The main thing missing is tests. Can we also cover the Box::leak
stuff with some allocation tests to confirm there's no unsound or memory leak scenario?
@heilhead converting As we are going to update the graphs anyway, I'm removing the conversion here and from the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
Description
Replaces
metrics
crate with the new one built on top ofmetrics
facade.Copy-pasted
crate
doc:Additionally:
metrics
example, examples are now inmetrics
crate docsalloc
example to use new metricsHow Has This Been Tested?
Some unit tests / doc tests inside
metrics
crate itself, plus going to test in therelay
Due Diligence