Skip to content

Commit fad817a

Browse files
committed
merge: resolve conflicts with origin/main
2 parents 247f871 + e8b6d50 commit fad817a

5 files changed

Lines changed: 293 additions & 1 deletion

File tree

gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,10 @@ public class ObservabilityAttributes {
8787

8888
/** Size of the response body in bytes. */
8989
public static final String HTTP_RESPONSE_BODY_SIZE = "http.response.body.size";
90+
91+
/** The resend count of the request. Only used in HTTP transport. */
92+
public static final String HTTP_RESEND_COUNT_ATTRIBUTE = "http.request.resend_count";
93+
94+
/** The resend count of the request. Only used in gRPC transport. */
95+
public static final String GRPC_RESEND_COUNT_ATTRIBUTE = "gcp.grpc.resend_count";
9096
}

gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ static Attributes toOtelAttributes(Map<String, Object> attributes) {
6565
(k, v) -> {
6666
if (v instanceof String) {
6767
attributesBuilder.put(k, (String) v);
68+
} else if (v instanceof Long) {
69+
attributesBuilder.put(k, (Long) v);
6870
} else if (v instanceof Integer) {
6971
attributesBuilder.put(k, (long) (Integer) v);
7072
}

gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,26 @@ private void buildAttributes() {
109109

110110
@Override
111111
public void attemptStarted(Object request, int attemptNumber) {
112+
Map<String, Object> currentAttemptAttributes = new HashMap<>(this.attemptAttributes);
113+
114+
if (attemptNumber > 0) {
115+
ApiTracerContext.Transport transport = apiTracerContext.transport();
116+
if (transport == ApiTracerContext.Transport.GRPC) {
117+
currentAttemptAttributes.put(
118+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE, (long) attemptNumber);
119+
} else if (transport == ApiTracerContext.Transport.HTTP) {
120+
currentAttemptAttributes.put(
121+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE, (long) attemptNumber);
122+
}
123+
}
124+
112125
SpanBuilder spanBuilder = tracer.spanBuilder(attemptSpanName);
113126

114127
// Attempt spans are of the CLIENT kind
115128
spanBuilder.setSpanKind(SpanKind.CLIENT);
116129

117-
spanBuilder.setAllAttributes(ObservabilityUtils.toOtelAttributes(this.attemptAttributes));
130+
// Pass the combined attributes to the new SpanBuilder method
131+
spanBuilder.setAllAttributes(ObservabilityUtils.toOtelAttributes(currentAttemptAttributes));
118132

119133
this.attemptSpan = spanBuilder.startSpan();
120134
}
@@ -154,6 +168,26 @@ private long extractContentLength(final java.util.Map<String, Object> headers) {
154168
}
155169
}
156170

171+
@Override
172+
public void attemptCancelled() {
173+
endAttempt();
174+
}
175+
176+
@Override
177+
public void attemptFailedDuration(Throwable error, java.time.Duration delay) {
178+
endAttempt();
179+
}
180+
181+
@Override
182+
public void attemptFailedRetriesExhausted(Throwable error) {
183+
endAttempt();
184+
}
185+
186+
@Override
187+
public void attemptPermanentFailure(Throwable error) {
188+
endAttempt();
189+
}
190+
157191
private void endAttempt() {
158192
if (attemptSpan != null) {
159193
attemptSpan.end();

gax-java/gax/src/test/java/com/google/api/gax/tracing/SpanTracerTest.java

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,102 @@ void testResponseHeadersReceived_badFormat() {
136136
org.mockito.ArgumentMatchers.eq(ObservabilityAttributes.HTTP_RESPONSE_BODY_SIZE),
137137
org.mockito.ArgumentMatchers.eq(-1));
138138
}
139+
void testAttemptStarted_noRetryAttributes_grpc() {
140+
ApiTracerContext grpcContext =
141+
ApiTracerContext.newBuilder()
142+
.setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty())
143+
.setTransport(ApiTracerContext.Transport.GRPC)
144+
.build();
145+
SpanTracer grpcTracer = new SpanTracer(tracer, grpcContext, ATTEMPT_SPAN_NAME);
146+
147+
// Initial attempt, attemptNumber is 0
148+
grpcTracer.attemptStarted(new Object(), 0);
149+
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
150+
verify(spanBuilder).setAllAttributes(attributesCaptor.capture());
151+
assertThat(attributesCaptor.getValue().asMap())
152+
.doesNotContainKey(
153+
io.opentelemetry.api.common.AttributeKey.longKey(
154+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE));
155+
assertThat(attributesCaptor.getValue().asMap())
156+
.doesNotContainKey(
157+
io.opentelemetry.api.common.AttributeKey.longKey(
158+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE));
159+
}
160+
161+
@Test
162+
void testAttemptStarted_retryAttributes_grpc() {
163+
ApiTracerContext grpcContext =
164+
ApiTracerContext.newBuilder()
165+
.setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty())
166+
.setTransport(ApiTracerContext.Transport.GRPC)
167+
.build();
168+
SpanTracer grpcTracer = new SpanTracer(tracer, grpcContext, ATTEMPT_SPAN_NAME);
169+
170+
// N-th retry, attemptNumber is 5
171+
grpcTracer.attemptStarted(new Object(), 5);
172+
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
173+
verify(spanBuilder).setAllAttributes(attributesCaptor.capture());
174+
java.util.Map<io.opentelemetry.api.common.AttributeKey<?>, Object> capturedAttributes =
175+
attributesCaptor.getValue().asMap();
176+
assertThat(capturedAttributes)
177+
.containsEntry(
178+
io.opentelemetry.api.common.AttributeKey.longKey(
179+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE),
180+
5L);
181+
assertThat(capturedAttributes)
182+
.doesNotContainKey(
183+
io.opentelemetry.api.common.AttributeKey.longKey(
184+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE));
185+
}
186+
187+
@Test
188+
void testAttemptStarted_noRetryAttributes_http() {
189+
ApiTracerContext httpContext =
190+
ApiTracerContext.newBuilder()
191+
.setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty())
192+
.setTransport(ApiTracerContext.Transport.HTTP)
193+
.build();
194+
SpanTracer httpTracer = new SpanTracer(tracer, httpContext, ATTEMPT_SPAN_NAME);
195+
196+
// Initial attempt, attemptNumber is 0
197+
httpTracer.attemptStarted(new Object(), 0);
198+
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
199+
verify(spanBuilder).setAllAttributes(attributesCaptor.capture());
200+
java.util.Map<io.opentelemetry.api.common.AttributeKey<?>, Object> capturedAttributes =
201+
attributesCaptor.getValue().asMap();
202+
assertThat(capturedAttributes)
203+
.doesNotContainKey(
204+
io.opentelemetry.api.common.AttributeKey.longKey(
205+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE));
206+
assertThat(capturedAttributes)
207+
.doesNotContainKey(
208+
io.opentelemetry.api.common.AttributeKey.longKey(
209+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE));
210+
}
211+
212+
@Test
213+
void testAttemptStarted_retryAttributes_http() {
214+
ApiTracerContext httpContext =
215+
ApiTracerContext.newBuilder()
216+
.setLibraryMetadata(com.google.api.gax.rpc.LibraryMetadata.empty())
217+
.setTransport(ApiTracerContext.Transport.HTTP)
218+
.build();
219+
SpanTracer httpTracer = new SpanTracer(tracer, httpContext, ATTEMPT_SPAN_NAME);
220+
221+
// N-th retry, attemptNumber is 5
222+
httpTracer.attemptStarted(new Object(), 5);
223+
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
224+
verify(spanBuilder).setAllAttributes(attributesCaptor.capture());
225+
java.util.Map<io.opentelemetry.api.common.AttributeKey<?>, Object> capturedAttributes =
226+
attributesCaptor.getValue().asMap();
227+
assertThat(capturedAttributes)
228+
.doesNotContainKey(
229+
io.opentelemetry.api.common.AttributeKey.longKey(
230+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE));
231+
assertThat(capturedAttributes)
232+
.containsEntry(
233+
io.opentelemetry.api.common.AttributeKey.longKey(
234+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE),
235+
5L);
236+
}
139237
}

