From f69bd88c4141f8a4b38f84534f5b5eaf8b2c7743 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Mon, 15 Oct 2018 17:25:49 -0400 Subject: [PATCH 1/5] add opencensus dependency --- build.gradle | 3 +++ gax/build.gradle | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d98423b5e..0be464928 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ ext { grpcVersion = '1.15.0' commonProtosVersion = '1.12.0' authVersion = '0.11.0' + opencensusVersion = '0.15.0' // Project names not used for release nonReleaseProjects = ['benchmark'] // Project names not using the default publication configuration @@ -109,6 +110,7 @@ subprojects { ext { grpcVersion = grpcVersion commonProtosVersion = commonProtosVersion + opencensusVersion = opencensusVersion // Shortcuts for libraries we are using libraries = [ @@ -126,6 +128,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 78f344f94..271c94ccc 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 From 2ad8a038a82e3af8b151cbe64173eb79bb546b9d Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Mon, 15 Oct 2018 17:29:27 -0400 Subject: [PATCH 2/5] Add tracer --- .../google/api/gax/tracing/NoopTracer.java | 81 ++++++++ .../api/gax/tracing/NoopTracerFactory.java | 51 +++++ .../api/gax/tracing/OpencensusTracer.java | 192 ++++++++++++++++++ .../gax/tracing/OpencensusTracerFactory.java | 75 +++++++ .../com/google/api/gax/tracing/SpanName.java | 51 +++++ .../com/google/api/gax/tracing/Tracer.java | 88 ++++++++ .../google/api/gax/tracing/TracerFactory.java | 37 ++++ 7 files changed, 575 insertions(+) create mode 100644 gax/src/main/java/com/google/api/gax/tracing/NoopTracer.java create mode 100644 gax/src/main/java/com/google/api/gax/tracing/NoopTracerFactory.java 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 create mode 100644 gax/src/main/java/com/google/api/gax/tracing/SpanName.java create mode 100644 gax/src/main/java/com/google/api/gax/tracing/Tracer.java create mode 100644 gax/src/main/java/com/google/api/gax/tracing/TracerFactory.java diff --git a/gax/src/main/java/com/google/api/gax/tracing/NoopTracer.java b/gax/src/main/java/com/google/api/gax/tracing/NoopTracer.java new file mode 100644 index 000000000..47d6c6e93 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/NoopTracer.java @@ -0,0 +1,81 @@ +/* + * 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 org.threeten.bp.Duration; + +public class NoopTracer implements Tracer { + public static Tracer create() { + return new NoopTracer(); + } + + @Override + public Scope inScope() { + return new NoopScope(); + } + + @Override + public void operationSucceeded() {} + + @Override + public void operationFailed(Throwable error) {} + + @Override + public void connectionSelected(int id) {} + + @Override + public void startAttempt() {} + + @Override + public void attemptSucceeded() {} + + @Override + public void retryableFailure(Throwable error, Duration delay) {} + + @Override + public void retriesExhausted() {} + + @Override + public void permanentFailure(Throwable error) {} + + @Override + public void receivedResponse() {} + + @Override + public void sentRequest() {} + + @Override + public void sentBatchRequest(long elementCount, long requestSize) {} + + private static class NoopScope implements Scope { + @Override + public void close() {} + } +} diff --git a/gax/src/main/java/com/google/api/gax/tracing/NoopTracerFactory.java b/gax/src/main/java/com/google/api/gax/tracing/NoopTracerFactory.java new file mode 100644 index 000000000..21f19ecb8 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/NoopTracerFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 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.api.gax.tracing.Tracer.Type; + +@InternalApi +public final class NoopTracerFactory implements TracerFactory { + @Override + public Tracer newTracer(Type type, SpanName spanName) { + return NoopTracer.create(); + } + + @Override + public int hashCode() { + return 1; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof NoopTracerFactory; + } +} 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..595beb72d --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracer.java @@ -0,0 +1,192 @@ +/* + * Copyright 2017 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.gax.rpc.ApiException; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +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 java.util.HashMap; +import java.util.Map; +import org.threeten.bp.Duration; + +public class OpencensusTracer implements Tracer { + private final io.opencensus.trace.Tracer tracer; + private final Span span; + private Type type; + + private int attempts = 0; + private long attemptRequests = 0; + private long attemptResponses = 0; + private long totalAttemptRequests = 0; + private long totalAttemptResponses = 0; + + OpencensusTracer(io.opencensus.trace.Tracer tracer, Span span, Type type) { + this.tracer = tracer; + this.span = span; + this.type = type; + } + + @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() { + span.putAttributes(baseAttemptAttributes()); + span.end(); + } + + @Override + public void operationFailed(Throwable error) { + span.putAttributes(baseAttemptAttributes()); + span.end(EndSpanOptions.builder().setStatus(convertErrorToStatus(error)).build()); + } + + private Map baseOperationAttributes() { + HashMap attributes = Maps.newHashMap(); + + if (type.getCountRequests()) { + attributes.put( + "total request count", AttributeValue.longAttributeValue(totalAttemptRequests)); + } + if (type.getCountResponses()) { + attributes.put( + "total response count", AttributeValue.longAttributeValue(totalAttemptResponses)); + } + return attributes; + } + + @Override + public void connectionSelected(int id) { + span.addAnnotation( + "Connection selected", ImmutableMap.of("id", AttributeValue.longAttributeValue(id))); + } + + @Override + public void startAttempt() { + HashMap attributes = Maps.newHashMap(); + attributes.put("attempt", AttributeValue.longAttributeValue(attempts)); + + attempts++; + attemptRequests = attemptResponses = 0; + + span.addAnnotation( + "Attempt started", ImmutableMap.of("attempt", AttributeValue.longAttributeValue(attempts))); + } + + @Override + public void attemptSucceeded() { + Map attributes = baseAttemptAttributes(); + span.addAnnotation("Attempt succeeded", attributes); + } + + @Override + public void retryableFailure(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 retriesExhausted() { + Map attributes = baseAttemptAttributes(); + span.addAnnotation("Attempts exhausted", attributes); + } + + @Override + public void permanentFailure(Throwable error) { + Map attributes = baseAttemptAttributes(); + span.addAnnotation("Attempt failed, error not retryable ", attributes); + } + + @Override + public void receivedResponse() { + attemptResponses++; + totalAttemptResponses++; + } + + private Map baseAttemptAttributes() { + HashMap attributes = Maps.newHashMap(); + attributes.put("attempt", AttributeValue.longAttributeValue(attempts)); + + if (type.getCountRequests()) { + attributes.put("attempt request count", AttributeValue.longAttributeValue(attemptRequests)); + } + if (type.getCountResponses()) { + attributes.put("attempt response count", AttributeValue.longAttributeValue(attemptResponses)); + } + + return attributes; + } + + @Override + public void sentRequest() { + attemptRequests++; + totalAttemptRequests++; + } + + @Override + public void sentBatchRequest(long elementCount, long requestSize) { + span.putAttribute("batch count", AttributeValue.longAttributeValue(elementCount)); + span.putAttribute("request size", AttributeValue.longAttributeValue(requestSize)); + } + + 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..0cc52b6d4 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/OpencensusTracerFactory.java @@ -0,0 +1,75 @@ +/* + * Copyright 2017 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.api.gax.tracing.Tracer.Type; +import com.google.common.base.Objects; +import io.opencensus.trace.Span; +import io.opencensus.trace.Tracing; +import javax.annotation.Nullable; + +@InternalApi +public final class OpencensusTracerFactory implements TracerFactory { + @Nullable private final String clientNameOverride; + + public OpencensusTracerFactory() { + this(null); + } + + public OpencensusTracerFactory(@Nullable String clientNameOverride) { + this.clientNameOverride = clientNameOverride; + } + + @Override + public Tracer newTracer(Type type, SpanName spanName) { + io.opencensus.trace.Tracer tracer = Tracing.getTracer(); + Span span = tracer.spanBuilder(spanName.toString()).setRecordEvents(true).startSpan(); + + return new OpencensusTracer(tracer, span, type); + } + + @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(clientNameOverride, that.clientNameOverride); + } + + @Override + public int hashCode() { + return Objects.hashCode(clientNameOverride); + } +} 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 new file mode 100644 index 000000000..b38cc45a3 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/SpanName.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 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.auto.value.AutoValue; + +@AutoValue +public abstract class SpanName { + public static SpanName of(String clientName, String methodName) { + return new AutoValue_SpanName(clientName, methodName); + } + + public abstract String getClientName(); + + public abstract String getMethodName(); + + public SpanName withClientName(String clientName) { + return of(clientName, getMethodName()); + } + + public SpanName withMethodName(String methodName) { + return of(getClientName(), methodName); + } +} diff --git a/gax/src/main/java/com/google/api/gax/tracing/Tracer.java b/gax/src/main/java/com/google/api/gax/tracing/Tracer.java new file mode 100644 index 000000000..2f166a445 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/Tracer.java @@ -0,0 +1,88 @@ +/* + * 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 org.threeten.bp.Duration; + +public interface Tracer { + Scope inScope(); + + void operationSucceeded(); + + void operationFailed(Throwable error); + + void connectionSelected(int id); + + void startAttempt(); + + void attemptSucceeded(); + + void retryableFailure(Throwable error, Duration delay); + + void retriesExhausted(); + + void permanentFailure(Throwable error); + + void receivedResponse(); + + void sentRequest(); + + void sentBatchRequest(long elementCount, long requestSize); + + interface Scope extends AutoCloseable { + @Override + void close(); + } + + enum Type { + Unary(false, false), + ServerStreaming(false, true), + ClientStreaming(true, false), + Bidi(true, true), + LongRunning(false, false), + Batched(false, false); + + private boolean countRequests; + private boolean countResponses; + + Type(boolean countRequests, boolean countResponses) { + this.countRequests = countRequests; + this.countResponses = countResponses; + } + + boolean getCountRequests() { + return countRequests; + } + + boolean getCountResponses() { + return countResponses; + } + } +} diff --git a/gax/src/main/java/com/google/api/gax/tracing/TracerFactory.java b/gax/src/main/java/com/google/api/gax/tracing/TracerFactory.java new file mode 100644 index 000000000..16eb29166 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/TracerFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 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.BetaApi; + +@BetaApi +public interface TracerFactory { + Tracer newTracer(Tracer.Type type, SpanName spanName); +} From 8785a77dd97e80ec1b7b44975336f76502945a88 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Mon, 15 Oct 2018 17:31:29 -0400 Subject: [PATCH 3/5] Plumb tracing through the Client and Call Contexts ClientContext gets a TracerFactory, which (for the time being) is set to a dummy factory. To enable tracing OpenCensusTracerFactory can be used. In the future, OpenCensusTracerFactory will become the default. TracerFactory will be used by outer Callables to create new Tracers ApiCallContext will be used to carry the Tracers through the callable chains to annotate the spans represented by the Tracers --- .../google/api/gax/grpc/GrpcCallContext.java | 31 +++++++++++ .../api/gax/httpjson/HttpJsonCallContext.java | 47 ++++++++++++++--- .../api/gax/retrying/RetryingContext.java | 7 ++- .../google/api/gax/rpc/ApiCallContext.java | 9 ++++ .../com/google/api/gax/rpc/ClientContext.java | 13 ++++- .../com/google/api/gax/rpc/StubSettings.java | 28 +++++++++- .../api/gax/rpc/testing/FakeCallContext.java | 52 +++++++++++++++---- 7 files changed, 167 insertions(+), 20 deletions(-) diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java index aaeac38cf..b3d918943 100644 --- a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java @@ -34,11 +34,14 @@ import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.TransportChannel; import com.google.api.gax.rpc.internal.Headers; +import com.google.api.gax.tracing.NoopTracer; +import com.google.api.gax.tracing.Tracer; import com.google.auth.Credentials; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import io.grpc.CallCredentials; import io.grpc.CallOptions; +import io.grpc.CallOptions.Key; import io.grpc.Channel; import io.grpc.Deadline; import io.grpc.Metadata; @@ -46,6 +49,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.threeten.bp.Duration; @@ -60,6 +64,8 @@ @BetaApi("Reference ApiCallContext instead - this class is likely to experience breaking changes") @InternalExtensionOnly public final class GrpcCallContext implements ApiCallContext { + private static final CallOptions.Key TRACER_KEY = Key.create("gax.tracer"); + private final Channel channel; private final CallOptions callOptions; @Nullable private final Duration timeout; @@ -254,6 +260,11 @@ public ApiCallContext merge(ApiCallContext inputCallContext) { newCallCredentials = this.callOptions.getCredentials(); } + Tracer newTracer = grpcCallContext.callOptions.getOption(TRACER_KEY); + if (newTracer == null) { + newTracer = this.callOptions.getOption(TRACER_KEY); + } + Duration newTimeout = grpcCallContext.timeout; if (newTimeout == null) { newTimeout = this.timeout; @@ -283,6 +294,10 @@ public ApiCallContext merge(ApiCallContext inputCallContext) { .withCallCredentials(newCallCredentials) .withDeadline(newDeadline); + if (newTracer != null) { + newCallOptions = newCallOptions.withOption(TRACER_KEY, newTracer); + } + return new GrpcCallContext( newChannel, newCallOptions, @@ -370,6 +385,22 @@ public GrpcCallContext withRequestParamsDynamicHeaderOption(String requestParams return withCallOptions(newCallOptions); } + @Nonnull + @Override + public Tracer getTracer() { + Tracer tracer = callOptions.getOption(TRACER_KEY); + if (tracer == null) { + tracer = NoopTracer.create(); + } + return tracer; + } + + @Override + public ApiCallContext withTracer(@Nonnull Tracer tracer) { + Preconditions.checkNotNull(tracer); + return withCallOptions(callOptions.withOption(TRACER_KEY, tracer)); + } + @Override public int hashCode() { return Objects.hash( diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java index 6fa64d6b8..03208d4b4 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java @@ -34,6 +34,8 @@ import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.TransportChannel; import com.google.api.gax.rpc.internal.Headers; +import com.google.api.gax.tracing.NoopTracer; +import com.google.api.gax.tracing.Tracer; import com.google.auth.Credentials; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; @@ -61,10 +63,12 @@ public final class HttpJsonCallContext implements ApiCallContext { private final Instant deadline; private final Credentials credentials; private final ImmutableMap> extraHeaders; + private final Tracer tracer; /** Returns an empty instance. */ public static HttpJsonCallContext createDefault() { - return new HttpJsonCallContext(null, null, null, null, ImmutableMap.>of()); + return new HttpJsonCallContext( + null, null, null, null, ImmutableMap.>of(), null); } private HttpJsonCallContext( @@ -72,12 +76,14 @@ private HttpJsonCallContext( Duration timeout, Instant deadline, Credentials credentials, - ImmutableMap> extraHeaders) { + ImmutableMap> extraHeaders, + Tracer tracer) { this.channel = channel; this.timeout = timeout; this.deadline = deadline; this.credentials = credentials; this.extraHeaders = extraHeaders; + this.tracer = tracer; } /** @@ -137,14 +143,19 @@ public HttpJsonCallContext merge(ApiCallContext inputCallContext) { ImmutableMap> newExtraHeaders = Headers.mergeHeaders(extraHeaders, httpJsonCallContext.extraHeaders); + Tracer newTracer = httpJsonCallContext.tracer; + if (newTracer == null) { + newTracer = this.tracer; + } + return new HttpJsonCallContext( - newChannel, newTimeout, newDeadline, newCredentials, newExtraHeaders); + newChannel, newTimeout, newDeadline, newCredentials, newExtraHeaders, newTracer); } @Override public HttpJsonCallContext withCredentials(Credentials newCredentials) { return new HttpJsonCallContext( - this.channel, this.timeout, this.deadline, newCredentials, this.extraHeaders); + this.channel, this.timeout, this.deadline, newCredentials, this.extraHeaders, this.tracer); } @Override @@ -171,7 +182,7 @@ public HttpJsonCallContext withTimeout(Duration timeout) { } return new HttpJsonCallContext( - this.channel, timeout, this.deadline, this.credentials, this.extraHeaders); + this.channel, timeout, this.deadline, this.credentials, this.extraHeaders, tracer); } @Nullable @@ -208,7 +219,8 @@ public ApiCallContext withExtraHeaders(Map> extraHeaders) { Preconditions.checkNotNull(extraHeaders); ImmutableMap> newExtraHeaders = Headers.mergeHeaders(this.extraHeaders, extraHeaders); - return new HttpJsonCallContext(channel, timeout, deadline, credentials, newExtraHeaders); + return new HttpJsonCallContext( + channel, timeout, deadline, credentials, newExtraHeaders, tracer); } @BetaApi("The surface for extra headers is not stable yet and may change in the future.") @@ -230,11 +242,30 @@ public Credentials getCredentials() { } public HttpJsonCallContext withChannel(HttpJsonChannel newChannel) { - return new HttpJsonCallContext(newChannel, timeout, deadline, credentials, extraHeaders); + return new HttpJsonCallContext( + newChannel, timeout, deadline, credentials, extraHeaders, tracer); } public HttpJsonCallContext withDeadline(Instant newDeadline) { - return new HttpJsonCallContext(channel, timeout, newDeadline, credentials, extraHeaders); + return new HttpJsonCallContext( + channel, timeout, newDeadline, credentials, extraHeaders, tracer); + } + + @Nonnull + @Override + public Tracer getTracer() { + if (tracer == null) { + return NoopTracer.create(); + } + return tracer; + } + + @Override + public ApiCallContext withTracer(@Nonnull Tracer newTracer) { + Preconditions.checkNotNull(newTracer); + + return new HttpJsonCallContext( + channel, timeout, deadline, credentials, extraHeaders, newTracer); } @Override diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java b/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java index 335d14611..5c96f1e5b 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java @@ -30,6 +30,8 @@ package com.google.api.gax.retrying; import com.google.api.core.BetaApi; +import com.google.api.gax.tracing.Tracer; +import javax.annotation.Nonnull; /** * Context for a retryable operation. @@ -37,4 +39,7 @@ *

It provides state to individual {@link RetryingFuture}s via the {@link RetryingExecutor}. */ @BetaApi("The surface for passing per operation state is not yet stable") -public interface RetryingContext {} +public interface RetryingContext { + @Nonnull + public abstract Tracer getTracer(); +} diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java index 658453ef7..f2cb16cf8 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java @@ -32,9 +32,11 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalExtensionOnly; import com.google.api.gax.retrying.RetryingContext; +import com.google.api.gax.tracing.Tracer; import com.google.auth.Credentials; import java.util.List; import java.util.Map; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.threeten.bp.Duration; @@ -130,6 +132,13 @@ public interface ApiCallContext extends RetryingContext { @Nullable Duration getStreamIdleTimeout(); + @BetaApi("The surface for tracing is not stable yet and may change in the future") + @Nonnull + Tracer getTracer(); + + @BetaApi("The surface for tracing is not stable yet and may change in the future") + ApiCallContext withTracer(@Nonnull Tracer tracer); + /** If inputContext is not null, returns it; if it is null, returns the present instance. */ ApiCallContext nullToSelf(ApiCallContext inputContext); diff --git a/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index 761f3baee..4650fa2f9 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -35,6 +35,8 @@ import com.google.api.gax.core.BackgroundResource; import com.google.api.gax.core.ExecutorAsBackgroundResource; import com.google.api.gax.core.ExecutorProvider; +import com.google.api.gax.tracing.NoopTracerFactory; +import com.google.api.gax.tracing.TracerFactory; import com.google.auth.Credentials; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; @@ -93,6 +95,9 @@ public abstract class ClientContext { @Nullable public abstract String getEndpoint(); + @BetaApi("The surface for tracing is not stable yet and may change in the future.") + public abstract TracerFactory getTracerFactory(); + public static Builder newBuilder() { return new AutoValue_ClientContext.Builder() .setBackgroundResources(Collections.emptyList()) @@ -101,7 +106,9 @@ public static Builder newBuilder() { .setInternalHeaders(Collections.emptyMap()) .setClock(NanoClock.getDefaultClock()) .setStreamWatchdog(null) - .setStreamWatchdogCheckInterval(Duration.ZERO); + .setStreamWatchdogCheckInterval(Duration.ZERO) + // TODO(igorbernstein2): switch this to OpencensusTracingFactory once everything is ready + .setTracerFactory(new NoopTracerFactory()); } public abstract Builder toBuilder(); @@ -186,6 +193,7 @@ public static ClientContext create(StubSettings settings) throws IOException { .setEndpoint(settings.getEndpoint()) .setStreamWatchdog(watchdog) .setStreamWatchdogCheckInterval(settings.getStreamWatchdogCheckInterval()) + .setTracerFactory(settings.getTracerFactory()) .build(); } @@ -218,6 +226,9 @@ public abstract static class Builder { @BetaApi("The surface for streaming is not stable yet and may change in the future.") public abstract Builder setStreamWatchdogCheckInterval(Duration duration); + @BetaApi("The surface for tracing is not stable yet and may change in the future.") + public abstract Builder setTracerFactory(TracerFactory tracerFactory); + public abstract ClientContext build(); } } diff --git a/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java b/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java index b0afdba88..e86e5bedd 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java +++ b/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java @@ -39,6 +39,8 @@ import com.google.api.gax.core.FixedExecutorProvider; import com.google.api.gax.core.InstantiatingExecutorProvider; import com.google.api.gax.core.NoCredentialsProvider; +import com.google.api.gax.tracing.NoopTracerFactory; +import com.google.api.gax.tracing.TracerFactory; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import java.io.IOException; @@ -57,7 +59,6 @@ * a default executor. */ public abstract class StubSettings> { - private final ExecutorProvider executorProvider; private final CredentialsProvider credentialsProvider; private final HeaderProvider headerProvider; @@ -67,6 +68,7 @@ public abstract class StubSettings> { private final String endpoint; @Nullable private final WatchdogProvider streamWatchdogProvider; @Nonnull private final Duration streamWatchdogCheckInterval; + private final TracerFactory tracerFactory; /** Constructs an instance of StubSettings. */ protected StubSettings(Builder builder) { @@ -79,6 +81,7 @@ protected StubSettings(Builder builder) { this.endpoint = builder.endpoint; this.streamWatchdogProvider = builder.streamWatchdogProvider; this.streamWatchdogCheckInterval = builder.streamWatchdogCheckInterval; + this.tracerFactory = builder.tracerFactory; } public final ExecutorProvider getExecutorProvider() { @@ -123,6 +126,11 @@ public final Duration getStreamWatchdogCheckInterval() { return streamWatchdogCheckInterval; } + @BetaApi("The surface for tracing is not stable yet and may change in the future.") + public TracerFactory getTracerFactory() { + return tracerFactory; + } + public String toString() { return MoreObjects.toStringHelper(this) .add("executorProvider", executorProvider) @@ -134,6 +142,7 @@ public String toString() { .add("endpoint", endpoint) .add("streamWatchdogProvider", streamWatchdogProvider) .add("streamWatchdogCheckInterval", streamWatchdogCheckInterval) + .add("tracerFactory", tracerFactory) .toString(); } @@ -151,6 +160,7 @@ public abstract static class Builder< private String endpoint; @Nullable private WatchdogProvider streamWatchdogProvider; @Nonnull private Duration streamWatchdogCheckInterval; + private TracerFactory tracerFactory; /** Create a builder from a StubSettings object. */ protected Builder(StubSettings settings) { @@ -163,6 +173,7 @@ protected Builder(StubSettings settings) { this.endpoint = settings.endpoint; this.streamWatchdogProvider = settings.streamWatchdogProvider; this.streamWatchdogCheckInterval = settings.streamWatchdogCheckInterval; + this.tracerFactory = settings.tracerFactory; } protected Builder(ClientContext clientContext) { @@ -176,6 +187,7 @@ protected Builder(ClientContext clientContext) { this.endpoint = null; this.streamWatchdogProvider = InstantiatingWatchdogProvider.create(); this.streamWatchdogCheckInterval = Duration.ofSeconds(10); + this.tracerFactory = new NoopTracerFactory(); } else { this.executorProvider = FixedExecutorProvider.create(clientContext.getExecutor()); this.transportChannelProvider = @@ -189,6 +201,7 @@ protected Builder(ClientContext clientContext) { this.streamWatchdogProvider = FixedWatchdogProvider.create(clientContext.getStreamWatchdog()); this.streamWatchdogCheckInterval = clientContext.getStreamWatchdogCheckInterval(); + this.tracerFactory = clientContext.getTracerFactory(); } } @@ -290,6 +303,13 @@ public B setStreamWatchdogCheckInterval(@Nonnull Duration checkInterval) { return self(); } + /** Configures the tracing implementation. */ + @BetaApi("The surface for tracing is not stable yet and may change in the future.") + public B setTracerFactory(TracerFactory tracerFactory) { + this.tracerFactory = tracerFactory; + return self(); + } + /** Gets the ExecutorProvider that was previously set on this Builder. */ public ExecutorProvider getExecutorProvider() { return executorProvider; @@ -339,6 +359,11 @@ public Duration getStreamWatchdogCheckInterval() { return streamWatchdogCheckInterval; } + @BetaApi("The surface for tracing is not stable yet and may change in the future.") + public TracerFactory getTracerFactory() { + return tracerFactory; + } + /** Applies the given settings updater function to the given method settings builders. */ protected static void applyToAllUnaryMethods( Iterable> methodSettingsBuilders, @@ -361,6 +386,7 @@ public String toString() { .add("endpoint", endpoint) .add("streamWatchdogProvider", streamWatchdogProvider) .add("streamWatchdogCheckInterval", streamWatchdogCheckInterval) + .add("tracerFactory", tracerFactory) .toString(); } } diff --git a/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java b/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java index e4106d6b6..47d856955 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java +++ b/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java @@ -34,6 +34,8 @@ import com.google.api.gax.rpc.ClientContext; import com.google.api.gax.rpc.TransportChannel; import com.google.api.gax.rpc.internal.Headers; +import com.google.api.gax.tracing.NoopTracer; +import com.google.api.gax.tracing.Tracer; import com.google.auth.Credentials; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; @@ -51,6 +53,7 @@ public class FakeCallContext implements ApiCallContext { private final Duration streamWaitTimeout; private final Duration streamIdleTimeout; private final ImmutableMap> extraHeaders; + private final Tracer tracer; private FakeCallContext( Credentials credentials, @@ -58,18 +61,20 @@ private FakeCallContext( Duration timeout, Duration streamWaitTimeout, Duration streamIdleTimeout, - ImmutableMap> extraHeaders) { + ImmutableMap> extraHeaders, + Tracer tracer) { this.credentials = credentials; this.channel = channel; this.timeout = timeout; this.streamWaitTimeout = streamWaitTimeout; this.streamIdleTimeout = streamIdleTimeout; this.extraHeaders = extraHeaders; + this.tracer = tracer; } public static FakeCallContext createDefault() { return new FakeCallContext( - null, null, null, null, null, ImmutableMap.>of()); + null, null, null, null, null, ImmutableMap.>of(), NoopTracer.create()); } @Override @@ -133,7 +138,8 @@ public ApiCallContext merge(ApiCallContext inputCallContext) { newTimeout, newStreamWaitTimeout, newStreamIdleTimeout, - newExtraHeaders); + newExtraHeaders, + fakeCallContext.tracer); } public Credentials getCredentials() { @@ -169,7 +175,8 @@ public FakeCallContext withCredentials(Credentials credentials) { this.timeout, this.streamWaitTimeout, this.streamIdleTimeout, - this.extraHeaders); + this.extraHeaders, + this.tracer); } @Override @@ -190,7 +197,8 @@ public FakeCallContext withChannel(FakeChannel channel) { this.timeout, this.streamWaitTimeout, this.streamIdleTimeout, - this.extraHeaders); + this.extraHeaders, + this.tracer); } @Override @@ -211,7 +219,8 @@ public FakeCallContext withTimeout(Duration timeout) { timeout, this.streamWaitTimeout, this.streamIdleTimeout, - this.extraHeaders); + this.extraHeaders, + this.tracer); } @Override @@ -223,7 +232,8 @@ public ApiCallContext withStreamWaitTimeout(@Nonnull Duration streamWaitTimeout) this.timeout, streamWaitTimeout, this.streamIdleTimeout, - this.extraHeaders); + this.extraHeaders, + this.tracer); } @Override @@ -235,7 +245,8 @@ public ApiCallContext withStreamIdleTimeout(@Nonnull Duration streamIdleTimeout) this.timeout, this.streamWaitTimeout, streamIdleTimeout, - this.extraHeaders); + this.extraHeaders, + this.tracer); } @Override @@ -244,7 +255,13 @@ public ApiCallContext withExtraHeaders(Map> extraHeaders) { ImmutableMap> newExtraHeaders = Headers.mergeHeaders(this.extraHeaders, extraHeaders); return new FakeCallContext( - credentials, channel, timeout, streamWaitTimeout, streamIdleTimeout, newExtraHeaders); + credentials, + channel, + timeout, + streamWaitTimeout, + streamIdleTimeout, + newExtraHeaders, + this.tracer); } @Override @@ -252,6 +269,23 @@ public Map> getExtraHeaders() { return this.extraHeaders; } + @Override + public Tracer getTracer() { + return tracer; + } + + @Override + public ApiCallContext withTracer(Tracer tracer) { + return new FakeCallContext( + this.credentials, + this.channel, + this.timeout, + this.streamWaitTimeout, + this.streamIdleTimeout, + this.extraHeaders, + tracer); + } + public static FakeCallContext create(ClientContext clientContext) { return FakeCallContext.createDefault() .withTransportChannel(clientContext.getTransportChannel()) From 523ba0c55d97751aa7f7771b41327c7935dd9ce5 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Mon, 22 Oct 2018 13:59:39 -0400 Subject: [PATCH 4/5] Add a helper to generate span names in GrpcCallableFactory --- .../google/api/gax/grpc/GrpcCallableFactory.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java index c05812a75..7d0318b22 100644 --- a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java @@ -46,9 +46,11 @@ import com.google.api.gax.rpc.StreamingCallSettings; import com.google.api.gax.rpc.UnaryCallSettings; import com.google.api.gax.rpc.UnaryCallable; +import com.google.api.gax.tracing.SpanName; import com.google.common.collect.ImmutableSet; import com.google.longrunning.Operation; import com.google.longrunning.stub.OperationsStub; +import io.grpc.MethodDescriptor; /** Class with utility methods to create grpc-based direct callables. */ @BetaApi("The surface for use by generated code is not stable yet and may change in the future.") @@ -276,4 +278,17 @@ ClientStreamingCallable createClientStreamingCallable( return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } + + private static SpanName getSpanName(GrpcCallSettings grpcCallSettings) { + MethodDescriptor methodDescriptor = grpcCallSettings.getMethodDescriptor(); + + int index = methodDescriptor.getFullMethodName().lastIndexOf('/'); + String fullServiceName = methodDescriptor.getFullMethodName().substring(0, index); + String methodName = methodDescriptor.getFullMethodName().substring(index + 1); + + int serviceIndex = fullServiceName.lastIndexOf('.'); + String clientName = fullServiceName.substring(serviceIndex + 1); + + return SpanName.of(clientName, methodName); + } } From df1c6b574afb2e09c0a1fab71123f3bb58ede740 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Mon, 22 Oct 2018 13:59:52 -0400 Subject: [PATCH 5/5] wip --- .../api/gax/grpc/GrpcCallableFactory.java | 58 ++++++- .../grpc/testing/FakeMethodDescriptor.java | 2 +- .../api/gax/retrying/BasicRetryingFuture.java | 13 ++ .../api/gax/retrying/NoopRetryingContext.java | 11 ++ .../api/gax/retrying/RetryAlgorithm.java | 17 ++ .../google/api/gax/rpc/AttemptCallable.java | 4 +- .../api/gax/rpc/CheckingAttemptCallable.java | 5 +- .../google/api/gax/tracing/TraceFinisher.java | 50 ++++++ .../api/gax/tracing/TracedBatchCallable.java | 79 +++++++++ .../api/gax/tracing/TracedBidiCallable.java | 157 ++++++++++++++++++ .../TracedClientStreamingCallable.java | 127 ++++++++++++++ .../gax/tracing/TracedOperationCallable.java | 118 +++++++++++++ .../TracedServerStreamingCallable.java | 103 ++++++++++++ .../api/gax/tracing/TracedUnaryCallable.java | 70 ++++++++ .../AbstractRetryingExecutorTest.java | 9 +- 15 files changed, 815 insertions(+), 8 deletions(-) create mode 100644 gax/src/main/java/com/google/api/gax/tracing/TraceFinisher.java create mode 100644 gax/src/main/java/com/google/api/gax/tracing/TracedBatchCallable.java create mode 100644 gax/src/main/java/com/google/api/gax/tracing/TracedBidiCallable.java create mode 100644 gax/src/main/java/com/google/api/gax/tracing/TracedClientStreamingCallable.java create mode 100644 gax/src/main/java/com/google/api/gax/tracing/TracedOperationCallable.java create mode 100644 gax/src/main/java/com/google/api/gax/tracing/TracedServerStreamingCallable.java create mode 100644 gax/src/main/java/com/google/api/gax/tracing/TracedUnaryCallable.java diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java index 7d0318b22..9a362e351 100644 --- a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java @@ -47,6 +47,12 @@ import com.google.api.gax.rpc.UnaryCallSettings; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.gax.tracing.SpanName; +import com.google.api.gax.tracing.TracedBatchCallable; +import com.google.api.gax.tracing.TracedBidiCallable; +import com.google.api.gax.tracing.TracedClientStreamingCallable; +import com.google.api.gax.tracing.TracedOperationCallable; +import com.google.api.gax.tracing.TracedServerStreamingCallable; +import com.google.api.gax.tracing.TracedUnaryCallable; import com.google.common.collect.ImmutableSet; import com.google.longrunning.Operation; import com.google.longrunning.stub.OperationsStub; @@ -92,6 +98,11 @@ public static UnaryCallable createUna ClientContext clientContext) { UnaryCallable callable = createBaseUnaryCallable(grpcCallSettings, callSettings, clientContext); + + callable = + new TracedUnaryCallable<>( + callable, clientContext.getTracerFactory(), getSpanName(grpcCallSettings)); + return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } @@ -111,8 +122,13 @@ UnaryCallable createPagedCallable( ClientContext clientContext) { UnaryCallable innerCallable = createBaseUnaryCallable(grpcCallSettings, pagedCallSettings, clientContext); + + UnaryCallable tracedCallable = + new TracedUnaryCallable<>( + innerCallable, clientContext.getTracerFactory(), getSpanName(grpcCallSettings)); + UnaryCallable pagedCallable = - Callables.paged(innerCallable, pagedCallSettings); + Callables.paged(tracedCallable, pagedCallSettings); return pagedCallable.withDefaultCallContext(clientContext.getDefaultCallContext()); } @@ -133,6 +149,14 @@ public static UnaryCallable createBat ClientContext clientContext) { UnaryCallable callable = createBaseUnaryCallable(grpcCallSettings, batchingCallSettings, clientContext); + + callable = + new TracedBatchCallable<>( + callable, + clientContext.getTracerFactory(), + getSpanName(grpcCallSettings), + batchingCallSettings.getBatchingDescriptor()); + callable = Callables.batching(callable, batchingCallSettings, clientContext); return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } @@ -161,11 +185,27 @@ OperationCallable createOperationCallable( grpcCallSettings, operationCallSettings.getInitialCallSettings(), clientContext); UnaryCallable initialCallable = new GrpcOperationSnapshotCallable<>(initialGrpcCallable); + + SpanName overallSpanName = getSpanName(grpcCallSettings); + + // Wrap the start of the operation in a sub-span + SpanName startSpanName = + overallSpanName.withMethodName(overallSpanName.getMethodName() + ".Start"); + + UnaryCallable tracedInitialCallable = + new TracedUnaryCallable<>(initialCallable, clientContext.getTracerFactory(), startSpanName); + LongRunningClient longRunningClient = new GrpcLongRunningClient(operationsStub); OperationCallable operationCallable = Callables.longRunningOperation( - initialCallable, operationCallSettings, clientContext, longRunningClient); - return operationCallable.withDefaultCallContext(clientContext.getDefaultCallContext()); + tracedInitialCallable, operationCallSettings, clientContext, longRunningClient); + + // Outer span + TracedOperationCallable tracedCallable = + new TracedOperationCallable<>( + operationCallable, clientContext.getTracerFactory(), overallSpanName); + + return tracedCallable.withDefaultCallContext(clientContext.getDefaultCallContext()); } /** @@ -190,6 +230,10 @@ BidiStreamingCallable createBidiStreamingCallable( callable = new GrpcExceptionBidiStreamingCallable<>(callable, ImmutableSet.of()); + callable = + new TracedBidiCallable<>( + callable, clientContext.getTracerFactory(), getSpanName(grpcCallSettings)); + return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } @@ -251,6 +295,10 @@ ServerStreamingCallable createServerStreamingCallable( callable = Callables.retrying(callable, streamingCallSettings, clientContext); + callable = + new TracedServerStreamingCallable<>( + callable, clientContext.getTracerFactory(), getSpanName(grpcCallSettings)); + return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } @@ -276,6 +324,10 @@ ClientStreamingCallable createClientStreamingCallable( callable = new GrpcExceptionClientStreamingCallable<>(callable, ImmutableSet.of()); + callable = + new TracedClientStreamingCallable<>( + callable, clientContext.getTracerFactory(), getSpanName(grpcCallSettings)); + return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } diff --git a/gax-grpc/src/test/java/com/google/api/gax/grpc/testing/FakeMethodDescriptor.java b/gax-grpc/src/test/java/com/google/api/gax/grpc/testing/FakeMethodDescriptor.java index a60e5008d..0aca65ecf 100644 --- a/gax-grpc/src/test/java/com/google/api/gax/grpc/testing/FakeMethodDescriptor.java +++ b/gax-grpc/src/test/java/com/google/api/gax/grpc/testing/FakeMethodDescriptor.java @@ -39,7 +39,7 @@ public class FakeMethodDescriptor { private FakeMethodDescriptor() {} public static MethodDescriptor create() { - return create(MethodDescriptor.MethodType.UNARY, "(default name)"); + return create(MethodDescriptor.MethodType.UNARY, "FakeClient/fake-method"); } public static MethodDescriptor create( diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java index 331a1e304..918858111 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java @@ -141,9 +141,11 @@ void handleAttempt(Throwable throwable, ResponseT response) { try { clearAttemptServiceData(); if (throwable instanceof CancellationException) { + retryingContext.getTracer().permanentFailure(throwable); // An attempt triggered cancellation. super.cancel(false); } else if (throwable instanceof RejectedExecutionException) { + retryingContext.getTracer().permanentFailure(throwable); // external executor cannot continue retrying super.setException(throwable); } @@ -155,18 +157,29 @@ void handleAttempt(Throwable throwable, ResponseT response) { retryAlgorithm.createNextAttempt(throwable, response, attemptSettings); boolean shouldRetry = retryAlgorithm.shouldRetry(throwable, response, nextAttemptSettings); if (shouldRetry) { + retryingContext + .getTracer() + .retryableFailure(throwable, nextAttemptSettings.getRandomizedRetryDelay()); attemptSettings = nextAttemptSettings; setAttemptResult(throwable, response, true); // a new attempt will be (must be) scheduled by an external executor } else if (throwable != null) { + if (retryAlgorithm.couldRetry(throwable, response)) { + retryingContext.getTracer().retriesExhausted(); + } else { + retryingContext.getTracer().permanentFailure(throwable); + } super.setException(throwable); } else { + retryingContext.getTracer().attemptSucceeded(); super.set(response); } } catch (CancellationException e) { + retryingContext.getTracer().retriesExhausted(); // A retry algorithm triggered cancellation. super.cancel(false); } catch (Exception e) { + retryingContext.getTracer().permanentFailure(e); // Should never happen, but still possible in case of buggy retry algorithm implementation. // Any bugs/exceptions (except CancellationException) in retry algorithms immediately // terminate retrying future and set the result to the thrown exception. diff --git a/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java b/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java index def9cc796..a8ef05a0d 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java +++ b/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java @@ -31,6 +31,11 @@ // TODO(igorbernstein2): Remove this class once RetryingExecutor#createFuture(Callable) is // deprecated and removed. + +import com.google.api.gax.tracing.NoopTracer; +import com.google.api.gax.tracing.Tracer; +import javax.annotation.Nonnull; + /** * Backwards compatibility class to aid in transition to adding operation state to {@link * RetryingFuture} implementations. @@ -39,4 +44,10 @@ class NoopRetryingContext implements RetryingContext { public static RetryingContext create() { return new NoopRetryingContext(); } + + @Nonnull + @Override + public Tracer getTracer() { + return NoopTracer.create(); + } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java index 057148752..f74dae6ae 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java @@ -95,6 +95,23 @@ public TimedAttemptSettings createNextAttempt( return newSettings; } + /** + * Returns {@code true} if the result of the last attempt is retryable, or {@code false} + * otherwise. + * + * @param prevThrowable exception thrown by the previous attempt or null if a result was returned + * instead + * @param prevResponse response returned by the previous attempt or null if an exception was + * thrown instead + */ + public boolean couldRetry(Throwable prevThrowable, ResponseT prevResponse) { + try { + return resultAlgorithm.shouldRetry(prevThrowable, prevResponse); + } catch (CancellationException ignored) { + return false; + } + } + /** * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * diff --git a/gax/src/main/java/com/google/api/gax/rpc/AttemptCallable.java b/gax/src/main/java/com/google/api/gax/rpc/AttemptCallable.java index b0ac657e5..f4b3701a4 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/AttemptCallable.java +++ b/gax/src/main/java/com/google/api/gax/rpc/AttemptCallable.java @@ -33,6 +33,7 @@ import com.google.api.core.ApiFutures; import com.google.api.gax.retrying.NonCancellableFuture; import com.google.api.gax.retrying.RetryingFuture; +import com.google.api.gax.tracing.Tracer.Scope; import com.google.common.base.Preconditions; import java.util.concurrent.Callable; import org.threeten.bp.Duration; @@ -68,7 +69,8 @@ public void setExternalFuture(RetryingFuture externalFuture) { public ResponseT call() { ApiCallContext callContext = originalCallContext; - try { + try (Scope ignored = callContext.getTracer().inScope()) { + callContext.getTracer().startAttempt(); Duration rpcTimeout = externalFuture.getAttemptSettings().getRpcTimeout(); if (!rpcTimeout.isZero()) { callContext = callContext.withTimeout(rpcTimeout); diff --git a/gax/src/main/java/com/google/api/gax/rpc/CheckingAttemptCallable.java b/gax/src/main/java/com/google/api/gax/rpc/CheckingAttemptCallable.java index 85c2976fa..5e43b2896 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/CheckingAttemptCallable.java +++ b/gax/src/main/java/com/google/api/gax/rpc/CheckingAttemptCallable.java @@ -33,6 +33,7 @@ import com.google.api.core.ApiFutures; import com.google.api.gax.retrying.NonCancellableFuture; import com.google.api.gax.retrying.RetryingFuture; +import com.google.api.gax.tracing.Tracer.Scope; import com.google.common.base.Preconditions; import java.util.concurrent.Callable; import org.threeten.bp.Duration; @@ -65,7 +66,9 @@ public void setExternalFuture(RetryingFuture externalFuture) { public ResponseT call() { ApiCallContext callContext = originalCallContext; - try { + try (Scope ignored = callContext.getTracer().inScope()) { + callContext.getTracer().startAttempt(); + Duration rpcTimeout = externalFuture.getAttemptSettings().getRpcTimeout(); if (!rpcTimeout.isZero()) { callContext = callContext.withTimeout(rpcTimeout); diff --git a/gax/src/main/java/com/google/api/gax/tracing/TraceFinisher.java b/gax/src/main/java/com/google/api/gax/tracing/TraceFinisher.java new file mode 100644 index 000000000..994390b3f --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/TraceFinisher.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017 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.ApiFutureCallback; + +class TraceFinisher implements ApiFutureCallback { + private final Tracer tracer; + + TraceFinisher(Tracer tracer) { + this.tracer = tracer; + } + + @Override + public void onFailure(Throwable throwable) { + tracer.operationFailed(throwable); + } + + @Override + public void onSuccess(Object responseT) { + tracer.operationSucceeded(); + } +} diff --git a/gax/src/main/java/com/google/api/gax/tracing/TracedBatchCallable.java b/gax/src/main/java/com/google/api/gax/tracing/TracedBatchCallable.java new file mode 100644 index 000000000..c5cf6b181 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/TracedBatchCallable.java @@ -0,0 +1,79 @@ +/* + * Copyright 2017 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.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.api.core.InternalApi; +import com.google.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.BatchingDescriptor; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.util.concurrent.MoreExecutors; + +@InternalApi +public class TracedBatchCallable extends UnaryCallable { + private final TracerFactory tracerFactory; + private final SpanName spanName; + private final BatchingDescriptor batchingDescriptor; + private final UnaryCallable innerCallable; + + public TracedBatchCallable( + UnaryCallable innerCallable, + TracerFactory tracerFactory, + SpanName spanName, + BatchingDescriptor batchingDescriptor) { + this.tracerFactory = tracerFactory; + this.spanName = spanName; + this.batchingDescriptor = batchingDescriptor; + this.innerCallable = innerCallable; + } + + @Override + public ApiFuture futureCall(RequestT request, ApiCallContext context) { + Tracer tracer = tracerFactory.newTracer(Tracer.Type.Batched, spanName); + TraceFinisher finisher = new TraceFinisher(tracer); + + try { + long elementCount = batchingDescriptor.countElements(request); + long requestSize = batchingDescriptor.countBytes(request); + + tracer.sentBatchRequest(elementCount, requestSize); + + context = context.withTracer(tracer); + ApiFuture future = innerCallable.futureCall(request, context); + ApiFutures.addCallback(future, finisher, MoreExecutors.directExecutor()); + + return future; + } catch (RuntimeException e) { + finisher.onFailure(e); + throw e; + } + } +} diff --git a/gax/src/main/java/com/google/api/gax/tracing/TracedBidiCallable.java b/gax/src/main/java/com/google/api/gax/tracing/TracedBidiCallable.java new file mode 100644 index 000000000..5835b59e7 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/TracedBidiCallable.java @@ -0,0 +1,157 @@ +/* + * Copyright 2017 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.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.BidiStreamingCallable; +import com.google.api.gax.rpc.ClientStream; +import com.google.api.gax.rpc.ClientStreamReadyObserver; +import com.google.api.gax.rpc.ResponseObserver; +import com.google.api.gax.rpc.StreamController; +import com.google.api.gax.tracing.Tracer.Scope; + +public class TracedBidiCallable + extends BidiStreamingCallable { + + private final TracerFactory tracerFactory; + private final SpanName spanName; + private final BidiStreamingCallable innerCallable; + + public TracedBidiCallable( + BidiStreamingCallable innerCallable, + TracerFactory tracerFactory, + SpanName spanName) { + this.tracerFactory = tracerFactory; + this.spanName = spanName; + this.innerCallable = innerCallable; + } + + @Override + public ClientStream internalCall( + ResponseObserver responseObserver, + ClientStreamReadyObserver onReady, + ApiCallContext context) { + + Tracer tracer = tracerFactory.newTracer(Tracer.Type.Bidi, spanName); + context = context.withTracer(tracer); + + ResponseObserver tracedObserver = + new TracingResponseObserver<>(tracer, responseObserver); + ClientStreamReadyObserver tracedReadyObserver = + new TracingClientStreamReadyObserver<>(tracer, onReady); + + try (Scope scope = tracer.inScope()) { + ClientStream clientStream = + innerCallable.internalCall(tracedObserver, tracedReadyObserver, context); + return new TracingClientStream<>(tracer, clientStream); + } + } + + private static class TracingResponseObserver implements ResponseObserver { + private final Tracer tracer; + private final ResponseObserver innerObserver; + + public TracingResponseObserver(Tracer tracer, ResponseObserver innerObserver) { + this.tracer = tracer; + this.innerObserver = innerObserver; + } + + @Override + public void onStart(StreamController controller) { + innerObserver.onStart(controller); + } + + @Override + public void onResponse(ResponseT response) { + tracer.receivedResponse(); + innerObserver.onResponse(response); + } + + @Override + public void onError(Throwable t) { + tracer.operationFailed(t); + innerObserver.onError(t); + } + + @Override + public void onComplete() { + tracer.operationSucceeded(); + innerObserver.onComplete(); + } + } + + private static class TracingClientStreamReadyObserver + implements ClientStreamReadyObserver { + private final Tracer tracer; + private final ClientStreamReadyObserver innerObserver; + + public TracingClientStreamReadyObserver( + Tracer tracer, ClientStreamReadyObserver innerObserver) { + this.tracer = tracer; + this.innerObserver = innerObserver; + } + + @Override + public void onReady(ClientStream stream) { + innerObserver.onReady(new TracingClientStream<>(tracer, stream)); + } + } + + private static class TracingClientStream implements ClientStream { + private final Tracer tracer; + private final ClientStream innerStream; + + public TracingClientStream(Tracer tracer, ClientStream innerStream) { + this.tracer = tracer; + this.innerStream = innerStream; + } + + @Override + public void send(RequestT request) { + tracer.sentRequest(); + innerStream.send(request); + } + + @Override + public void closeSendWithError(Throwable t) { + innerStream.closeSendWithError(t); + } + + @Override + public void closeSend() { + innerStream.closeSend(); + } + + @Override + public boolean isSendReady() { + return innerStream.isSendReady(); + } + } +} diff --git a/gax/src/main/java/com/google/api/gax/tracing/TracedClientStreamingCallable.java b/gax/src/main/java/com/google/api/gax/tracing/TracedClientStreamingCallable.java new file mode 100644 index 000000000..f2ae0618f --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/TracedClientStreamingCallable.java @@ -0,0 +1,127 @@ +/* + * Copyright 2017 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.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.ApiStreamObserver; +import com.google.api.gax.rpc.ClientStreamingCallable; +import com.google.api.gax.tracing.Tracer.Type; + +@InternalApi +public class TracedClientStreamingCallable + extends ClientStreamingCallable { + private final ClientStreamingCallable innerCallable; + private final TracerFactory tracerFactory; + private final SpanName spanName; + + public TracedClientStreamingCallable( + ClientStreamingCallable innerCallable, + TracerFactory tracerFactory, + SpanName spanName) { + this.innerCallable = innerCallable; + this.tracerFactory = tracerFactory; + this.spanName = spanName; + } + + @Override + public ApiStreamObserver clientStreamingCall( + ApiStreamObserver responseObserver, ApiCallContext context) { + + Tracer tracer = tracerFactory.newTracer(Type.ClientStreaming, spanName); + context = context.withTracer(tracer); + + try { + ApiStreamObserver innerResponseObserver = + new TracedResponseObserver<>(tracer, responseObserver); + ApiStreamObserver innerRequestObserver = + innerCallable.clientStreamingCall(innerResponseObserver, context); + + return new TracedRequestObserver<>(tracer, innerRequestObserver); + } catch (RuntimeException e) { + tracer.operationFailed(e); + throw e; + } + } + + private static class TracedRequestObserver implements ApiStreamObserver { + private final Tracer tracer; + private final ApiStreamObserver innerObserver; + + TracedRequestObserver(Tracer tracer, ApiStreamObserver innerObserver) { + this.tracer = tracer; + this.innerObserver = innerObserver; + } + + @Override + public void onNext(RequestT value) { + tracer.sentRequest(); + innerObserver.onNext(value); + } + + @Override + public void onError(Throwable t) { + innerObserver.onError(t); + } + + @Override + public void onCompleted() { + innerObserver.onCompleted(); + } + } + + private static class TracedResponseObserver implements ApiStreamObserver { + private final Tracer tracer; + private final ApiStreamObserver innerObserver; + + TracedResponseObserver(Tracer tracer, ApiStreamObserver innerObserver) { + this.tracer = tracer; + this.innerObserver = innerObserver; + } + + @Override + public void onNext(RequestT value) { + this.tracer.receivedResponse(); + innerObserver.onNext(value); + } + + @Override + public void onError(Throwable t) { + tracer.operationFailed(t); + innerObserver.onError(t); + } + + @Override + public void onCompleted() { + tracer.operationSucceeded(); + innerObserver.onCompleted(); + } + } +} diff --git a/gax/src/main/java/com/google/api/gax/tracing/TracedOperationCallable.java b/gax/src/main/java/com/google/api/gax/tracing/TracedOperationCallable.java new file mode 100644 index 000000000..0737b0628 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/TracedOperationCallable.java @@ -0,0 +1,118 @@ +/* + * Copyright 2017 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.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.OperationCallable; +import com.google.api.gax.tracing.Tracer.Scope; +import com.google.common.util.concurrent.MoreExecutors; + +public class TracedOperationCallable + extends OperationCallable { + private OperationCallable innerCallable; + private final TracerFactory tracerFactory; + private SpanName spanName; + + public TracedOperationCallable( + OperationCallable innerCallable, + TracerFactory tracerFactory, + SpanName spanName) { + this.innerCallable = innerCallable; + this.tracerFactory = tracerFactory; + this.spanName = spanName; + } + + @Override + public OperationFuture futureCall( + RequestT request, ApiCallContext context) { + Tracer tracer = tracerFactory.newTracer(Tracer.Type.LongRunning, spanName); + TraceFinisher finisher = new TraceFinisher(tracer); + + context = context.withTracer(tracer); + + try (Scope ignored = tracer.inScope()) { + OperationFuture future = innerCallable.futureCall(request, context); + + ApiFutures.addCallback(future, finisher, MoreExecutors.directExecutor()); + + return future; + } catch (RuntimeException e) { + finisher.onFailure(e); + throw e; + } + } + + @Override + public OperationFuture resumeFutureCall( + String operationName, ApiCallContext context) { + Tracer tracer = + tracerFactory.newTracer( + Tracer.Type.LongRunning, spanName.withMethodName(spanName.getMethodName() + ".Resume")); + TraceFinisher finisher = new TraceFinisher(tracer); + + context = context.withTracer(tracer); + + try (Scope ignored = tracer.inScope()) { + OperationFuture future = + innerCallable.resumeFutureCall(operationName, context); + + ApiFutures.addCallback(future, finisher, MoreExecutors.directExecutor()); + + return future; + } catch (RuntimeException e) { + finisher.onFailure(e); + throw e; + } + } + + @Override + public ApiFuture cancel(String operationName, ApiCallContext context) { + Tracer tracer = + tracerFactory.newTracer( + Tracer.Type.Unary, spanName.withMethodName(spanName.getMethodName() + ".Resume")); + TraceFinisher finisher = new TraceFinisher(tracer); + + context = context.withTracer(tracer); + + try (Scope ignored = tracer.inScope()) { + ApiFuture future = innerCallable.cancel(operationName, context); + + ApiFutures.addCallback(future, finisher, MoreExecutors.directExecutor()); + + return future; + } catch (RuntimeException e) { + finisher.onFailure(e); + throw e; + } + } +} diff --git a/gax/src/main/java/com/google/api/gax/tracing/TracedServerStreamingCallable.java b/gax/src/main/java/com/google/api/gax/tracing/TracedServerStreamingCallable.java new file mode 100644 index 000000000..4d30f6bc6 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/TracedServerStreamingCallable.java @@ -0,0 +1,103 @@ +/* + * Copyright 2017 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.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.ResponseObserver; +import com.google.api.gax.rpc.ServerStreamingCallable; +import com.google.api.gax.rpc.StreamController; + +public class TracedServerStreamingCallable + extends ServerStreamingCallable { + + private final TracerFactory tracerFactory; + private final SpanName spanName; + private final ServerStreamingCallable innerCallable; + + public TracedServerStreamingCallable( + ServerStreamingCallable innerCallable, + TracerFactory tracerFactory, + SpanName spanName) { + this.tracerFactory = tracerFactory; + this.spanName = spanName; + this.innerCallable = innerCallable; + } + + @Override + public void call( + RequestT request, ResponseObserver responseObserver, ApiCallContext context) { + + Tracer tracer = tracerFactory.newTracer(Tracer.Type.ServerStreaming, spanName); + TracedResponseObserver tracedObserver = + new TracedResponseObserver<>(tracer, responseObserver); + + context = context.withTracer(tracer); + + try { + innerCallable.call(request, tracedObserver, context); + } catch (RuntimeException e) { + tracedObserver.onError(e); + throw e; + } + } + + private static class TracedResponseObserver implements ResponseObserver { + private final Tracer tracer; + private final ResponseObserver innerObserver; + + public TracedResponseObserver(Tracer tracer, ResponseObserver innerObserver) { + this.tracer = tracer; + this.innerObserver = innerObserver; + } + + @Override + public void onStart(StreamController controller) { + innerObserver.onStart(controller); + } + + @Override + public void onResponse(ResponseT response) { + tracer.receivedResponse(); + innerObserver.onResponse(response); + } + + @Override + public void onError(Throwable t) { + tracer.operationFailed(t); + innerObserver.onError(t); + } + + @Override + public void onComplete() { + tracer.operationSucceeded(); + innerObserver.onComplete(); + } + } +} diff --git a/gax/src/main/java/com/google/api/gax/tracing/TracedUnaryCallable.java b/gax/src/main/java/com/google/api/gax/tracing/TracedUnaryCallable.java new file mode 100644 index 000000000..dc66d3097 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/tracing/TracedUnaryCallable.java @@ -0,0 +1,70 @@ +/* + * Copyright 2017 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.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.api.core.InternalApi; +import com.google.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.util.concurrent.MoreExecutors; + +@InternalApi +public class TracedUnaryCallable extends UnaryCallable { + private final UnaryCallable innerCallable; + private final TracerFactory tracerFactory; + private final SpanName spanName; + + public TracedUnaryCallable( + UnaryCallable innerCallable, + TracerFactory tracerFactory, + SpanName spanName) { + this.innerCallable = innerCallable; + this.tracerFactory = tracerFactory; + this.spanName = spanName; + } + + @Override + public ApiFuture futureCall(RequestT request, ApiCallContext context) { + Tracer tracer = tracerFactory.newTracer(Tracer.Type.Unary, spanName); + TraceFinisher finisher = new TraceFinisher(tracer); + + try { + context = context.withTracer(tracer); + ApiFuture future = innerCallable.futureCall(request, context); + ApiFutures.addCallback(future, finisher, MoreExecutors.directExecutor()); + + return future; + } catch (RuntimeException e) { + finisher.onFailure(e); + throw e; + } + } +} diff --git a/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java index 08f6562eb..7551c0be8 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java @@ -45,15 +45,15 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.threeten.bp.Duration; @RunWith(MockitoJUnitRunner.class) public abstract class AbstractRetryingExecutorTest { - @Mock protected RetryingContext retryingContext; + protected RetryingContext retryingContext; protected abstract RetryingExecutorWithContext getExecutor( RetryAlgorithm retryAlgorithm); @@ -61,6 +61,11 @@ protected abstract RetryingExecutorWithContext getExecutor( protected abstract RetryAlgorithm getAlgorithm( RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException); + @Before + public void setUp() { + retryingContext = NoopRetryingContext.create(); + } + @Test public void testSuccess() throws Exception { FailingCallable callable = new FailingCallable(0, "SUCCESS");