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: Auto-label profiles based on request baggage #99
base: main
Are you sure you want to change the base?
Conversation
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.
Nice job! I've left a number of comments, but the main suggestion is to move the feature to a submodule (e.g., contrib/k6)
func getBaggageLabels(r *http.Request, filters []FilterFunc, transforms []TransformFunc) LabelSet { | ||
b, err := baggage.Parse(r.Header.Get("Baggage")) | ||
if err != nil { | ||
return Labels() |
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.
nit: Lables()
does allocate labels. Consider returning nil
} | ||
|
||
func (lh *labelHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
TagWrapper(r.Context(), getBaggageLabels(r, lh.cfg.filters, lh.cfg.transforms), func(ctx context.Context) { |
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.
Consider using pprof.SetGorouteLabels
instead: TagWrapper
and pprof.Do
introduce some overhead and also cause appearance of quite noisy frames in the stack traces
// FilterFunc returns true if this key should be used. | ||
type FilterFunc func(key string) bool | ||
|
||
// TransformFunc transforms the key. | ||
type TransformFunc func(key string) 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.
I think that something like func(baggage.Baggage) Labels
would be:
- More flexible and extensible. The function would have more context: the caller already knows that it has to deal with the W3C baggage, there is no reason to hide the details.
- More performant in a common case, as it requires less iterations and function calls
- Simpler
// K6Options provides default options to select k6 members from the baggage. | ||
func K6Options() []BaggageOption { | ||
return []BaggageOption{ | ||
WithFilters(func(key string) bool { | ||
return strings.HasPrefix(key, "k6.") | ||
}), | ||
WithTransforms(func(key string) string { | ||
return strings.ReplaceAll(key, ".", "_") | ||
}), | ||
} | ||
} |
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'm a little concerned about the cardinality of the labels we receive through this process. Do we have an estimate of how many unique combinations this could produce over time (e.g., tens/hundreds/thousands per hour)?
// K6Options provides default options to select k6 members from the baggage. | ||
func K6Options() []BaggageOption { |
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.
Should we perhaps place the newly added functionality into a submodule? For example, contrib/k6? There are a number of reasons why this could be beneficial:
- This is an experimental feature, and the API may change. It would be better if we had clear boundaries between stable and experimental features.
- The API surface of the main module would be cleaner: I expect that only a very small fraction of users are interested in K6Options and baggage handling.
- We intentionally keep the number of dependencies of the main module low. otel/baggage dependency is quite heavy.
Another issue is that in Go The opentracing API does not allow for passing context in a general case, therefore there is no a trivial solution to this (if we want to reuse the tracing instrumentation for our purposes). As a workaround, we could "re-inject" the goroutine labels, but that would require binding to |
What I would also like to learn more about and discuss in detail is the integration scenario as a whole because I definitely lack the context. I'm wondering if the use of pprof labels is required at all: my understanding is that load tests never run concurrently on the test subject system (I just can't imagine when it may make sense), therefore it might be sufficient to refer to the time range for a given test run (identified with the series labels, such as |
In order to support integrations with k6 load testing, we need to be able to pull key-value pairs out of the request baggage and create labels to add to the profile for that request. Instead of targeting specifically k6 baggage, I create a semi-extensible feature to attach arbitrary labels from the request baggage.
To use this feature in conjunction with k6, the
LabelsFromBaggageHandler
should wrap the routes that should have this auto-labeling behavior (ideally it wraps the http server mux to enable it for all handlers), like so:This example adds auto-labeling to all the routes (
/
,/bike
,/scooter
,/car
) for any request baggage with k6-specific members.Of course, it's not limited to just k6 baggage, arbitrary baggage members can be filtered and transformed before applying labels using the
WithFilters
andWithTransforms
options. For example: