Conversation
markdroth
left a comment
There was a problem hiding this comment.
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!
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).
markdroth
left a comment
There was a problem hiding this comment.
This looks much better! Remaining comments are mostly minor.
Please let me know if you have any questions. Thanks!
markdroth
left a comment
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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) | |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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). |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
| #### 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. |
There was a problem hiding this comment.
As above, please change "first appears or changes" to just "first appears".
No description provided.