From eb5531dc084d7e158cd931b322604b73812f7a16 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Tue, 8 Jan 2019 18:29:03 -0500 Subject: [PATCH 01/12] WIP: implement ApiTracer using opencensus api --- build.gradle | 4 + gax/build.gradle | 3 +- .../com/google/api/gax/tracing/ApiTracer.java | 3 +- .../google/api/gax/tracing/NoopApiTracer.java | 2 +- .../api/gax/tracing/OpencensusTracer.java | 218 ++++++++++++++++++ .../gax/tracing/OpencensusTracerFactory.java | 84 +++++++ 6 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java create mode 100644 gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java diff --git a/build.gradle b/build.gradle index af7717e04..1c7c1cd41 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,9 @@ apply plugin: 'io.codearte.nexus-staging' project.version = "1.35.2-SNAPSHOT" // {x-version-update:gax:current} ext { + // When upgrading grpc, make sure to upgrade opencensusVersion to be consistent with grpc. grpcVersion = '1.16.1' + opencensusVersion = '0.12.3' commonProtosVersion = '1.12.0' authVersion = '0.12.0' // Project names not used for release @@ -108,6 +110,7 @@ subprojects { ext { grpcVersion = grpcVersion + opencensusVersion = opencensusVersion commonProtosVersion = commonProtosVersion // Shortcuts for libraries we are using @@ -126,6 +129,7 @@ subprojects { authCredentials: "com.google.auth:google-auth-library-credentials:${authVersion}", commonProtos: "com.google.api.grpc:proto-google-common-protos:${commonProtosVersion}", apiCommon: "com.google.api:api-common:1.7.0", + opencensusApi: "io.opencensus:opencensus-api:${opencensusVersion}", // Testing junit: 'junit:junit:4.12', diff --git a/gax/build.gradle b/gax/build.gradle index 7161d30e8..a1bbb4d99 100644 --- a/gax/build.gradle +++ b/gax/build.gradle @@ -19,7 +19,8 @@ dependencies { libraries.jsr305, libraries.threetenbp, libraries.auth, - libraries.apiCommon + libraries.apiCommon, + libraries.opencensusApi compileOnly libraries.autovalue diff --git a/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java b/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java index 8a137b10b..18bf348ae 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java +++ b/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java @@ -93,8 +93,9 @@ public interface ApiTracer { /** * Adds an annotation that the attempt failed and that no further attempts will be made because * retry limits have been reached. + * @param error */ - void attemptFailedRetriesExhausted(); + void attemptFailedRetriesExhausted(Throwable error); /** * Adds an annotation that the attempt failed and that no further attempts will be made because diff --git a/gax/src/main/java/com/google/api/gax/tracing/NoopApiTracer.java b/gax/src/main/java/com/google/api/gax/tracing/NoopApiTracer.java index 4b569b7f9..173aa2070 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/NoopApiTracer.java +++ b/gax/src/main/java/com/google/api/gax/tracing/NoopApiTracer.java @@ -91,7 +91,7 @@ public void attemptFailed(Throwable error, Duration delay) { } @Override - public void attemptFailedRetriesExhausted() { + public void attemptFailedRetriesExhausted(Throwable error) { // noop } diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java new file mode 100644 index 000000000..faafd9e8e --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java @@ -0,0 +1,218 @@ +/* + * Copyright 2019 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.tracing; + +import com.google.api.client.util.Maps; +import com.google.api.core.BetaApi; +import com.google.api.gax.rpc.ApiException; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import io.opencensus.trace.AttributeValue; +import io.opencensus.trace.EndSpanOptions; +import io.opencensus.trace.Span; +import io.opencensus.trace.Status; +import io.opencensus.trace.Status.CanonicalCode; +import io.opencensus.trace.Tracer; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import org.threeten.bp.Duration; + +/** + * Implementation of {@link ApiTracer} that uses OpenCensus. + * + *

