Skip to content

Commit af69f94

Browse files
committed
core, opentelemetry, rls, xds, util: Enrich delay_reason diagnostic metadata across pass-through containers and Category C scenarios
1 parent 69b353e commit af69f94

15 files changed

Lines changed: 85 additions & 35 deletions

File tree

core/src/main/java/io/grpc/internal/DelayedClientTransport.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ private static String determineQueuingDelayType(@Nullable PickResult pickResult)
372372
return "subchannel_state_mismatch";
373373
}
374374
if (!pickResult.getStatus().isOk()) {
375-
return "wait_for_ready_failed";
375+
return "picker_failing_with_wait_for_ready";
376376
}
377377
if (pickResult.getDelayType() != null) {
378378
return pickResult.getDelayType();
@@ -427,8 +427,7 @@ private PendingStream(PickSubchannelArgs args, ClientStreamTracer[] tracers,
427427
*/
428428
void updateDelay(@Nullable String newType, @Nullable String newReason) {
429429
if (!Objects.equals(activeDelayType, newType)) {
430-
// Delay categorization changed (e.g., from RLS lookup to TCP connecting).
431-
// Close prior active segment across all tracers before starting new canonical segment.
430+
// Delay type changed (e.g., from RLS lookup to connecting). End the previous delay.
432431
if (activeDelayType != null) {
433432
for (ClientStreamTracer tracer : tracers) {
434433
tracer.recordAttemptDelayEnd();
@@ -443,8 +442,7 @@ void updateDelay(@Nullable String newType, @Nullable String newReason) {
443442
}
444443
}
445444
if (newType != null && newReason != null && !Objects.equals(activeDelayReason, newReason)) {
446-
// Categorization remained constant, but granular runtime diagnostics updated
447-
// (e.g., priority policy failover between tiers). Emit transition event.
445+
// Delay type is unchanged, but the reason changed (e.g., priority failover).
448446
activeDelayReason = newReason;
449447
for (ClientStreamTracer tracer : tracers) {
450448
tracer.recordAttemptDelayReasonChanged(newReason);

core/src/main/java/io/grpc/internal/PickFirstLoadBalancer.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void onSubchannelState(ConnectivityStateInfo stateInfo) {
8585

8686
// The channel state does not get updated when doing name resolving today, so for the moment
8787
// let LB report CONNECTION and call subchannel.requestConnection() immediately.
88-
updateBalancingState(CONNECTING, new FixedResultPicker(CONNECTING_RESULT));
88+
updateBalancingState(CONNECTING, new FixedResultPicker(connectingResult()));
8989
subchannel.requestConnection();
9090
} else {
9191
subchannel.updateAddresses(servers);
@@ -137,7 +137,7 @@ private void processSubchannelState(Subchannel subchannel, ConnectivityStateInfo
137137
case CONNECTING:
138138
// It's safe to use RequestConnectionPicker here, so when coming from IDLE we could leave
139139
// the current picker in-place. But ignoring the potential optimization is simpler.
140-
picker = new FixedResultPicker(CONNECTING_RESULT);
140+
picker = new FixedResultPicker(connectingResult());
141141
break;
142142
case READY:
143143
picker = new FixedResultPicker(PickResult.withSubchannel(subchannel));
@@ -171,6 +171,10 @@ public void requestConnection() {
171171
}
172172
}
173173

174+
private PickResult connectingResult() {
175+
return PickResult.withNoResult("connecting", "pick_first: attempting to connect");
176+
}
177+
174178
/** Picker that requests connection during the first pick, and returns noResult. */
175179
private final class RequestConnectionPicker extends SubchannelPicker {
176180
private final AtomicBoolean connectionRequested = new AtomicBoolean(false);

core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,7 @@ public void streamDelayMetrics_channelFallback_waitForReadyFailed() {
918918
delayedTransport.newStream(method, headers, wfrOptions, customTracers);
919919

920920
verify(mockTracer).recordAttemptDelayStart(
921-
"wait_for_ready_failed",
921+
"picker_failing_with_wait_for_ready",
922922
"wait_for_ready RPC failed with status: " + Status.UNAVAILABLE);
923923
}
924924

opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ public void streamCreated(io.grpc.Attributes transportAtts, Metadata headers) {
228228
public void recordAttemptDelayStart(String delayType, String delayReason) {
229229
if (!GrpcOpenTelemetry.isDelayObservabilityEnabled()
230230
|| (activeDelayStopwatch != null && Objects.equals(activeDelayType, delayType))) {
231-
// If delay metrics are disabled or canonical delay type is unchanged, skip timer reset.
231+
// Do not reset the stopwatch if the delay type is unchanged.
232232
return;
233233
}
234234
recordAttemptDelayEnd();
@@ -238,7 +238,7 @@ public void recordAttemptDelayStart(String delayType, String delayReason) {
238238

239239
@Override
240240
public void recordAttemptDelayReasonChanged(String delayReason) {
241-
// Reason strings are high-cardinality diagnostics intended strictly for tracing spans.
241+
// Reason strings are high-cardinality diagnostics intended for tracing spans.
242242
}
243243

244244
@Override
@@ -251,7 +251,6 @@ public void recordAttemptDelayEnd() {
251251
activeDelayStopwatch = null;
252252
activeDelayType = null;
253253
if (module.resource.clientAttemptDelayCounter() != null) {
254-
// Export elapsed duration to grpc.client.attempt.delay.duration histogram (seconds).
255254
AttributesBuilder builder = io.opentelemetry.api.common.Attributes.builder()
256255
.put(METHOD_KEY, fullMethodName)
257256
.put(TARGET_KEY, target)

opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,7 @@ public void recordAttemptDelayStart(String delayType, String delayReason) {
223223
return;
224224
}
225225
if (activeDelaySpan != null && Objects.equals(activeDelayType, delayType)) {
226-
// Active Span Retention invariant: If canonical delay type remains identical
227-
// (e.g., priority failover between tiers), delegate to reason changed hook.
226+
// Do not recreate the span if the delay type is unchanged (e.g., priority failover).
228227
recordAttemptDelayReasonChanged(delayReason);
229228
return;
230229
}
@@ -237,7 +236,6 @@ public void recordAttemptDelayStart(String delayType, String delayReason) {
237236
.setAttribute("grpc.delay_type", delayType)
238237
.startSpan();
239238
activeDelaySpan = delaySpan;
240-
// Emit structured transition event containing explicit categorization attributes.
241239
delaySpan.addEvent(
242240
"Delay state transition",
243241
io.opentelemetry.api.common.Attributes.of(
@@ -251,7 +249,6 @@ public void recordAttemptDelayReasonChanged(String delayReason) {
251249
return;
252250
}
253251
String type = activeDelayType;
254-
// Append granular runtime diagnostics to active child span without span re-creation.
255252
activeDelaySpan.addEvent(
256253
"Delay state transition",
257254
io.opentelemetry.api.common.Attributes.of(

rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,8 +1028,7 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
10281028
SubchannelPicker picker =
10291029
(childPolicyWrapper != null) ? childPolicyWrapper.getPicker() : null;
10301030
if (picker == null) {
1031-
// Category F (Pass-Through container): Preserve leaf "connecting" delay type while
1032-
// recording RLS child policy context.
1031+
// Child policy is connecting. Preserve leaf delay type.
10331032
return PickResult.withNoResult(
10341033
"connecting", "RLS child policy connecting");
10351034
}
@@ -1040,6 +1039,11 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
10401039
Arrays.asList(helper.getChannelTarget(), lookupService,
10411040
childPolicyWrapper.getTarget(), determineMetricsPickResult(pickResult)),
10421041
Arrays.asList(determineCustomLabel(args)));
1042+
} else if (pickResult.getDelayType() != null) {
1043+
return PickResult.withNoResult(
1044+
pickResult.getDelayType(),
1045+
"RLS child (" + childPolicyWrapper.getTarget() + ") delayed: "
1046+
+ pickResult.getDelayReason());
10431047
}
10441048
return pickResult;
10451049
} else if (response.hasError()) {
@@ -1053,8 +1057,7 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
10531057
convertRlsServerStatus(response.getStatus(),
10541058
lbPolicyConfig.getRouteLookupConfig().lookupService()));
10551059
} else {
1056-
// Category B (Control-Plane scenario): RPC is blocked executing an RLS control-plane
1057-
// query. Report canonical "rls_lookup_pending" type and target server address.
1060+
// RLS control-plane query is pending.
10581061
return PickResult.withNoResult(
10591062
"rls_lookup_pending",
10601063
"Route Lookup Service query pending on " + lookupService);
@@ -1074,6 +1077,11 @@ private PickResult useFallback(PickSubchannelArgs args) {
10741077
Arrays.asList(helper.getChannelTarget(), lookupService,
10751078
fallbackChildPolicyWrapper.getTarget(), determineMetricsPickResult(pickResult)),
10761079
Arrays.asList(determineCustomLabel(args)));
1080+
} else if (pickResult.getDelayType() != null) {
1081+
return PickResult.withNoResult(
1082+
pickResult.getDelayType(),
1083+
"RLS fallback (" + fallbackChildPolicyWrapper.getTarget() + ") delayed: "
1084+
+ pickResult.getDelayReason());
10771085
}
10781086
return pickResult;
10791087
}

util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,17 @@ final class RoundRobinLoadBalancer extends MultiChildLoadBalancer {
4444
private static final PickResult CONNECTING_RESULT =
4545
PickResult.withNoResult("connecting", "round_robin: attempting to connect");
4646
private final AtomicInteger sequence = new AtomicInteger(new Random().nextInt());
47-
private SubchannelPicker currentPicker = new FixedResultPicker(CONNECTING_RESULT);
47+
private SubchannelPicker currentPicker = new FixedResultPicker(connectingResult());
4848

4949
public RoundRobinLoadBalancer(Helper helper) {
5050
super(helper);
5151
}
5252

53+
private PickResult connectingResult() {
54+
return PickResult.withNoResult("connecting",
55+
"round_robin connecting: TCP/TLS handshake in progress to child balancers");
56+
}
57+
5358
/**
5459
* Updates picker with the list of active subchannels (state == READY).
5560
*/
@@ -70,7 +75,7 @@ protected void updateOverallBalancingState() {
7075
}
7176

7277
if (isConnecting) {
73-
updateBalancingState(CONNECTING, new FixedResultPicker(CONNECTING_RESULT));
78+
updateBalancingState(CONNECTING, new FixedResultPicker(connectingResult()));
7479
} else {
7580
updateBalancingState(TRANSIENT_FAILURE, createReadyPicker(getChildLbStates()));
7681
}

util/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ public class RoundRobinLoadBalancerTest {
8787
private static final Attributes.Key<String> MAJOR_KEY = Attributes.Key.create("major-key");
8888
private static final SubchannelPicker EMPTY_PICKER =
8989
new FixedResultPicker(
90-
PickResult.withNoResult("connecting", "round_robin: attempting to connect"));
90+
PickResult.withNoResult("connecting",
91+
"round_robin connecting: TCP/TLS handshake in progress to child balancers"));
9192

9293
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
9394

@@ -584,7 +585,7 @@ public void roundRobin_delayAttributes() {
584585
.updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
585586
PickResult res = pickerCaptor.getValue().pickSubchannel(mockArgs);
586587
assertThat(res.getDelayType()).isEqualTo("connecting");
587-
assertThat(res.getDelayReason()).contains("attempting to connect");
588+
assertThat(res.getDelayReason()).contains("TCP/TLS handshake in progress");
588589
}
589590

590591
private static class FakeSocketAddress extends SocketAddress {

xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
123123
CONNECTING,
124124
new FixedResultPicker(
125125
PickResult.withNoResult(
126-
"cds_dynamic_discovery", "cds: fetching xDS cluster metadata")));
126+
"cds_dynamic_discovery",
127+
"waiting for CDS resource definition for cluster " + clusterName)));
127128
return Status.OK;
128129
}
129130
if (!clusterConfigOr.hasValue()) {

xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,13 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
150150
Status.UNAVAILABLE.withDescription("CDS encountered error: unable to find "
151151
+ "available subchannel for cluster " + clusterName));
152152
}
153-
return childPicker.pickSubchannel(args);
153+
PickResult childResult = childPicker.pickSubchannel(args);
154+
if (!childResult.hasResult() && childResult.getDelayType() != null) {
155+
String reason = "xds_cluster_manager: child '" + clusterName + "': "
156+
+ (childResult.getDelayReason() != null ? childResult.getDelayReason() : "");
157+
return PickResult.withNoResult(childResult.getDelayType(), reason);
158+
}
159+
return childResult;
154160
}
155161

156162
@Override

0 commit comments

Comments
 (0)