Skip to content

A121: RPC Delay Observability - #556

Open
mbissa wants to merge 15 commits into
grpc:masterfrom
mbissa:lb-policy-delay-metrics-latest
Open

mbissa wants to merge 15 commits into
grpc:masterfrom
mbissa:lb-policy-delay-metrics-latest

Conversation

@mbissa

@mbissa mbissa commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@mbissa mbissa self-assigned this Jun 23, 2026
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
@mbissa mbissa removed their assignment Jun 25, 2026
Comment thread A121-rpc-delay-observability.md Outdated

@markdroth markdroth left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The design looks good overall. I have one or two minor questions about design details, but they don't affect the overall shape of the design. Most of my comments are about the organization of the document -- the content is good but is not organized in a way that makes it easy to understand.

Please let me know if you have any questions. Thanks!

Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
AgraVator added a commit to grpc/grpc-java that referenced this pull request Jul 27, 2026
This PR implements **Attempt-Level RPC Delay Observability** across the core channel transport, built-in load balancers, xDS policies, and the OpenTelemetry telemetry plugin, aligned with [gRPC Proposal A121](grpc/proposal#556).

@jggj-c jggj-c 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.

...

@markdroth markdroth left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks much better! Remaining comments are mostly minor.

Please let me know if you have any questions. Thanks!

Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated
Comment thread A121-rpc-delay-observability.md Outdated

@markdroth markdroth left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is getting closer!

Please let me know if you have any questions. Thanks!

### Metric Schema

The following client-side per-call metrics are registered, extending the instrumentation framework defined in [gRPC A66][A66].
All metrics added in this proposal will start as experimental and therefore off by default. The long term goal will be to de-experimentalize them and have them be on by default, but the exact criteria for that change are TBD.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You added a section on this below, so I don't think it needs to be stated here as well. I would just remove this line.

| `recordAttemptDelayEnd()` | attempt | the attempt-level delay resolves |
| Method | Called when |
|---|---|
| `RecordDelayStart(delay_type, reason)` | a delay begins, or the `delay_type` changes (which ends the previous delay and starts a new one) |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think the "(which ends the previous delay and starts a new one)" part makes sense. If the channel is the only thing storing the current delay type, then the call tracer cannot automatically end the previous delay, because it doesn't know what the delay type was previously.

I think we need to require that if the delay type changes, the channel must call RecordDelayEnd() with the old delay type before calling RecordDelayStart() with the new delay type.

| `RecordDelayEnd(delay_type)` | the delay resolves |

**Caller (channel) responsibilities.** The channel detects when a delay begins and ends, chooses the `delay_type` and `delay_reason`, and calls `Start` when a `delay_type` first appears or changes, `ReasonChanged` when only the reason changes, and `End` when the wait resolves. The scope — and therefore which histogram the delay is recorded to (see [Metric Schema](#metric-schema)) — is chosen by whether the channel calls the method on the call-scoped or the attempt-scoped tracer.
**Caller (channel) responsibilities.** The channel is the single owner of the current `delay_type`: it stores it, chooses the `delay_type` and `delay_reason`, and passes the `delay_type` on every call so the call tracer never has to store it. It calls `RecordDelayStart` when a `delay_type` first appears or changes, `RecordDelayReasonChanged` when only the reason changes, and `RecordDelayEnd` when the delay resolves. The scope — and therefore which histogram the delay is recorded to (see [Metric Schema](#metric-schema)) — is chosen by whether the channel calls the method on the call-scoped or the attempt-scoped tracer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As per my comment above, please change "first appears or changes" to just "first appears".

**Caller (channel) responsibilities.** The channel is the single owner of the current `delay_type`: it stores it, chooses the `delay_type` and `delay_reason`, and passes the `delay_type` on every call so the call tracer never has to store it. It calls `RecordDelayStart` when a `delay_type` first appears or changes, `RecordDelayReasonChanged` when only the reason changes, and `RecordDelayEnd` when the delay resolves. The scope — and therefore which histogram the delay is recorded to (see [Metric Schema](#metric-schema)) — is chosen by whether the channel calls the method on the call-scoped or the attempt-scoped tracer.

**Call tracer (telemetry plugin) responsibilities.** On `Start`, the plugin opens the `Delay` span (see [Tracing Schema](#tracing-schema)) and begins timing. On `ReasonChanged`, it adds a `Delay state transition` event. On `End`, it closes the span and records the elapsed duration to the corresponding histogram. The plugin owns timing, so the `End` methods carry no duration argument.
**Call tracer (telemetry plugin) responsibilities.** On `RecordDelayStart`, the plugin opens the `Delay` span (see [Tracing Schema](#tracing-schema)) and records the delay type and start time of the delay. On `RecordDelayReasonChanged`, it adds a `Delay triggered` event. On `RecordDelayEnd`, it closes the span and records the elapsed duration to the histogram identified by the supplied `delay_type`. Because `delay_type` is supplied on every call, the plugin does not need to store it, and the plugin owns timing (the methods carry no duration argument).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The call tracer will also add a Delay triggered event in RecordDelayStart().

**Call tracer (telemetry plugin) responsibilities.** On `Start`, the plugin opens the `Delay` span (see [Tracing Schema](#tracing-schema)) and begins timing. On `ReasonChanged`, it adds a `Delay state transition` event. On `End`, it closes the span and records the elapsed duration to the corresponding histogram. The plugin owns timing, so the `End` methods carry no duration argument.
**Call tracer (telemetry plugin) responsibilities.** On `RecordDelayStart`, the plugin opens the `Delay` span (see [Tracing Schema](#tracing-schema)) and records the delay type and start time of the delay. On `RecordDelayReasonChanged`, it adds a `Delay triggered` event. On `RecordDelayEnd`, it closes the span and records the elapsed duration to the histogram identified by the supplied `delay_type`. Because `delay_type` is supplied on every call, the plugin does not need to store it, and the plugin owns timing (the methods carry no duration argument).

**Cancellation and deadlines.** The channel already notifies the call tracer when an RPC is cancelled or reaches its deadline. If a delay is open at that point, the call tracer automatically terminates it — closing the span and recording the partial duration — so the channel does not need to explicitly end open delays on these paths.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This can no longer be the case if we're saying that only the channel will store the current delay type. We must require that the channel will call RecordDelayEnd() in this case.

### Resolver Delays

The channel records a call-level delay of type **`resolving`** while an RPC is blocked waiting for name resolution. When an RPC is sent while the channel has not yet received its first resolver result, the channel calls `recordCallDelayStart("resolving", reason)` on the call tracer; when the resolver delivers its first result and the RPC can proceed, the channel calls `recordCallDelayEnd()`. If resolution has already completed by the time the RPC is sent, no delay is recorded.
The channel records a call-level delay of type **`resolving`** while an RPC is blocked waiting for name resolution. When an RPC is sent while the channel has not yet received its first resolver result, the channel calls `RecordDelayStart("resolving", reason)` on the call tracer; when the resolver delivers its first result and the RPC can proceed, the channel calls `RecordDelayEnd("resolving")`. If resolution has already completed by the time the RPC is sent, no delay is recorded.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

s/delivers/delivers/

#### Channel Behavior for LB Pick Delays

For picker-generated delays, the channel reads the deferred pick's `delay_type` and `delay_reason` and drives the call tracer per the [Call Tracer API](#call-tracer-api-changes): `Start` when the `delay_type` first appears or changes, `ReasonChanged` when only the reason changes, and `End` when a pick assigns a ready subchannel.
For picker-generated delays, the channel reads the queued pick's `delay_type` and `delay_reason` and drives the call tracer per the [Call Tracer API](#call-tracer-api-changes): `RecordDelayStart` when the `delay_type` first appears or changes, `RecordDelayReasonChanged` when only the reason changes, and `RecordDelayEnd` when a pick assigns a ready subchannel.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As above, please change "first appears or changes" to just "first appears".

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.

7 participants