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-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-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..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 @@ -46,9 +46,17 @@ 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.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; +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.") @@ -90,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()); } @@ -109,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()); } @@ -131,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()); } @@ -159,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()); } /** @@ -188,6 +230,10 @@ BidiStreamingCallable createBidiStreamingCallable( callable = new GrpcExceptionBidiStreamingCallable<>(callable, ImmutableSet.of()); + callable = + new TracedBidiCallable<>( + callable, clientContext.getTracerFactory(), getSpanName(grpcCallSettings)); + return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } @@ -249,6 +295,10 @@ ServerStreamingCallable createServerStreamingCallable( callable = Callables.retrying(callable, streamingCallSettings, clientContext); + callable = + new TracedServerStreamingCallable<>( + callable, clientContext.getTracerFactory(), getSpanName(grpcCallSettings)); + return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } @@ -274,6 +324,23 @@ ClientStreamingCallable createClientStreamingCallable( callable = new GrpcExceptionClientStreamingCallable<>(callable, ImmutableSet.of()); + callable = + new TracedClientStreamingCallable<>( + callable, clientContext.getTracerFactory(), getSpanName(grpcCallSettings)); + 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); + } } 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-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/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 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/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/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/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/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/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/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); +} 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"); 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())