Skip to content

feat(http/prom): loosen bounds for StreamLabel's associated labels#4250

Merged
cratelyn merged 2 commits intomainfrom
kate/http-prom.label-set-trait-alias
Nov 5, 2025
Merged

feat(http/prom): loosen bounds for StreamLabel's associated labels#4250
cratelyn merged 2 commits intomainfrom
kate/http-prom.label-set-trait-alias

Conversation

@cratelyn
Copy link
Member

overview

this commit makes a change, loosening the prerequisite trait bounds for
implementations of StreamLabel and MkStreamLabel's associated
StatusLabels and DurationLabels types.

this commit introduces a trait alias,
linkerd_http_prom::stream_label::LabelSet, which is implemented on
behalf of types that satisfy the bounds..

T: EncodeLabelSet + Clone + Eq + std::fmt::Debug + std::hash::Hash + Send + Sync + 'static

..previously expected of these traits' associated types. this provides
dependent code with a shorthand mechanism to express these bounds on
linkerd-app-outbound's route- and backend-level stream labeling
facilities.

motivation

the implicit purpose of this change is that loosening these bounds
allows for us to compose discrete, reusable, StreamLabel
implementations.

this will permit us to decompose the outbound proxy's LabelGrpcRsp
and LabelHttpRsp types into discrete components for obtaining HTTP
status codes, gRPC status codes, error labels, and route labels that we
will be able to share with the inbound proxy.

Signed-off-by: katelyn martin kate@buoyant.io

 ## overview

this commit makes a change, loosening the prerequisite trait bounds for
implementations of `StreamLabel` and `MkStreamLabel`'s associated
`StatusLabels` and `DurationLabels` types.

this commit introduces a trait alias,
`linkerd_http_prom::stream_label::LabelSet`, which is implemented on
behalf of types that satisfy the bounds..

`T: EncodeLabelSet + Clone + Eq + std::fmt::Debug + std::hash::Hash + Send + Sync + 'static`

..previously expected of these traits' associated types. this provides
dependent code with a shorthand mechanism to express these bounds on
`linkerd-app-outbound`'s route- and backend-level stream labeling
facilities.

 ## motivation

the implicit purpose of this change is that loosening these bounds
allows for us to _compose_ discrete, reusable, `StreamLabel`
implementations.

this will permit us to decompose the outbound proxy's `LabelGrpcRsp`
and `LabelHttpRsp` types into discrete components for obtaining HTTP
status codes, gRPC status codes, error labels, and route labels that we
will be able to share with the inbound proxy.

Signed-off-by: katelyn martin <kate@buoyant.io>
@cratelyn cratelyn self-assigned this Oct 17, 2025
@cratelyn cratelyn marked this pull request as ready for review October 17, 2025 18:52
@cratelyn cratelyn requested a review from a team as a code owner October 17, 2025 18:52
@cratelyn
Copy link
Member Author

see #4251, which is based upon this branch. that pull request proposes StreamLabel implementations that can be used to label metrics with HTTP or gRPC status codes, and for converting boxed errors into metric labels.

Copy link
Member

@olix0r olix0r left a comment

Choose a reason for hiding this comment

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

Not hard-blocked, but in general I'm wary of having to express additional type assertions everywhere. It would help to understand what flexibility we get for the verbosity trade-off.

Comment on lines +20 to +21
L::DurationLabels: LabelSet,
L::StatusLabels: LabelSet,
Copy link
Member

Choose a reason for hiding this comment

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

are these load bearing on the struct definition? we try to minimize type requirements on type definitions (since it bleeds out quickly, requiring assertions even when decoupled from an impl)

Comment on lines +25 to +26
type DurationLabels;
type StatusLabels;
Copy link
Member

Choose a reason for hiding this comment

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

I know this is the main point of this PR but I'm missing how it all ties together. It seems like we incur a ton of boilerplate by not having these enforce a : LabelSet bound here.

Where aren't these bounds actually required? Is there a way for us to special-case that?

Signed-off-by: katelyn martin <kate@buoyant.io>
@cratelyn
Copy link
Member Author

cratelyn commented Nov 4, 2025

[..] I'm wary of having to express additional type assertions everywhere.
It would help to understand what flexibility we get for the verbosity
trade-off.

certainly. let me try and outline where i believe we are headed with this
change. first, let's restate where we are now, and where we would like to go.

we have a pair of MkStreamLabel and StreamLabel traits that are intended
for use in our metrics middleware layers. these allow us to abstract over
protocol-specific details for both HTTP and gRPC traffic, so that we can e.g.
count HTTP and gRPC status codes, without needing to reimplement separate
layers.

today, this pair of traits is used only within the
NewRecordResponse<L, X, M, N> layer, defined in linkerd-http-prom.
consumers of this middleware should record request duration or response duration
by using the NewRequestDuration<L, X, N> and NewResponseDuration<L, X, N>
aliases of this middleware. note that each of these include status code
telemetry as well.

today, the NewRecordResponse<L, X, M, N> layer is only used within the
outbound proxy. we would like to use it within the inbound proxy as well.
the inbound proxy's stack does not include the same retry facilities as the
outbound proxy, so two families of status code metrics would introduce a
redundant family of high-cardinality metrics.