This implementation creates an OpenCensus {@link Span} for every tracer and annotates that + * {@link Span} with various events throughout the lifecycle of the logical operation. + */ +@BetaApi("Surface for tracing is not yet stable") +public class OpencensusTracer implements ApiTracer { + private final Tracer tracer; + private final Span span; + + private long currentAttemptId; + private long attemptRequests = 0; + private long attemptResponses = 0; + private long totalAttemptRequests = 0; + private long totalAttemptResponses = 0; + + public OpencensusTracer(@Nonnull Tracer tracer, @Nonnull Span span) { + this.tracer = Preconditions.checkNotNull(tracer, "tracer can't be null"); + this.span = Preconditions.checkNotNull(span, "span can't be null"); + } + + @Override + public Scope inScope() { + final io.opencensus.common.Scope scope = tracer.withSpan(span); + + return new Scope() { + @Override + public void close() { + scope.close(); + } + }; + } + + @Override + public void operationSucceeded() { + Map attributes = Maps.newHashMap(); + + attributes.put("attempt count", AttributeValue.longAttributeValue(currentAttemptId + 1)); + attributes.put("total request count", AttributeValue.longAttributeValue(totalAttemptRequests)); + attributes.put("total response count", AttributeValue.longAttributeValue(totalAttemptResponses)); + + span.putAttributes(attributes); + + span.end(); + } + + @Override + public void operationFailed(Throwable error) { + Map attributes = Maps.newHashMap(); + + attributes.put("attempt count", AttributeValue.longAttributeValue(currentAttemptId + 1)); + attributes.put("total request count", AttributeValue.longAttributeValue(totalAttemptRequests)); + attributes.put("total response count", AttributeValue.longAttributeValue(totalAttemptResponses)); + + span.putAttributes(attributes); + + span.end(EndSpanOptions.builder().setStatus(convertErrorToStatus(error)).build()); + } + + @Override + public void connectionSelected(int id) { + span.addAnnotation( + "Connection selected", ImmutableMap.of("id", AttributeValue.longAttributeValue(id))); + } + + @Override + public void attemptStarted(int attemptNumber) { + currentAttemptId = attemptNumber; + + HashMap attributes = Maps.newHashMap(); + populateAttemptNumber(attributes); + + span.addAnnotation("Attempt started", attributes); + } + + @Override + public void attemptSucceeded() { + Map attributes = baseAttemptAttributes(); + + span.addAnnotation("Attempt succeeded", attributes); + } + + @Override + public void attemptFailed(Throwable error, Duration delay) { + Map attributes = baseAttemptAttributes(); + attributes.put("delay ms", AttributeValue.longAttributeValue(delay.toMillis())); + + String msg = error != null ? "Attempt failed" : "Operation incomplete"; + span.addAnnotation(msg + ", scheduling next attempt", attributes); + } + + @Override + public void attemptFailedRetriesExhausted(Throwable error) { + Map attributes = baseAttemptAttributes(); + populateError(attributes, error); + + span.addAnnotation("Attempts exhausted", attributes); + } + + @Override + public void attemptPermanentFailure(Throwable error) { + Map attributes = baseAttemptAttributes(); + populateError(attributes, error); + + span.addAnnotation("Attempt failed, error not retryable ", attributes); + } + + @Override + public void responseReceived() { + attemptResponses++; + totalAttemptResponses++; + } + + @Override + public void requestSent() { + attemptRequests++; + totalAttemptRequests++; + } + + @Override + public void batchRequestSent(long elementCount, long requestSize) { + span.putAttribute("batch count", AttributeValue.longAttributeValue(elementCount)); + span.putAttribute("request size", AttributeValue.longAttributeValue(requestSize)); + } + + + private Map baseAttemptAttributes() { + HashMap attributes = Maps.newHashMap(); + + populateAttemptNumber(attributes); + attributes.put("attempt request count", AttributeValue.longAttributeValue(attemptRequests)); + attributes.put("attempt response count", AttributeValue.longAttributeValue(attemptResponses)); + + return attributes; + } + + private void populateAttemptNumber(Map attributes) { + attributes.put("attempt", AttributeValue.longAttributeValue(currentAttemptId)); + } + + private void populateError(Map attributes, Throwable error) { + if (error == null) { + attributes.put("errorCode", null); + return; + } + + Status status = convertErrorToStatus(error); + + attributes.put("errorCode", + AttributeValue.stringAttributeValue(status.getCanonicalCode().toString())); + } + + private static Status convertErrorToStatus(Throwable error) { + if (!(error instanceof ApiException)) { + return Status.UNKNOWN.withDescription(error.getMessage()); + } + + ApiException apiException = (ApiException) error; + + Status.CanonicalCode code; + try { + code = Status.CanonicalCode.valueOf(apiException.getStatusCode().getCode().name()); + } catch (IllegalArgumentException e) { + code = CanonicalCode.UNKNOWN; + } + + return code.toStatus().withDescription(error.getMessage()); + } +} diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java new file mode 100644 index 000000000..03470859f --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java @@ -0,0 +1,84 @@ +/* + * Copyright 2019 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.tracing; + +import com.google.api.core.InternalApi; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import io.opencensus.trace.Span; +import io.opencensus.trace.Tracer; +import io.opencensus.trace.Tracing; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@InternalApi("For google-cloud-java client use only") +public final class OpencensusTracerFactory implements ApiTracerFactory { + @Nonnull private final Tracer internalTracer; + @Nullable private final String clientNameOverride; + + public OpencensusTracerFactory() { + this(null); + } + + public OpencensusTracerFactory(@Nullable String clientNameOverride) { + this(Tracing.getTracer(), clientNameOverride); + } + + @InternalApi("Visible for testing") + OpencensusTracerFactory(Tracer internalTracer, @Nullable String clientNameOverride) { + this.internalTracer = Preconditions.checkNotNull(internalTracer, "internalTracer can't be null"); + this.clientNameOverride = clientNameOverride; + } + + @Override + public ApiTracer newTracer(SpanName spanName) { + Span span = internalTracer.spanBuilder(spanName.toString()).setRecordEvents(true).startSpan(); + + return new OpencensusTracer(internalTracer, span); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OpencensusTracerFactory that = (OpencensusTracerFactory) o; + return Objects.equal(internalTracer, that.internalTracer) && + Objects.equal(clientNameOverride, that.clientNameOverride); + } + + @Override + public int hashCode() { + return Objects.hashCode(internalTracer, clientNameOverride); + } +} \ No newline at end of file From 486832c40002e8d56ceb44da375271db65ddba4c Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Tue, 8 Jan 2019 23:25:19 -0500 Subject: [PATCH 02/12] add tests --- .../com/google/api/gax/tracing/ApiTracer.java | 1 + .../api/gax/tracing/OpencensusTracer.java | 200 ++++++++++++++++-- .../gax/tracing/OpencensusTracerFactory.java | 39 +++- .../com/google/api/gax/tracing/SpanName.java | 5 + .../tracing/OpencensusTracerFactoryTest.java | 117 ++++++++++ .../api/gax/tracing/OpencensusTracerTest.java | 185 ++++++++++++++++ 6 files changed, 521 insertions(+), 26 deletions(-) create mode 100644 gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerFactoryTest.java create mode 100644 gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java diff --git a/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java b/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java index 18bf348ae..f11b1af69 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java +++ b/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java @@ -93,6 +93,7 @@ public interface ApiTracer { /** * Adds an annotation that the attempt failed and that no further attempts will be made because * retry limits have been reached. + * * @param error */ void attemptFailedRetriesExhausted(Throwable error); diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java index faafd9e8e..12da2f638 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java @@ -27,11 +27,11 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - package com.google.api.gax.tracing; import com.google.api.client.util.Maps; import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; import com.google.api.gax.rpc.ApiException; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; @@ -49,8 +49,132 @@ /** * Implementation of {@link ApiTracer} that uses OpenCensus. * - *

This implementation creates an OpenCensus {@link Span} for every tracer and annotates that + *

This implementation wraps an OpenCensus {@link Span} for every tracer and annotates that * {@link Span} with various events throughout the lifecycle of the logical operation. + * + *

+ *   ClientName.UnaryMethod
+ *     - attributes:
+ *       - {@code attempt count}: number of attempts sent before the logical operation completed
+ *       - status: the status code of the last attempt
+ *     - annotations:
+ *       - Attempt started
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *       - Connection selected:
+ *         - attributes:
+ *           - id: the id of the connection in the local connection pool
+ *       - Attempt failed, scheduling next attempt
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the status code of the failed attempt
+ *           - delay: number of milliseconds to wait before trying again
+ *       - Attempts exhausted
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the status code of the failed attempt
+ *       - Attempt failed, error not retryable
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the non-retryable status code of the failed attempt
+ * 
+ * + *
+ *   ClientName.ServerStreamingMethod
+ *     - attributes:
+ *       - {@code attempt count}: number of attempts sent before the logical operation completed
+ *       - status: the status code of the last attempt
+ *       - total response count: number of messages received across all of the attempts
+ *     - annotations:
+ *       - Attempt started
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *       - Connection selected:
+ *         - attributes:
+ *           - id: the id of the connection in the local connection pool
+ *       - Attempt failed, scheduling next attempt
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the status code of the failed attempt
+ *           - delay: number of milliseconds to wait before trying again
+ *           - attempt response count: number of responses received in this attempt
+ *       - Attempts exhausted
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the status code of the failed attempt
+ *           - attempt response count: number of responses received in this attempt
+ *       - Attempt failed, error not retryable
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the non-retryable status code of the failed attempt
+ *           - attempt response count: number of responses received in this attempt
+ * 
+ * + *
+ *   ClientName.ClientStreamingMethod
+ *     - attributes:
+ *       - {@code attempt count}: number of attempts sent before the logical operation completed
+ *       - status: the status code of the last attempt
+ *       - total request count: number of messages sent across all of the attempts
+ *     - annotations:
+ *       - Attempt started
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *       - Connection selected:
+ *         - attributes:
+ *           - id: the id of the connection in the local connection pool
+ *       - Attempt failed, scheduling next attempt
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the status code of the failed attempt
+ *           - delay: number of milliseconds to wait before trying again
+ *           - attempt request count: number of requests sent in this attempt
+ *       - Attempts exhausted
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the status code of the failed attempt
+ *           - attempt request count: number of requests sent in this attempt
+ *       - Attempt failed, error not retryable
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the non-retryable status code of the failed attempt
+ *           - attempt request count: number of requests sent in this attempt
+ * 
+ * + *
+ *   ClientName.BidiStreamingMethod
+ *     - attributes:
+ *       - {@code attempt count}: number of attempts sent before the logical operation completed
+ *       - status: the status code of the last attempt
+ *       - total request count: number of messages sent across all of the attempts
+ *       - total response count: number of messages received across all of the attempts
+ *     - annotations:
+ *       - Attempt started
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *       - Connection selected:
+ *         - attributes:
+ *           - id: the id of the connection in the local connection pool
+ *       - Attempt failed, scheduling next attempt
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the status code of the failed attempt
+ *           - delay: number of milliseconds to wait before trying again
+ *           - attempt request count: number of requests sent in this attempt
+ *           - attempt response count: number of responses received in this attempt
+ *       - Attempts exhausted
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the status code of the failed attempt
+ *           - attempt request count: number of requests sent in this attempt
+ *           - attempt response count: number of responses received in this attempt
+ *       - Attempt failed, error not retryable
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - status: the non-retryable status code of the failed attempt
+ *           - attempt request count: number of requests sent in this attempt
+ *           - attempt response count: number of responses received in this attempt
+ * 
*/ @BetaApi("Surface for tracing is not yet stable") public class OpencensusTracer implements ApiTracer { @@ -63,11 +187,17 @@ public class OpencensusTracer implements ApiTracer { private long totalAttemptRequests = 0; private long totalAttemptResponses = 0; - public OpencensusTracer(@Nonnull Tracer tracer, @Nonnull Span span) { + OpencensusTracer(@Nonnull Tracer tracer, @Nonnull Span span) { this.tracer = Preconditions.checkNotNull(tracer, "tracer can't be null"); this.span = Preconditions.checkNotNull(span, "span can't be null"); } + @InternalApi("Visible for testing") + Span getSpan() { + return span; + } + + /** {@inheritDoc} */ @Override public Scope inScope() { final io.opencensus.common.Scope scope = tracer.withSpan(span); @@ -80,41 +210,37 @@ public void close() { }; } + /** {@inheritDoc} */ @Override public void operationSucceeded() { - Map attributes = Maps.newHashMap(); - - attributes.put("attempt count", AttributeValue.longAttributeValue(currentAttemptId + 1)); - attributes.put("total request count", AttributeValue.longAttributeValue(totalAttemptRequests)); - attributes.put("total response count", AttributeValue.longAttributeValue(totalAttemptResponses)); + Map attributes = baseOperationAttributes(); span.putAttributes(attributes); - span.end(); } + /** {@inheritDoc} */ @Override public void operationFailed(Throwable error) { - Map attributes = Maps.newHashMap(); - - attributes.put("attempt count", AttributeValue.longAttributeValue(currentAttemptId + 1)); - attributes.put("total request count", AttributeValue.longAttributeValue(totalAttemptRequests)); - attributes.put("total response count", AttributeValue.longAttributeValue(totalAttemptResponses)); + Map attributes = baseOperationAttributes(); span.putAttributes(attributes); - span.end(EndSpanOptions.builder().setStatus(convertErrorToStatus(error)).build()); } + /** {@inheritDoc} */ @Override public void connectionSelected(int id) { span.addAnnotation( "Connection selected", ImmutableMap.of("id", AttributeValue.longAttributeValue(id))); } + /** {@inheritDoc} */ @Override public void attemptStarted(int attemptNumber) { currentAttemptId = attemptNumber; + attemptRequests = 0; + attemptResponses = 0; HashMap attributes = Maps.newHashMap(); populateAttemptNumber(attributes); @@ -122,6 +248,7 @@ public void attemptStarted(int attemptNumber) { span.addAnnotation("Attempt started", attributes); } + /** {@inheritDoc} */ @Override public void attemptSucceeded() { Map attributes = baseAttemptAttributes(); @@ -129,15 +256,18 @@ public void attemptSucceeded() { span.addAnnotation("Attempt succeeded", attributes); } + /** {@inheritDoc} */ @Override public void attemptFailed(Throwable error, Duration delay) { Map attributes = baseAttemptAttributes(); attributes.put("delay ms", AttributeValue.longAttributeValue(delay.toMillis())); + populateError(attributes, error); String msg = error != null ? "Attempt failed" : "Operation incomplete"; span.addAnnotation(msg + ", scheduling next attempt", attributes); } + /** {@inheritDoc} */ @Override public void attemptFailedRetriesExhausted(Throwable error) { Map attributes = baseAttemptAttributes(); @@ -146,39 +276,64 @@ public void attemptFailedRetriesExhausted(Throwable error) { span.addAnnotation("Attempts exhausted", attributes); } + /** {@inheritDoc} */ @Override public void attemptPermanentFailure(Throwable error) { Map attributes = baseAttemptAttributes(); populateError(attributes, error); - span.addAnnotation("Attempt failed, error not retryable ", attributes); + span.addAnnotation("Attempt failed, error not retryable", attributes); } + /** {@inheritDoc} */ @Override public void responseReceived() { attemptResponses++; totalAttemptResponses++; } + /** {@inheritDoc} */ @Override public void requestSent() { attemptRequests++; totalAttemptRequests++; } + /** {@inheritDoc} */ @Override public void batchRequestSent(long elementCount, long requestSize) { span.putAttribute("batch count", AttributeValue.longAttributeValue(elementCount)); span.putAttribute("request size", AttributeValue.longAttributeValue(requestSize)); } + private Map baseOperationAttributes() { + HashMap attributes = Maps.newHashMap(); + + attributes.put("attempt count", AttributeValue.longAttributeValue(currentAttemptId + 1)); + + if (totalAttemptRequests > 0) { + attributes.put( + "total request count", AttributeValue.longAttributeValue(totalAttemptRequests)); + } + if (totalAttemptResponses > 0) { + attributes.put( + "total response count", AttributeValue.longAttributeValue(totalAttemptResponses)); + } + + return attributes; + } private Map baseAttemptAttributes() { HashMap attributes = Maps.newHashMap(); populateAttemptNumber(attributes); - attributes.put("attempt request count", AttributeValue.longAttributeValue(attemptRequests)); - attributes.put("attempt response count", AttributeValue.longAttributeValue(attemptResponses)); + + if (attemptRequests > 0) { + attributes.put("attempt request count", AttributeValue.longAttributeValue(attemptRequests)); + } + if (attemptResponses > 0) { + attributes.put("attempt response count", AttributeValue.longAttributeValue(attemptResponses)); + } return attributes; } @@ -189,17 +344,18 @@ private void populateAttemptNumber(Map attributes) { private void populateError(Map attributes, Throwable error) { if (error == null) { - attributes.put("errorCode", null); + attributes.put("status", null); return; } Status status = convertErrorToStatus(error); - attributes.put("errorCode", - AttributeValue.stringAttributeValue(status.getCanonicalCode().toString())); + attributes.put( + "status", AttributeValue.stringAttributeValue(status.getCanonicalCode().toString())); } - private static Status convertErrorToStatus(Throwable error) { + @InternalApi("Visible for testing") + static Status convertErrorToStatus(Throwable error) { if (!(error instanceof ApiException)) { return Status.UNKNOWN.withDescription(error.getMessage()); } diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java index 03470859f..59f7f4657 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java @@ -38,27 +38,58 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +/** + * A {@link ApiTracerFactory} to build instances of {@link OpencensusTracer}. + * + *

This class wraps the {@link Tracer} provided by Opencensus in {@code Tracing.getTracer()}. It + * will be used to create new spans and wrap them in {@link OpencensusTracer} defined in gax. + */ @InternalApi("For google-cloud-java client use only") public final class OpencensusTracerFactory implements ApiTracerFactory { @Nonnull private final Tracer internalTracer; @Nullable private final String clientNameOverride; + /** + * Instantiates a new instance capturing the {@link io.opencensus.trace.Tracer} in {@code + * Tracing.getTracer}. + */ public OpencensusTracerFactory() { this(null); } + /** + * Instantiates a new instance capturing the {@link io.opencensus.trace.Tracer} in {@code + * Tracing.getTracer}. It will also override the service name of the grpc stub with a custom + * client name. This is useful disambiguate spans created outer manual written wrappers and around + * generated gapic spans. + * + * @param clientNameOverride the client name that will override all of the spans' client name. + */ public OpencensusTracerFactory(@Nullable String clientNameOverride) { this(Tracing.getTracer(), clientNameOverride); } + /** + * Instantiates a new instance with an explicit {@link io.opencensus.trace.Tracer}. It will also + * override the service name of the grpc stub with a custom client name. This is useful + * disambiguate spans created outer manual written wrappers and around generated gapic spans. + * + * @param internalTracer the Opencensus tracer to wrap. + * @param clientNameOverride the client name that will override all of the spans' client name. + */ @InternalApi("Visible for testing") OpencensusTracerFactory(Tracer internalTracer, @Nullable String clientNameOverride) { - this.internalTracer = Preconditions.checkNotNull(internalTracer, "internalTracer can't be null"); + this.internalTracer = + Preconditions.checkNotNull(internalTracer, "internalTracer can't be null"); this.clientNameOverride = clientNameOverride; } + /** {@inheritDoc */ @Override public ApiTracer newTracer(SpanName spanName) { + if (clientNameOverride != null) { + spanName = spanName.withClientName(clientNameOverride); + } Span span = internalTracer.spanBuilder(spanName.toString()).setRecordEvents(true).startSpan(); return new OpencensusTracer(internalTracer, span); @@ -73,12 +104,12 @@ public boolean equals(Object o) { return false; } OpencensusTracerFactory that = (OpencensusTracerFactory) o; - return Objects.equal(internalTracer, that.internalTracer) && - Objects.equal(clientNameOverride, that.clientNameOverride); + return Objects.equal(internalTracer, that.internalTracer) + && Objects.equal(clientNameOverride, that.clientNameOverride); } @Override public int hashCode() { return Objects.hashCode(internalTracer, clientNameOverride); } -} \ No newline at end of file +} diff --git a/gax/src/main/java/com/google/api/gax/tracing/SpanName.java b/gax/src/main/java/com/google/api/gax/tracing/SpanName.java index 80c3ed5a6..d4fb0eb5f 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/SpanName.java +++ b/gax/src/main/java/com/google/api/gax/tracing/SpanName.java @@ -65,4 +65,9 @@ public SpanName withClientName(String clientName) { public SpanName withMethodName(String methodName) { return of(getClientName(), methodName); } + + @Override + public String toString() { + return getClientName() + "." + getMethodName(); + } } diff --git a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerFactoryTest.java b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerFactoryTest.java new file mode 100644 index 000000000..2d462d86f --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerFactoryTest.java @@ -0,0 +1,117 @@ +/* + * Copyright 2018 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.tracing; + +import com.google.common.truth.Truth; +import io.opencensus.trace.BlankSpan; +import io.opencensus.trace.Sampler; +import io.opencensus.trace.Span; +import io.opencensus.trace.SpanBuilder; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.Tracer; +import java.util.List; +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(JUnit4.class) +public class OpencensusTracerFactoryTest { + @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); + private FakeTracer internalTracer; + + private OpencensusTracerFactory factory; + + @Before + public void setUp() { + internalTracer = new FakeTracer(); + } + + @Test + public void testSpanNamePassthrough() { + OpencensusTracerFactory factory = new OpencensusTracerFactory(internalTracer, null); + + factory.newTracer(SpanName.of("FakeClient", "FakeMethod")); + + Truth.assertThat(internalTracer.lastSpanName).isEqualTo("FakeClient.FakeMethod"); + } + + @Test + public void testSpanNameOverride() { + OpencensusTracerFactory factory = + new OpencensusTracerFactory(internalTracer, "OverridenClient"); + + factory.newTracer(SpanName.of("FakeClient", "FakeMethod")); + + Truth.assertThat(internalTracer.lastSpanName).isEqualTo("OverridenClient.FakeMethod"); + } + + private static class FakeTracer extends Tracer { + String lastSpanName; + + @Override + public SpanBuilder spanBuilderWithExplicitParent(String s, @Nullable Span span) { + lastSpanName = s; + return new FakeSpanBuilder(); + } + + @Override + public SpanBuilder spanBuilderWithRemoteParent(String s, @Nullable SpanContext spanContext) { + lastSpanName = s; + return new FakeSpanBuilder(); + } + } + + private static class FakeSpanBuilder extends SpanBuilder { + @Override + public SpanBuilder setSampler(Sampler sampler) { + return this; + } + + @Override + public SpanBuilder setParentLinks(List list) { + return this; + } + + @Override + public SpanBuilder setRecordEvents(boolean b) { + return this; + } + + @Override + public Span startSpan() { + return BlankSpan.INSTANCE; + } + } +} diff --git a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java new file mode 100644 index 000000000..e7f7af242 --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java @@ -0,0 +1,185 @@ +/* + * Copyright 2018 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.tracing; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; + +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.DeadlineExceededException; +import com.google.api.gax.rpc.NotFoundException; +import com.google.api.gax.rpc.StatusCode.Code; +import com.google.api.gax.rpc.testing.FakeStatusCode; +import io.opencensus.trace.AttributeValue; +import io.opencensus.trace.Span; +import io.opencensus.trace.Status; +import io.opencensus.trace.Tracer; +import java.util.Map; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.threeten.bp.Duration; + +@RunWith(JUnit4.class) +public class OpencensusTracerTest { + @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock private Tracer internalTracer; + @Mock private Span span; + @Captor private ArgumentCaptor> attributeCaptor; + + private OpencensusTracer tracer; + + @Before + public void setUp() { + tracer = new OpencensusTracer(internalTracer, span); + } + + @Test + public void testResponseCount() { + // Initial attempt got 2 messages, then failed + tracer.attemptStarted(0); + tracer.responseReceived(); + tracer.responseReceived(); + tracer.attemptFailed(new RuntimeException(), Duration.ofMillis(1)); + + // Next attempt got 1 message, then successfully finished the attempt and the logical operation. + tracer.attemptStarted(1); + tracer.responseReceived(); + tracer.attemptSucceeded(); + tracer.operationSucceeded(); + + verify(span) + .addAnnotation(eq("Attempt failed, scheduling next attempt"), attributeCaptor.capture()); + assertThat(attributeCaptor.getValue()) + .containsEntry("attempt response count", AttributeValue.longAttributeValue(2)); + + verify(span).addAnnotation(eq("Attempt succeeded"), attributeCaptor.capture()); + assertThat(attributeCaptor.getValue()) + .containsEntry("attempt response count", AttributeValue.longAttributeValue(1)); + + verify(span).putAttributes(attributeCaptor.capture()); + assertThat(attributeCaptor.getValue()) + .containsEntry("total response count", AttributeValue.longAttributeValue(3)); + } + + @Test + public void testRequestCount() { + // Initial attempt sent 2 messages, then failed + tracer.attemptStarted(0); + tracer.requestSent(); + tracer.requestSent(); + tracer.attemptFailed(new RuntimeException(), Duration.ofMillis(1)); + + // Next attempt sent 1 message, then successfully finished the attempt and the logical operation. + tracer.attemptStarted(1); + tracer.requestSent(); + tracer.attemptSucceeded(); + tracer.operationSucceeded(); + + verify(span) + .addAnnotation(eq("Attempt failed, scheduling next attempt"), attributeCaptor.capture()); + assertThat(attributeCaptor.getValue()) + .containsEntry("attempt request count", AttributeValue.longAttributeValue(2)); + + verify(span).addAnnotation(eq("Attempt succeeded"), attributeCaptor.capture()); + assertThat(attributeCaptor.getValue()) + .containsEntry("attempt request count", AttributeValue.longAttributeValue(1)); + + verify(span).putAttributes(attributeCaptor.capture()); + assertThat(attributeCaptor.getValue()) + .containsEntry("total request count", AttributeValue.longAttributeValue(3)); + } + + @Test + public void testAttemptNumber() { + tracer.attemptStarted(0); + tracer.attemptFailed(new RuntimeException(), Duration.ofMillis(1)); + tracer.attemptStarted(1); + tracer.attemptSucceeded(); + tracer.operationSucceeded(); + + verify(span) + .addAnnotation(eq("Attempt failed, scheduling next attempt"), attributeCaptor.capture()); + assertThat(attributeCaptor.getValue()) + .containsEntry("attempt", AttributeValue.longAttributeValue(0)); + + verify(span).addAnnotation(eq("Attempt succeeded"), attributeCaptor.capture()); + assertThat(attributeCaptor.getValue()) + .containsEntry("attempt", AttributeValue.longAttributeValue(1)); + + verify(span).putAttributes(attributeCaptor.capture()); + assertThat(attributeCaptor.getValue()) + .containsEntry("attempt count", AttributeValue.longAttributeValue(2)); + } + + @Test + public void testStatusCode() { + tracer.attemptStarted(0); + tracer.attemptFailed( + new DeadlineExceededException( + "deadline exceeded", null, new FakeStatusCode(Code.DEADLINE_EXCEEDED), true), + Duration.ofMillis(1)); + + tracer.attemptStarted(1); + ApiException permanentError = + new NotFoundException("not found", null, new FakeStatusCode(Code.NOT_FOUND), false); + tracer.attemptPermanentFailure(permanentError); + tracer.operationFailed(permanentError); + + verify(span) + .addAnnotation(eq("Attempt failed, scheduling next attempt"), attributeCaptor.capture()); + assertThat(attributeCaptor.getValue()) + .containsEntry("status", AttributeValue.stringAttributeValue("DEADLINE_EXCEEDED")); + + verify(span) + .addAnnotation(eq("Attempt failed, error not retryable"), attributeCaptor.capture()); + assertThat(attributeCaptor.getValue()) + .containsEntry("status", AttributeValue.stringAttributeValue("NOT_FOUND")); + } + + @Test + public void testErrorConversion() { + for (Code code : Code.values()) { + ApiException error = new ApiException("fake message", null, new FakeStatusCode(code), false); + Status opencensusStatus = OpencensusTracer.convertErrorToStatus(error); + assertThat(opencensusStatus.getDescription()).isEqualTo("fake message"); + assertThat(opencensusStatus.getCanonicalCode().toString()).isEqualTo(code.toString()); + } + } +} From 3c3ccd846987cd7a20827ddb95c76ad8546b2c8b Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Tue, 8 Jan 2019 23:26:36 -0500 Subject: [PATCH 03/12] oops --- .../main/java/com/google/api/gax/tracing/OpencensusTracer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java index 12da2f638..d1ee9b4e3 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java @@ -55,7 +55,7 @@ *

  *   ClientName.UnaryMethod
  *     - attributes:
- *       - {@code attempt count}: number of attempts sent before the logical operation completed
+ *       - attempt count: number of attempts sent before the logical operation completed
  *       - status: the status code of the last attempt
  *     - annotations:
  *       - Attempt started

From dc0461f64bf6cb41e45e2d27c722020f7f3163a0 Mon Sep 17 00:00:00 2001
From: Igor Bernstein 
Date: Wed, 9 Jan 2019 10:33:48 -0500
Subject: [PATCH 04/12] add more tests

---
 .../api/gax/tracing/OpencensusTracerTest.java | 138 ++++++++++++++++++
 1 file changed, 138 insertions(+)

diff --git a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java
index e7f7af242..b36c9dc66 100644
--- a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java
+++ b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java
@@ -38,7 +38,9 @@
 import com.google.api.gax.rpc.NotFoundException;
 import com.google.api.gax.rpc.StatusCode.Code;
 import com.google.api.gax.rpc.testing.FakeStatusCode;
+import com.google.common.collect.ImmutableMap;
 import io.opencensus.trace.AttributeValue;
+import io.opencensus.trace.EndSpanOptions;
 import io.opencensus.trace.Span;
 import io.opencensus.trace.Status;
 import io.opencensus.trace.Tracer;
@@ -51,6 +53,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.threeten.bp.Duration;
@@ -70,6 +73,141 @@ public void setUp() {
     tracer = new OpencensusTracer(internalTracer, span);
   }
 
+  @Test
+  public void testUnarySuccessExample() {
+    tracer.attemptStarted(0);
+    tracer.connectionSelected(1);
+    ApiException error0 =
+        new DeadlineExceededException(
+            "deadline exceeded", null, new FakeStatusCode(Code.DEADLINE_EXCEEDED), true);
+    tracer.attemptFailed(error0, Duration.ofMillis(5));
+
+    tracer.attemptStarted(1);
+    tracer.connectionSelected(2);
+    tracer.attemptSucceeded();
+    tracer.operationSucceeded();
+
+    // Attempt 0
+    verify(span)
+        .addAnnotation(
+            "Attempt started", ImmutableMap.of("attempt", AttributeValue.longAttributeValue(0)));
+
+    verify(span)
+        .addAnnotation(
+            "Connection selected", ImmutableMap.of("id", AttributeValue.longAttributeValue(1)));
+
+    verify(span)
+        .addAnnotation(
+            "Attempt failed, scheduling next attempt",
+            ImmutableMap.of(
+                "attempt", AttributeValue.longAttributeValue(0),
+                "delay ms", AttributeValue.longAttributeValue(5),
+                "status", AttributeValue.stringAttributeValue("DEADLINE_EXCEEDED")));
+
+    // Attempt 1
+    verify(span)
+        .addAnnotation(
+            "Attempt started", ImmutableMap.of("attempt", AttributeValue.longAttributeValue(1)));
+
+    verify(span)
+        .addAnnotation(
+            "Connection selected", ImmutableMap.of("id", AttributeValue.longAttributeValue(2)));
+
+    verify(span)
+        .addAnnotation(
+            "Attempt succeeded", ImmutableMap.of("attempt", AttributeValue.longAttributeValue(1)));
+
+    verify(span)
+        .putAttributes(ImmutableMap.of("attempt count", AttributeValue.longAttributeValue(2)));
+    verify(span).end();
+
+    Mockito.verifyNoMoreInteractions(span);
+  }
+
+  @Test
+  public void testBatchExample() {
+    tracer.batchRequestSent(100, 1000);
+    tracer.attemptStarted(0);
+    tracer.connectionSelected(1);
+    tracer.attemptSucceeded();
+    tracer.operationSucceeded();
+
+    verify(span).putAttribute("batch count", AttributeValue.longAttributeValue(100));
+    verify(span).putAttribute("request size", AttributeValue.longAttributeValue(1000));
+  }
+
+  @Test
+  public void testRetriesExhaustedExample() {
+    tracer.attemptStarted(0);
+    tracer.connectionSelected(1);
+    ApiException error0 =
+        new DeadlineExceededException(
+            "deadline exceeded", null, new FakeStatusCode(Code.DEADLINE_EXCEEDED), false);
+    tracer.attemptFailedRetriesExhausted(error0);
+    tracer.operationFailed(error0);
+
+    verify(span)
+        .addAnnotation(
+            "Attempt started", ImmutableMap.of("attempt", AttributeValue.longAttributeValue(0)));
+
+    verify(span)
+        .addAnnotation(
+            "Connection selected", ImmutableMap.of("id", AttributeValue.longAttributeValue(1)));
+
+    verify(span)
+        .addAnnotation(
+            "Attempts exhausted",
+            ImmutableMap.of(
+                "attempt", AttributeValue.longAttributeValue(0),
+                "status", AttributeValue.stringAttributeValue("DEADLINE_EXCEEDED")));
+
+    verify(span)
+        .putAttributes(ImmutableMap.of("attempt count", AttributeValue.longAttributeValue(1)));
+
+    verify(span)
+        .end(
+            EndSpanOptions.builder()
+                .setStatus(Status.DEADLINE_EXCEEDED.withDescription("deadline exceeded"))
+                .build());
+
+    Mockito.verifyNoMoreInteractions(span);
+  }
+
+  @Test
+  public void testFailureExample() {
+    tracer.attemptStarted(0);
+    tracer.connectionSelected(1);
+    ApiException error0 =
+        new NotFoundException("not found", null, new FakeStatusCode(Code.NOT_FOUND), false);
+    tracer.attemptPermanentFailure(error0);
+    tracer.operationFailed(error0);
+
+    verify(span)
+        .addAnnotation(
+            "Attempt started", ImmutableMap.of("attempt", AttributeValue.longAttributeValue(0)));
+
+    verify(span)
+        .addAnnotation(
+            "Connection selected", ImmutableMap.of("id", AttributeValue.longAttributeValue(1)));
+
+    verify(span)
+        .addAnnotation(
+            "Attempt failed, error not retryable",
+            ImmutableMap.of(
+                "attempt", AttributeValue.longAttributeValue(0),
+                "status", AttributeValue.stringAttributeValue("NOT_FOUND")));
+
+    verify(span)
+        .putAttributes(ImmutableMap.of("attempt count", AttributeValue.longAttributeValue(1)));
+
+    verify(span)
+        .end(
+            EndSpanOptions.builder()
+                .setStatus(Status.NOT_FOUND.withDescription("not found"))
+                .build());
+    Mockito.verifyNoMoreInteractions(span);
+  }
+
   @Test
   public void testResponseCount() {
     // Initial attempt got 2 messages, then failed

From 071e28f78fafd8314a104b523e5259dd39e24cad Mon Sep 17 00:00:00 2001
From: Igor Bernstein 
Date: Wed, 9 Jan 2019 11:03:17 -0500
Subject: [PATCH 05/12] remove unused method

---
 .../java/com/google/api/gax/tracing/OpencensusTracer.java    | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java
index d1ee9b4e3..06e829e87 100644
--- a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java
+++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java
@@ -192,11 +192,6 @@ public class OpencensusTracer implements ApiTracer {
     this.span = Preconditions.checkNotNull(span, "span can't be null");
   }
 
-  @InternalApi("Visible for testing")
-  Span getSpan() {
-    return span;
-  }
-
   /** {@inheritDoc} */
   @Override
   public Scope inScope() {

From bb6fec76dcfc3294827cdabcbf398439749a6eca Mon Sep 17 00:00:00 2001
From: Igor Bernstein 
Date: Fri, 11 Jan 2019 16:22:17 -0500
Subject: [PATCH 06/12] operation cancellation & static imports

---
 .../com/google/api/gax/tracing/ApiTracer.java |  3 ++
 .../google/api/gax/tracing/NoopApiTracer.java |  5 +++
 .../api/gax/tracing/OpencensusTracer.java     | 17 +++++++
 .../api/gax/tracing/OpencensusTracerTest.java | 44 ++++++++++++++++---
 4 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java b/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java
index ed9b793a9..c3211189e 100644
--- a/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java
+++ b/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java
@@ -88,6 +88,9 @@ public interface ApiTracer {
   /** Adds an annotation that the attempt succeeded. */
   void attemptSucceeded();
 
+  /** Add an annotation that the attempt was cancelled by the user. */
+  void attemptCancelled();
+
   /**
    * Adds an annotation that the attempt failed, but another attempt will be made after the delay.
    *
diff --git a/gax/src/main/java/com/google/api/gax/tracing/NoopApiTracer.java b/gax/src/main/java/com/google/api/gax/tracing/NoopApiTracer.java
index 3e58560cd..412afde51 100644
--- a/gax/src/main/java/com/google/api/gax/tracing/NoopApiTracer.java
+++ b/gax/src/main/java/com/google/api/gax/tracing/NoopApiTracer.java
@@ -90,6 +90,11 @@ public void attemptSucceeded() {
     // noop
   }
 
+  @Override
+  public void attemptCancelled() {
+    // noop
+  }
+
   @Override
   public void attemptFailed(Throwable error, Duration delay) {
     // noop
diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java
index 06e829e87..7d606eab8 100644
--- a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java
+++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java
@@ -214,6 +214,16 @@ public void operationSucceeded() {
     span.end();
   }
 
+  @Override
+  public void operationCancelled() {
+    Map attributes = baseOperationAttributes();
+    span.putAttributes(attributes);
+    span.end(
+        EndSpanOptions.builder()
+            .setStatus(Status.CANCELLED.withDescription("Cancelled by caller"))
+            .build());
+  }
+
   /** {@inheritDoc} */
   @Override
   public void operationFailed(Throwable error) {
@@ -251,6 +261,13 @@ public void attemptSucceeded() {
     span.addAnnotation("Attempt succeeded", attributes);
   }
 
+  @Override
+  public void attemptCancelled() {
+    Map attributes = baseAttemptAttributes();
+
+    span.addAnnotation("Attempt cancelled", attributes);
+  }
+
   /** {@inheritDoc} */
   @Override
   public void attemptFailed(Throwable error, Duration delay) {
diff --git a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java
index b36c9dc66..169300c89 100644
--- a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java
+++ b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java
@@ -32,6 +32,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import com.google.api.gax.rpc.ApiException;
 import com.google.api.gax.rpc.DeadlineExceededException;
@@ -53,14 +54,14 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
 import org.threeten.bp.Duration;
 
 @RunWith(JUnit4.class)
 public class OpencensusTracerTest {
-  @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule();
+  @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
 
   @Mock private Tracer internalTracer;
   @Mock private Span span;
@@ -121,7 +122,7 @@ public void testUnarySuccessExample() {
         .putAttributes(ImmutableMap.of("attempt count", AttributeValue.longAttributeValue(2)));
     verify(span).end();
 
-    Mockito.verifyNoMoreInteractions(span);
+    verifyNoMoreInteractions(span);
   }
 
   @Test
@@ -170,9 +171,42 @@ public void testRetriesExhaustedExample() {
                 .setStatus(Status.DEADLINE_EXCEEDED.withDescription("deadline exceeded"))
                 .build());
 
-    Mockito.verifyNoMoreInteractions(span);
+    verifyNoMoreInteractions(span);
   }
 
+  @Test
+  public void testCancellationExample() {
+    tracer.attemptStarted(0);
+    tracer.connectionSelected(1);
+    tracer.attemptCancelled();
+    tracer.operationCancelled();
+
+    verify(span)
+        .addAnnotation(
+            "Attempt started", ImmutableMap.of("attempt", AttributeValue.longAttributeValue(0)));
+
+    verify(span)
+        .addAnnotation(
+            "Connection selected", ImmutableMap.of("id", AttributeValue.longAttributeValue(1)));
+
+    verify(span)
+        .addAnnotation(
+            "Attempt cancelled",
+            ImmutableMap.of(
+                "attempt", AttributeValue.longAttributeValue(0)));
+
+    verify(span)
+        .putAttributes(ImmutableMap.of("attempt count", AttributeValue.longAttributeValue(1)));
+
+    verify(span)
+        .end(
+            EndSpanOptions.builder()
+                .setStatus(Status.CANCELLED.withDescription("Cancelled by caller"))
+                .build());
+    verifyNoMoreInteractions(span);
+  }
+
+
   @Test
   public void testFailureExample() {
     tracer.attemptStarted(0);
@@ -205,7 +239,7 @@ public void testFailureExample() {
             EndSpanOptions.builder()
                 .setStatus(Status.NOT_FOUND.withDescription("not found"))
                 .build());
-    Mockito.verifyNoMoreInteractions(span);
+    verifyNoMoreInteractions(span);
   }
 
   @Test

From c39e96726511ee1580d8bf4d0d2ccf9fd64e9f87 Mon Sep 17 00:00:00 2001
From: Igor Bernstein 
Date: Fri, 11 Jan 2019 16:22:38 -0500
Subject: [PATCH 07/12] format

---
 .../com/google/api/gax/tracing/OpencensusTracerTest.java  | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java
index 169300c89..e197541e9 100644
--- a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java
+++ b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java
@@ -61,7 +61,8 @@
 
 @RunWith(JUnit4.class)
 public class OpencensusTracerTest {
-  @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+  @Rule
+  public final MockitoRule mockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
 
   @Mock private Tracer internalTracer;
   @Mock private Span span;
@@ -191,9 +192,7 @@ public void testCancellationExample() {
 
     verify(span)
         .addAnnotation(
-            "Attempt cancelled",
-            ImmutableMap.of(
-                "attempt", AttributeValue.longAttributeValue(0)));
+            "Attempt cancelled", ImmutableMap.of("attempt", AttributeValue.longAttributeValue(0)));
 
     verify(span)
         .putAttributes(ImmutableMap.of("attempt count", AttributeValue.longAttributeValue(1)));
@@ -206,7 +205,6 @@ public void testCancellationExample() {
     verifyNoMoreInteractions(span);
   }
 
-
   @Test
   public void testFailureExample() {
     tracer.attemptStarted(0);

From 90a6f158810bbd64a78608a476e2b3861de4b489 Mon Sep 17 00:00:00 2001
From: Igor Bernstein 
Date: Thu, 17 Jan 2019 16:37:18 -0500
Subject: [PATCH 08/12] address feedback

---
 .../com/google/api/gax/tracing/ApiTracer.java |  2 +-
 .../api/gax/tracing/OpencensusTracer.java     | 81 ++++++++++++++-----
 .../gax/tracing/OpencensusTracerFactory.java  |  2 +
 .../tracing/OpencensusTracerFactoryTest.java  |  2 +-
 .../api/gax/tracing/OpencensusTracerTest.java |  2 +-
 5 files changed, 64 insertions(+), 25 deletions(-)

diff --git a/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java b/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java
index c3211189e..7e32d8994 100644
--- a/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java
+++ b/gax/src/main/java/com/google/api/gax/tracing/ApiTracer.java
@@ -103,7 +103,7 @@ public interface ApiTracer {
    * Adds an annotation that the attempt failed and that no further attempts will be made because
    * retry limits have been reached.
    *
-   * @param error
+   * @param error the error that caused the attempt to fail.
    */
   void attemptFailedRetriesExhausted(Throwable error);
 
diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java
index 7d606eab8..6a5b4f687 100644
--- a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java
+++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java
@@ -52,6 +52,10 @@
  * 

This implementation wraps an OpenCensus {@link Span} for every tracer and annotates that * {@link Span} with various events throughout the lifecycle of the logical operation. * + *

This class is thread compatible. It expects callers to follow grpc's threading model: there is + * only one thread that invokes the operation* and attempt* methods. Please see {@link + * com.google.api.gax.rpc.ApiStreamObserver} for more information. + * *

  *   ClientName.UnaryMethod
  *     - attributes:
@@ -64,6 +68,9 @@
  *       - Connection selected:
  *         - attributes:
  *           - id: the id of the connection in the local connection pool
+ *       - Attempt cancelled
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
  *       - Attempt failed, scheduling next attempt
  *         - attributes:
  *           - attempt: zero based sequential attempt number
@@ -77,12 +84,15 @@
  *         - attributes:
  *           - attempt: zero based sequential attempt number
  *           - status: the non-retryable status code of the failed attempt
+ *       - Attempt succeeded
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
  * 
* *
  *   ClientName.ServerStreamingMethod
  *     - attributes:
- *       - {@code attempt count}: number of attempts sent before the logical operation completed
+ *       - attempt count: number of attempts sent before the logical operation completed
  *       - status: the status code of the last attempt
  *       - total response count: number of messages received across all of the attempts
  *     - annotations:
@@ -92,6 +102,10 @@
  *       - Connection selected:
  *         - attributes:
  *           - id: the id of the connection in the local connection pool
+ *       - Attempt cancelled
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - attempt response count: number of responses received in this attempt
  *       - Attempt failed, scheduling next attempt
  *         - attributes:
  *           - attempt: zero based sequential attempt number
@@ -108,12 +122,16 @@
  *           - attempt: zero based sequential attempt number
  *           - status: the non-retryable status code of the failed attempt
  *           - attempt response count: number of responses received in this attempt
+ *       - Attempt succeeded
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - attempt response count: number of responses received in this attempt
  * 
* *
  *   ClientName.ClientStreamingMethod
  *     - attributes:
- *       - {@code attempt count}: number of attempts sent before the logical operation completed
+ *       - attempt count: number of attempts sent before the logical operation completed
  *       - status: the status code of the last attempt
  *       - total request count: number of messages sent across all of the attempts
  *     - annotations:
@@ -123,6 +141,10 @@
  *       - Connection selected:
  *         - attributes:
  *           - id: the id of the connection in the local connection pool
+ *       - Attempt cancelled
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - attempt request count: number of requests sent in this attempt
  *       - Attempt failed, scheduling next attempt
  *         - attributes:
  *           - attempt: zero based sequential attempt number
@@ -139,12 +161,16 @@
  *           - attempt: zero based sequential attempt number
  *           - status: the non-retryable status code of the failed attempt
  *           - attempt request count: number of requests sent in this attempt
+ *       - Attempt succeeded
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - attempt request count: number of requests sent in this attempt
  * 
* *
  *   ClientName.BidiStreamingMethod
  *     - attributes:
- *       - {@code attempt count}: number of attempts sent before the logical operation completed
+ *       - attempt count: number of attempts sent before the logical operation completed
  *       - status: the status code of the last attempt
  *       - total request count: number of messages sent across all of the attempts
  *       - total response count: number of messages received across all of the attempts
@@ -155,6 +181,11 @@
  *       - Connection selected:
  *         - attributes:
  *           - id: the id of the connection in the local connection pool
+ *       - Attempt cancelled
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - attempt request count: number of requests sent in this attempt
+ *           - attempt response count: number of responses received in this attempt
  *       - Attempt failed, scheduling next attempt
  *         - attributes:
  *           - attempt: zero based sequential attempt number
@@ -174,6 +205,11 @@
  *           - status: the non-retryable status code of the failed attempt
  *           - attempt request count: number of requests sent in this attempt
  *           - attempt response count: number of responses received in this attempt
+ *       - Attempt succeeded
+ *         - attributes:
+ *           - attempt: zero based sequential attempt number
+ *           - attempt request count: number of requests sent in this attempt
+ *           - attempt response count: number of responses received in this attempt
  * 
*/ @BetaApi("Surface for tracing is not yet stable") @@ -182,10 +218,10 @@ public class OpencensusTracer implements ApiTracer { private final Span span; private long currentAttemptId; - private long attemptRequests = 0; - private long attemptResponses = 0; - private long totalAttemptRequests = 0; - private long totalAttemptResponses = 0; + private long attemptSentMessages = 0; + private long attemptReceivedMessages = 0; + private long totalSentMessages = 0; + private long totalReceivedMessages = 0; OpencensusTracer(@Nonnull Tracer tracer, @Nonnull Span span) { this.tracer = Preconditions.checkNotNull(tracer, "tracer can't be null"); @@ -244,8 +280,8 @@ public void connectionSelected(int id) { @Override public void attemptStarted(int attemptNumber) { currentAttemptId = attemptNumber; - attemptRequests = 0; - attemptResponses = 0; + attemptSentMessages = 0; + attemptReceivedMessages = 0; HashMap attributes = Maps.newHashMap(); populateAttemptNumber(attributes); @@ -300,15 +336,15 @@ public void attemptPermanentFailure(Throwable error) { /** {@inheritDoc} */ @Override public void responseReceived() { - attemptResponses++; - totalAttemptResponses++; + attemptReceivedMessages++; + totalReceivedMessages++; } /** {@inheritDoc} */ @Override public void requestSent() { - attemptRequests++; - totalAttemptRequests++; + attemptSentMessages++; + totalSentMessages++; } /** {@inheritDoc} */ @@ -323,13 +359,12 @@ private Map baseOperationAttributes() { attributes.put("attempt count", AttributeValue.longAttributeValue(currentAttemptId + 1)); - if (totalAttemptRequests > 0) { - attributes.put( - "total request count", AttributeValue.longAttributeValue(totalAttemptRequests)); + if (totalSentMessages > 0) { + attributes.put("total request count", AttributeValue.longAttributeValue(totalSentMessages)); } - if (totalAttemptResponses > 0) { + if (totalReceivedMessages > 0) { attributes.put( - "total response count", AttributeValue.longAttributeValue(totalAttemptResponses)); + "total response count", AttributeValue.longAttributeValue(totalReceivedMessages)); } return attributes; @@ -340,11 +375,13 @@ private Map baseAttemptAttributes() { populateAttemptNumber(attributes); - if (attemptRequests > 0) { - attributes.put("attempt request count", AttributeValue.longAttributeValue(attemptRequests)); + if (attemptSentMessages > 0) { + attributes.put( + "attempt request count", AttributeValue.longAttributeValue(attemptSentMessages)); } - if (attemptResponses > 0) { - attributes.put("attempt response count", AttributeValue.longAttributeValue(attemptResponses)); + if (attemptReceivedMessages > 0) { + attributes.put( + "attempt response count", AttributeValue.longAttributeValue(attemptReceivedMessages)); } return attributes; diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java index 59f7f4657..34e5b6db7 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java @@ -43,6 +43,8 @@ * *

This class wraps the {@link Tracer} provided by Opencensus in {@code Tracing.getTracer()}. It * will be used to create new spans and wrap them in {@link OpencensusTracer} defined in gax. + * + *

This class is thread safe. */ @InternalApi("For google-cloud-java client use only") public final class OpencensusTracerFactory implements ApiTracerFactory { diff --git a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerFactoryTest.java b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerFactoryTest.java index 2d462d86f..24a3a1c36 100644 --- a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerFactoryTest.java +++ b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google LLC + * Copyright 2019 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java index e197541e9..66526b549 100644 --- a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java +++ b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google LLC + * Copyright 2019 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are From ef22030d305611c39488cb3313317ea4ad9cf145 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Wed, 23 Jan 2019 16:36:44 -0500 Subject: [PATCH 09/12] update docs --- .../api/gax/tracing/OpencensusTracer.java | 260 +++++++----------- .../gax/tracing/OpencensusTracerFactory.java | 2 +- 2 files changed, 105 insertions(+), 157 deletions(-) diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java index 6a5b4f687..c1664afbd 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java @@ -52,165 +52,113 @@ *

This implementation wraps an OpenCensus {@link Span} for every tracer and annotates that * {@link Span} with various events throughout the lifecycle of the logical operation. * - *

This class is thread compatible. It expects callers to follow grpc's threading model: there is - * only one thread that invokes the operation* and attempt* methods. Please see {@link - * com.google.api.gax.rpc.ApiStreamObserver} for more information. + *

Each span will be named {@code ClientName.MethodName} and will have the following attributes: + * + *

+ *
{@code attempt count} + *
The Number of attempts sent before the logical operation completed + *
{@code status} + *
The status code of the last attempt + *
{@code total response count} + *
The number of messages received across all of the attempts. This will only be set for + * server streaming and bidi RPCs. + *
{@code total request count} + *
The number of messages sent across all of the attempts. This will only be set for client + * streaming and bidi RPCs. + *
{@code batch count} + *
For batch requests, the number of elements in the request. + *
{@code batch size} + *
For batch requests, the byte size of the request. + *
+ * + *

The spans will contain the following annotations: + * + *

    + *
  • {@code Connection selected} with the following attributes: + *
    + *
    {@code id} + *
    The id of the connection in the local connection pool + *
    + * + *
  • {@code Attempt started} with the following attributes: + *
    + *
    {@code attempt} + *
    Zero based sequential attempt number + *
    * - *
    - *   ClientName.UnaryMethod
    - *     - attributes:
    - *       - attempt count: number of attempts sent before the logical operation completed
    - *       - status: the status code of the last attempt
    - *     - annotations:
    - *       - Attempt started
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *       - Connection selected:
    - *         - attributes:
    - *           - id: the id of the connection in the local connection pool
    - *       - Attempt cancelled
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *       - Attempt failed, scheduling next attempt
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the status code of the failed attempt
    - *           - delay: number of milliseconds to wait before trying again
    - *       - Attempts exhausted
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the status code of the failed attempt
    - *       - Attempt failed, error not retryable
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the non-retryable status code of the failed attempt
    - *       - Attempt succeeded
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - * 
    + *
  • {@code Attempt cancelled} with the following attributes: + *
    + *
    {@code attempt} + *
    Zero based sequential attempt number + *
    {@code attempt request count} + *
    The number of requests sent in this attempt. This will only be set for client + * streaming and bidi RPCs. + *
    {@code attempt response count} + *
    The number of responses received in this attempt. This will only be set for server + * streaming and bidi RPCs. + *
    * - *
    - *   ClientName.ServerStreamingMethod
    - *     - attributes:
    - *       - attempt count: number of attempts sent before the logical operation completed
    - *       - status: the status code of the last attempt
    - *       - total response count: number of messages received across all of the attempts
    - *     - annotations:
    - *       - Attempt started
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *       - Connection selected:
    - *         - attributes:
    - *           - id: the id of the connection in the local connection pool
    - *       - Attempt cancelled
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - attempt response count: number of responses received in this attempt
    - *       - Attempt failed, scheduling next attempt
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the status code of the failed attempt
    - *           - delay: number of milliseconds to wait before trying again
    - *           - attempt response count: number of responses received in this attempt
    - *       - Attempts exhausted
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the status code of the failed attempt
    - *           - attempt response count: number of responses received in this attempt
    - *       - Attempt failed, error not retryable
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the non-retryable status code of the failed attempt
    - *           - attempt response count: number of responses received in this attempt
    - *       - Attempt succeeded
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - attempt response count: number of responses received in this attempt
    - * 
    + *
  • {@code Attempt failed, scheduling next attempt} with the following attributes: + *
    + *
    {@code attempt} + *
    Zero based sequential attempt number + *
    {@code status} + *
    The status code of the failed attempt + *
    {@code delay} + *
    The number of milliseconds to wait before trying again + *
    {@code attempt request count} + *
    The number of requests sent in this attempt. This will only be set for client + * streaming and bidi RPCs. + *
    {@code attempt response count} + *
    The number of responses received in this attempt. This will only be set for server + * streaming and bidi RPCs. + *
    * - *
    - *   ClientName.ClientStreamingMethod
    - *     - attributes:
    - *       - attempt count: number of attempts sent before the logical operation completed
    - *       - status: the status code of the last attempt
    - *       - total request count: number of messages sent across all of the attempts
    - *     - annotations:
    - *       - Attempt started
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *       - Connection selected:
    - *         - attributes:
    - *           - id: the id of the connection in the local connection pool
    - *       - Attempt cancelled
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - attempt request count: number of requests sent in this attempt
    - *       - Attempt failed, scheduling next attempt
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the status code of the failed attempt
    - *           - delay: number of milliseconds to wait before trying again
    - *           - attempt request count: number of requests sent in this attempt
    - *       - Attempts exhausted
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the status code of the failed attempt
    - *           - attempt request count: number of requests sent in this attempt
    - *       - Attempt failed, error not retryable
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the non-retryable status code of the failed attempt
    - *           - attempt request count: number of requests sent in this attempt
    - *       - Attempt succeeded
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - attempt request count: number of requests sent in this attempt
    - * 
    + *
  • {@code Attempts exhausted} with the following attributes: + *
    + *
    {@code attempt} + *
    Zero based sequential attempt number + *
    {@code status} + *
    The status code of the failed attempt + *
    {@code attempt request count} + *
    The number of requests sent in this attempt. This will only be set for client + * streaming and bidi RPCs. + *
    {@code attempt response count} + *
    The number of responses received in this attempt. This will only be set for server + * streaming and bidi RPCs. + *
    * - *
    - *   ClientName.BidiStreamingMethod
    - *     - attributes:
    - *       - attempt count: number of attempts sent before the logical operation completed
    - *       - status: the status code of the last attempt
    - *       - total request count: number of messages sent across all of the attempts
    - *       - total response count: number of messages received across all of the attempts
    - *     - annotations:
    - *       - Attempt started
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *       - Connection selected:
    - *         - attributes:
    - *           - id: the id of the connection in the local connection pool
    - *       - Attempt cancelled
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - attempt request count: number of requests sent in this attempt
    - *           - attempt response count: number of responses received in this attempt
    - *       - Attempt failed, scheduling next attempt
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the status code of the failed attempt
    - *           - delay: number of milliseconds to wait before trying again
    - *           - attempt request count: number of requests sent in this attempt
    - *           - attempt response count: number of responses received in this attempt
    - *       - Attempts exhausted
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the status code of the failed attempt
    - *           - attempt request count: number of requests sent in this attempt
    - *           - attempt response count: number of responses received in this attempt
    - *       - Attempt failed, error not retryable
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - status: the non-retryable status code of the failed attempt
    - *           - attempt request count: number of requests sent in this attempt
    - *           - attempt response count: number of responses received in this attempt
    - *       - Attempt succeeded
    - *         - attributes:
    - *           - attempt: zero based sequential attempt number
    - *           - attempt request count: number of requests sent in this attempt
    - *           - attempt response count: number of responses received in this attempt
    - * 
    + *
  • {@code Attempt failed, error not retryable} with the following attributes: + *
    + *
    {@code attempt} + *
    Zero based sequential attempt number + *
    {@code status} + *
    The status code of the failed attempt + *
    {@code attempt request count} + *
    The number of requests sent in this attempt. This will only be set for client + * streaming and bidi RPCs. + *
    {@code attempt response count} + *
    The number of responses received in this attempt. This will only be set for server + * streaming and bidi RPCs. + *
    + * + *
  • {@code Attempt succeeded} with the following attributes: + *
    + *
    {@code attempt} + *
    Zero based sequential attempt number + *
    {@code attempt request count} + *
    The number of requests sent in this attempt. This will only be set for client + * streaming and bidi RPCs. + *
    {@code attempt response count} + *
    The number of responses received in this attempt. This will only be set for server + * streaming and bidi RPCs. + *
    + * + *
+ * + *

This class is thread compatible. It expects callers to follow grpc's threading model: there is + * only one thread that invokes the operation* and attempt* methods. Please see {@link + * com.google.api.gax.rpc.ApiStreamObserver} for more information. */ @BetaApi("Surface for tracing is not yet stable") public class OpencensusTracer implements ApiTracer { @@ -351,7 +299,7 @@ public void requestSent() { @Override public void batchRequestSent(long elementCount, long requestSize) { span.putAttribute("batch count", AttributeValue.longAttributeValue(elementCount)); - span.putAttribute("request size", AttributeValue.longAttributeValue(requestSize)); + span.putAttribute("batch size", AttributeValue.longAttributeValue(requestSize)); } private Map baseOperationAttributes() { diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java index 34e5b6db7..55eab5b8d 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java @@ -86,7 +86,7 @@ public OpencensusTracerFactory(@Nullable String clientNameOverride) { this.clientNameOverride = clientNameOverride; } - /** {@inheritDoc */ + /** {@inheritDoc } */ @Override public ApiTracer newTracer(SpanName spanName) { if (clientNameOverride != null) { From a7c8025986834b949f1c3e1b80cfcedf54263ec9 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Wed, 23 Jan 2019 16:38:37 -0500 Subject: [PATCH 10/12] typo --- .../main/java/com/google/api/gax/tracing/OpencensusTracer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java index c1664afbd..c45050090 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java @@ -198,6 +198,7 @@ public void operationSucceeded() { span.end(); } + /** {@inheritDoc} */ @Override public void operationCancelled() { Map attributes = baseOperationAttributes(); From 89a4777eee10e66e0f90add14ecd08663170f517 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Wed, 23 Jan 2019 18:15:29 -0500 Subject: [PATCH 11/12] fix test --- .../java/com/google/api/gax/tracing/OpencensusTracerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java index 66526b549..f3b24ca11 100644 --- a/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java +++ b/gax/src/test/java/com/google/api/gax/tracing/OpencensusTracerTest.java @@ -135,7 +135,7 @@ public void testBatchExample() { tracer.operationSucceeded(); verify(span).putAttribute("batch count", AttributeValue.longAttributeValue(100)); - verify(span).putAttribute("request size", AttributeValue.longAttributeValue(1000)); + verify(span).putAttribute("batch size", AttributeValue.longAttributeValue(1000)); } @Test From f96e981e2649f21a8ac7d1035a8ae2496da04b53 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Mon, 28 Jan 2019 16:12:18 -0500 Subject: [PATCH 12/12] address feedback --- .../api/gax/tracing/OpencensusTracer.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java index c45050090..b58746dc6 100644 --- a/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java @@ -29,7 +29,6 @@ */ package com.google.api.gax.tracing; -import com.google.api.client.util.Maps; import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.api.gax.rpc.ApiException; @@ -43,6 +42,7 @@ import io.opencensus.trace.Tracer; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import javax.annotation.Nonnull; import org.threeten.bp.Duration; @@ -165,10 +165,10 @@ public class OpencensusTracer implements ApiTracer { private final Tracer tracer; private final Span span; - private long currentAttemptId; - private long attemptSentMessages = 0; + private volatile long currentAttemptId; + private AtomicLong attemptSentMessages = new AtomicLong(0); private long attemptReceivedMessages = 0; - private long totalSentMessages = 0; + private AtomicLong totalSentMessages = new AtomicLong(0); private long totalReceivedMessages = 0; OpencensusTracer(@Nonnull Tracer tracer, @Nonnull Span span) { @@ -229,10 +229,10 @@ public void connectionSelected(int id) { @Override public void attemptStarted(int attemptNumber) { currentAttemptId = attemptNumber; - attemptSentMessages = 0; + attemptSentMessages.set(0); attemptReceivedMessages = 0; - HashMap attributes = Maps.newHashMap(); + HashMap attributes = new HashMap<>(); populateAttemptNumber(attributes); span.addAnnotation("Attempt started", attributes); @@ -292,8 +292,8 @@ public void responseReceived() { /** {@inheritDoc} */ @Override public void requestSent() { - attemptSentMessages++; - totalSentMessages++; + attemptSentMessages.incrementAndGet(); + totalSentMessages.incrementAndGet(); } /** {@inheritDoc} */ @@ -304,12 +304,14 @@ public void batchRequestSent(long elementCount, long requestSize) { } private Map baseOperationAttributes() { - HashMap attributes = Maps.newHashMap(); + HashMap attributes = new HashMap<>(); attributes.put("attempt count", AttributeValue.longAttributeValue(currentAttemptId + 1)); - if (totalSentMessages > 0) { - attributes.put("total request count", AttributeValue.longAttributeValue(totalSentMessages)); + long localTotalSentMessages = totalSentMessages.get(); + if (localTotalSentMessages > 0) { + attributes.put( + "total request count", AttributeValue.longAttributeValue(localTotalSentMessages)); } if (totalReceivedMessages > 0) { attributes.put( @@ -320,13 +322,14 @@ private Map baseOperationAttributes() { } private Map baseAttemptAttributes() { - HashMap attributes = Maps.newHashMap(); + HashMap attributes = new HashMap<>(); populateAttemptNumber(attributes); - if (attemptSentMessages > 0) { + long localAttemptSentMessages = attemptSentMessages.get(); + if (localAttemptSentMessages > 0) { attributes.put( - "attempt request count", AttributeValue.longAttributeValue(attemptSentMessages)); + "attempt request count", AttributeValue.longAttributeValue(localAttemptSentMessages)); } if (attemptReceivedMessages > 0) { attributes.put(