java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelTracing.java

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,23 @@
3131
package com.google.showcase.v1beta1.it;
3232

3333
import static com.google.common.truth.Truth.assertThat;
34+
import static org.junit.Assert.assertThrows;
3435

36+
import com.google.api.client.http.javanet.NetHttpTransport;
37+
import com.google.api.gax.core.NoCredentialsProvider;
38+
import com.google.api.gax.retrying.RetrySettings;
39+
import com.google.api.gax.rpc.StatusCode;
40+
import com.google.api.gax.rpc.UnavailableException;
3541
import com.google.api.gax.tracing.ObservabilityAttributes;
3642
import com.google.api.gax.tracing.SpanTracer;
3743
import com.google.api.gax.tracing.SpanTracerFactory;
44+
import com.google.rpc.Status;
3845
import com.google.showcase.v1beta1.EchoClient;
3946
import com.google.showcase.v1beta1.EchoRequest;
47+
import com.google.showcase.v1beta1.EchoSettings;
4048
import com.google.showcase.v1beta1.it.util.TestClientInitializer;
49+
import com.google.showcase.v1beta1.stub.EchoStub;
50+
import com.google.showcase.v1beta1.stub.EchoStubSettings;
4151
import io.opentelemetry.api.GlobalOpenTelemetry;
4252
import io.opentelemetry.api.common.AttributeKey;
4353
import io.opentelemetry.api.trace.SpanKind;
@@ -205,4 +215,146 @@ void testTracing_successfulEcho_httpjson() throws Exception {
205215
.isAtLeast(1L);
206216
}
207217
}
218+
219+
@Test
220+
void testTracing_retry_grpc() throws Exception {
221+
final int attempts = 5;
222+
final StatusCode.Code statusCode = StatusCode.Code.UNAVAILABLE;
223+
// A custom EchoClient is used in this test because retries have jitter, and we cannot
224+
// predict the number of attempts that are scheduled for an RPC invocation otherwise.
225+
// The custom retrySettings limit to a set number of attempts before the call gives up.
226+
RetrySettings retrySettings =
227+
RetrySettings.newBuilder()
228+
.setTotalTimeout(org.threeten.bp.Duration.ofMillis(5000L))
229+
.setMaxAttempts(attempts)
230+
.build();
231+
232+
EchoStubSettings.Builder grpcEchoSettingsBuilder = EchoStubSettings.newBuilder();
233+
grpcEchoSettingsBuilder
234+
.echoSettings()
235+
.setRetrySettings(retrySettings)
236+
.setRetryableCodes(statusCode);
237+
EchoSettings grpcEchoSettings = EchoSettings.create(grpcEchoSettingsBuilder.build());
238+
grpcEchoSettings =
239+
grpcEchoSettings.toBuilder()
240+
.setCredentialsProvider(NoCredentialsProvider.create())
241+
.setTransportChannelProvider(EchoSettings.defaultGrpcTransportProviderBuilder().build())
242+
.setEndpoint("localhost:7469")
243+
.build();
244+
245+
SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk);
246+
247+
EchoStubSettings echoStubSettings =
248+
(EchoStubSettings)
249+
grpcEchoSettings.getStubSettings().toBuilder().setTracerFactory(tracingFactory).build();
250+
EchoStub stub = echoStubSettings.createStub();
251+
EchoClient grpcClient = EchoClient.create(stub);
252+
253+
EchoRequest echoRequest =
254+
EchoRequest.newBuilder()
255+
.setError(Status.newBuilder().setCode(statusCode.ordinal()).build())
256+
.build();
257+
258+
assertThrows(UnavailableException.class, () -> grpcClient.echo(echoRequest));
259+
260+
List<SpanData> spans = spanExporter.getFinishedSpanItems();
261+
assertThat(spans).hasSize(attempts); // Expect exactly one span for the successful retry
262+
263+
// This single span represents the successful retry, which has resend_count=1
264+
// The first attempt has no resend_count. The subsequent retries will have a resend_count,
265+
// starting from 1.
266+
List<Long> resendCounts =
267+
spans.stream()
268+
.map(
269+
span ->
270+
(Long)
271+
span.getAttributes()
272+
.asMap()
273+
.get(
274+
AttributeKey.longKey(
275+
ObservabilityAttributes.GRPC_RESEND_COUNT_ATTRIBUTE)))
276+
.filter(java.util.Objects::nonNull)
277+
.sorted()
278+
.collect(java.util.stream.Collectors.toList());
279+
280+
List<Long> expectedCounts =
281+
java.util.stream.LongStream.range(1, attempts)
282+
.boxed()
283+
.collect(java.util.stream.Collectors.toList());
284+
assertThat(resendCounts).containsExactlyElementsIn(expectedCounts).inOrder();
285+
}
286+
287+
@Test
288+
void testTracing_retry_httpjson() throws Exception {
289+
final int attempts = 5;
290+
final StatusCode.Code statusCode = StatusCode.Code.UNAVAILABLE;
291+
// A custom EchoClient is used in this test because retries have jitter, and we cannot
292+
// predict the number of attempts that are scheduled for an RPC invocation otherwise.
293+
// The custom retrySettings limit to a set number of attempts before the call gives up.
294+
RetrySettings retrySettings =
295+
RetrySettings.newBuilder()
296+
.setTotalTimeout(org.threeten.bp.Duration.ofMillis(5000L))
297+
.setMaxAttempts(attempts)
298+
.build();
299+
300+
EchoStubSettings.Builder httpJsonEchoSettingsBuilder = EchoStubSettings.newHttpJsonBuilder();
301+
httpJsonEchoSettingsBuilder
302+
.echoSettings()
303+
.setRetrySettings(retrySettings)
304+
.setRetryableCodes(statusCode);
305+
EchoSettings httpJsonEchoSettings = EchoSettings.create(httpJsonEchoSettingsBuilder.build());
306+
httpJsonEchoSettings =
307+
httpJsonEchoSettings.toBuilder()
308+
.setCredentialsProvider(NoCredentialsProvider.create())
309+
.setTransportChannelProvider(
310+
EchoSettings.defaultHttpJsonTransportProviderBuilder()
311+
.setHttpTransport(
312+
new NetHttpTransport.Builder().doNotValidateCertificate().build())
313+
.setEndpoint("http://localhost:7469")
314+
.build())
315+
.build();
316+
317+
SpanTracerFactory tracingFactory = new SpanTracerFactory(openTelemetrySdk);
318+
319+
EchoStubSettings echoStubSettings =
320+
(EchoStubSettings)
321+
httpJsonEchoSettings.getStubSettings().toBuilder()
322+
.setTracerFactory(tracingFactory)
323+
.build();
324+
EchoStub stub = echoStubSettings.createStub();
325+
EchoClient httpClient = EchoClient.create(stub);
326+
327+
EchoRequest echoRequest =
328+
EchoRequest.newBuilder()
329+
.setError(Status.newBuilder().setCode(statusCode.ordinal()).build())
330+
.build();
331+
332+
assertThrows(UnavailableException.class, () -> httpClient.echo(echoRequest));
333+
334+
List<SpanData> spans = spanExporter.getFinishedSpanItems();
335+
assertThat(spans).hasSize(attempts); // Expect exactly one span for the successful retry
336+
337+
// This single span represents the successful retry, which has resend_count=1
338+
// The first attempt has no resend_count. The subsequent retries will have a resend_count,
339+
// starting from 1.
340+
List<Long> resendCounts =
341+
spans.stream()
342+
.map(
343+
span ->
344+
(Long)
345+
span.getAttributes()
346+
.asMap()
347+
.get(
348+
AttributeKey.longKey(
349+
ObservabilityAttributes.HTTP_RESEND_COUNT_ATTRIBUTE)))
350+
.filter(java.util.Objects::nonNull)
351+
.sorted()
352+
.collect(java.util.stream.Collectors.toList());
353+
354+
List<Long> expectedCounts =
355+
java.util.stream.LongStream.range(1, attempts)
356+
.boxed()
357+
.collect(java.util.stream.Collectors.toList());
358+
assertThat(resendCounts).containsExactlyElementsIn(expectedCounts).inOrder();
359+
}
208360
}

0 commit comments

Comments
 (0)