so, our high-level goal is to decompose NewRecordResponse<L, X, M, N>
and in turn, NewRequestDuration<L, X, N> and NewResponseDuration<L, X, N>,
into two distinct layers that can be reused by both the inbound and outbound
proxies.

so, how does this change help us get there?

by loosening this constraint, we may define some reusable stream labeling
components (see #4251) for
common concerns like HTTP and gRPC status code labels or mapping errors to
labels, that can be shared by the inbound and outbound proxy. today, we can't
write such a component because this trait's prerequisite bounds demand that we
emit a type that implements EncodeLabelSet and friends.

for what it's worth, an alias like the LabelSet trait proposed here can also
help with some of the existing boilerplate in
linkerd_http_prom::record_response's submodules. see
1de0cd5.

now, note that MkStreamLabel and StreamLabel are currently tightly coupled
to this duration middleware. in
#4231, we hoisted this up a
level so that it could be reused in the future, but it currently is explicitly
aware of the notion of "status" and "duration" labels.

after we introduce a standalone status counting layer, we can remove these
metrics from the duration recording layer. and once that is done, we can in
turn, consolidate the separate StatusLabels and DurationLabels associated
types in MkStreamLabel and StreamLabel into a single associated type.

in this world, we end up with a StreamLabel trait that is shaped like this:

pub trait StreamLabel: Send + 'static {
    type Labels;
    fn init_response<B>(&mut self, rsp: &http::Response<B>);
    fn end_response(&mut self, trailers: Result<Option<&http::HeaderMap>, &Error>);
    fn labels(&self) -> Self::Labels;
}

there is, as you point out, a tradeoff here: we incur some complexity in
dependent code, but we will end up in a position that also greatly simplifies
the complex bounds in our duration middleware, and opens the door the sharing
labeling facilities between both proxies and other future middleware layers.

i've also noticed that after we extricate status code metrics from
the duration middleware, it wouldn't even need MkStreamLabel and StreamLabel.
today, we simply pass the route labels along, which are known in advance and
require no inspection of the response or trailers. should we remove that,
even more boilerplate will melt away in the wake of these changes!

so, i feel some conviction that in the long-term, this is worth the cost. i'm
curious to hear your thoughts. i hope that this helped illuminate the bigger
picture outside of the scope of this change, #4251, and #4252.

@cratelyn cratelyn requested a review from olix0r November 4, 2025 16:02
@cratelyn cratelyn merged commit 59f90ac into main Nov 5, 2025
15 checks passed
@cratelyn cratelyn deleted the kate/http-prom.label-set-trait-alias branch November 5, 2025 16:06
cratelyn added a commit that referenced this pull request Nov 5, 2025
see #4250 (59f90ac) for previous related work.

* feat(http/prom): add http and grpc status labelers

this commit introduces a collection of `MkStreamLabel` and `StreamLabel`
implementations that can be used to inspect status codes for HTTP and
gRPC traffic.

these do not emit duration labels, but can be used by other
implementors of these traits to perform the common logic of checking
HTTP status codes in response front matter, and for inspecting body
trailers in gRPC traffic.

Signed-off-by: katelyn martin <kate@buoyant.io>

* feat(http/prom): add an error labeler

this commit introduces a `LabelError<E>` type, which provides a
`StreamLabel` implementation that maps boxed errors to labels.

this is generic across `E`-typed labels that can be constructed `From` a
reference to a boxed `linkerd_error::Error`.

Signed-off-by: katelyn martin <kate@buoyant.io>

* feat(http/prom): add an identity labeler

this commit introduces a simple "identity" implementation of
`MkStreamLabel` and `StreamLabel`.

this is useful for metrics such as our duration recording histogram,
that do not inspect the request, response, or outcome for further
labels.

Signed-off-by: katelyn martin <kate@buoyant.io>

---------

Signed-off-by: katelyn martin <kate@buoyant.io>
cratelyn added a commit that referenced this pull request Nov 5, 2025
**nb:** this branch is based upon
#4250
and #4251.

this branch re-frames the outbound proxy's stream labeling
facilities in terms of the constituent status and error labeling
facilities introduced to the `linkerd-http-prom` library in
#4251.

* feat(app/outbound): convenience constructors for `HttpRsp`, `GrpcRsp`

Signed-off-by: katelyn martin <kate@buoyant.io>

* feat(app/outbound): `GrpcRsp`, `HttpRsp` are `From<&Error>`

Signed-off-by: katelyn martin <kate@buoyant.io>

* feat(app/outbound): `GrpcRsp` and `HttpRsp` can be merged

this commit introduces `apply()` methods to `HttpRsp` and `GrpcRsp`.
this allows us to collect both the response status and response error
when a body encounters an error whilst polling.

Signed-off-by: katelyn martin <kate@buoyant.io>

* refactor(app/outbound): `LabelHttpRsp` uses `LabelHttpStatus`

Signed-off-by: katelyn martin <kate@buoyant.io>

* refactor(app/outbound): `LabelGrpcRsp` uses `LabelGrpcStatus`

Signed-off-by: katelyn martin <kate@buoyant.io>

---------

Signed-off-by: katelyn martin <kate@buoyant.io>
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