Skip to content

feat: add debug spans for decoding requests - #1760

Open
neoeinstein wants to merge 1 commit into
grpc:masterfrom
neoeinstein:add-decode-debug-spans
Open

feat: add debug spans for decoding requests#1760
neoeinstein wants to merge 1 commit into
grpc:masterfrom
neoeinstein:add-decode-debug-spans

Conversation

@neoeinstein

Copy link
Copy Markdown
Contributor

Closes: #1759

Motivation

Adds optional debug spans into the decoder state. At the same time, it resolves a size regression in the State struct, which got much larger because of the inclusion of the Status in the error. The size change overall is from 16 bytes (v0.11.0) to 176 (master) to 56 (this PR).

Solution

Adds tracing spans at the debug level for tracking decoding of streaming messages. This should have negligible overhead when debug spans are not enabled, and provide some more context when they are enabled.

Boxes the Status in the Error variant to avoid size bloat for the common case.

If desired, this functionality could be gated behind a feature, but I start off by avoiding the extra complexity of adding conditional compilation code.

Comment thread tonic/src/codec/decode.rs Outdated
Comment on lines +177 to +187
if !self.buf.has_remaining() {
return Ok(None);
}

let span = span.get_or_insert_with(|| {
tracing::debug_span!(
"read_header",
compression = tracing::field::Empty,
body.bytes = tracing::field::Empty,
)
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This avoids creating a vestigial read_header span for unary endpoints, which have only a single message. This does mean that there may be a small gap if there isn't any pending header data for a streaming endpoint until the first bytes of the next header are available in the buffer.

Comment thread tonic/src/codec/decode.rs Outdated
len: usize,
},
Error(Status),
Error(Box<Status>),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Boxing here significantly reduces the size of State and StreamingInner as a result, even with the addition of tracing::Span to the other variants.

@neoeinstein
neoeinstein force-pushed the add-decode-debug-spans branch 2 times, most recently from 3ac6746 to 1aa0321 Compare June 27, 2024 15:32
@neoeinstein

Copy link
Copy Markdown
Contributor Author

I wonder if there's value in more explicit boxing of Status, or if Status should internally box in order to reduce its size.

@neoeinstein
neoeinstein force-pushed the add-decode-debug-spans branch 2 times, most recently from c1963b5 to 971d51f Compare June 28, 2024 14:25
Comment thread tonic/src/codec/decode.rs Outdated
) -> Result<Option<DecodeBuf<'_>>, Status> {
if let State::ReadHeader = self.state {
if let State::ReadHeader { span } = &mut self.state {
if !self.buf.has_remaining() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why add this guard here? Can we just do the self.buf.remaining() < HEADER_SIZE check here instead?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I had considered that. The reason for the guard at all is that, even with unary requests, Tonic still polls the stream after reading the first message. That was generating read_header, read_body, read_header for these unary requests, with the last read_header really being vestigial, since the stream would then be dropped after the message. I opted to try to remove this vestigial span, but we could simplify by re-introducing it.

With that in mind, if there are still bytes in the stream, then that would indicate that there's a header already partially in the input, so opening a new span is indicated. If not, then we'd delay opening a new span (but that also means that a new span wouldn't capture the time from the empty poll until the next header started appearing in the buffer).

Changing it so that the span is only opened once 5 bytes are in the buffer would mean that the span would only open once we've already completely read the header, making this really just timing the extraction of the u8 compression encoding and u32 payload size, which probably then isn't all that valuable.

Another option here is to have a debug span one level higher for the entire stream itself, skip the span for the header, and keep a span for the body.

@djc

djc commented Jul 3, 2024

Copy link
Copy Markdown
Contributor

This seems reasonable to me, thanks! (Sorry for the delay in reviewing.)

I wonder if there's value in more explicit boxing of Status, or if Status should internally box in order to reduce its size.

Could make sense to evaluate this next, in a separate PR? How/why is the size of Status causing issues for you?

@marcus-griep-simplisafe marcus-griep-simplisafe left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Could make sense to evaluate this next, in a separate PR? How/why is the size of Status causing issues for you?

I'm not opposed to taking this out. There's probably not a lot of issue here, other than the fact that it now requires more data move instructions to copy up the Result on every return. Since this is in the hot-path for deserialization, and Status in this case is an error condition, optimizing the size of the non-error case seemed appropriate, but I don't have any data that indicates it is required.

@LucioFranco

Copy link
Copy Markdown
Member

@neoeinstein or someone else would like to step up and finish this PR? We already have the status stuff merged or about to be merged so we just need the span stuff.

@LucioFranco LucioFranco added the E-help-wanted Call for participation: Help is requested to fix this issue. label Jun 20, 2025
Adds tracing debug spans to the streaming decoder to measure latency and
track payload metrics (compression type, compressed bytes, and
uncompressed bytes) during message decoding.

Co-authored-by: Marcus Griep <marcus@griep.us>
@nathanielford
nathanielford force-pushed the add-decode-debug-spans branch from 198de46 to 67a7199 Compare August 27, 2026 15:07
@nathanielford

Copy link
Copy Markdown
Contributor

I've pushed a merge of the master branch changes since this was opened, and I think also addressed the requested items from the PR here. @LucioFranco do you mind doing a quick review?

(Just moving through the backlog looking for likely things to clean up and close out.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

E-help-wanted Call for participation: Help is requested to fix this issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add debug spans for codec tracing

5 participants