From 589619306313700e24d074a4bd464cbf7b20091e Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Thu, 23 May 2024 16:13:24 -0400 Subject: [PATCH] feat: Allow Adding Client Level Attributes to MetricsTracerFactory --- gax-java/gax-grpc/pom.xml | 10 + .../InstantiatingGrpcChannelProvider.java | 65 ++++-- .../InstantiatingGrpcChannelProviderTest.java | 180 +++++++++++++++- .../google/api/gax/tracing/MetricsTracer.java | 1 + .../api/gax/tracing/MetricsTracerFactory.java | 22 +- .../gax/tracing/MetricsTracerFactoryTest.java | 40 +++- gax-java/pom.xml | 6 + .../showcase/v1beta1/it/ITOtelMetrics.java | 203 ++++++++++++------ 8 files changed, 423 insertions(+), 104 deletions(-) diff --git a/gax-java/gax-grpc/pom.xml b/gax-java/gax-grpc/pom.xml index b25e708814..1bdca58764 100644 --- a/gax-java/gax-grpc/pom.xml +++ b/gax-java/gax-grpc/pom.xml @@ -128,6 +128,16 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + + + \ No newline at end of file diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java index 5969ed4693..cd3eed190a 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java @@ -32,7 +32,6 @@ import com.google.api.core.ApiFunction; import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; -import com.google.api.core.InternalExtensionOnly; import com.google.api.gax.core.ExecutorProvider; import com.google.api.gax.rpc.FixedHeaderProvider; import com.google.api.gax.rpc.HeaderProvider; @@ -82,13 +81,24 @@ *

The client lib header and generator header values are used to form a value that goes into the * http header of requests to the service. */ -@InternalExtensionOnly public final class InstantiatingGrpcChannelProvider implements TransportChannelProvider { + + static String systemProductName; + + static { + try { + systemProductName = + Files.asCharSource(new File("/sys/class/dmi/id/product_name"), StandardCharsets.UTF_8) + .readFirstLine(); + } catch (IOException e) { + systemProductName = null; + } + } + @VisibleForTesting static final Logger LOG = Logger.getLogger(InstantiatingGrpcChannelProvider.class.getName()); - private static final String DIRECT_PATH_ENV_DISABLE_DIRECT_PATH = - "GOOGLE_CLOUD_DISABLE_DIRECT_PATH"; + static final String DIRECT_PATH_ENV_DISABLE_DIRECT_PATH = "GOOGLE_CLOUD_DISABLE_DIRECT_PATH"; private static final String DIRECT_PATH_ENV_ENABLE_XDS = "GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS"; static final long DIRECT_PATH_KEEP_ALIVE_TIME_SECONDS = 3600; static final long DIRECT_PATH_KEEP_ALIVE_TIMEOUT_SECONDS = 20; @@ -145,6 +155,7 @@ private InstantiatingGrpcChannelProvider(Builder builder) { builder.directPathServiceConfig == null ? getDefaultDirectPathServiceConfig() : builder.directPathServiceConfig; + systemProductName = builder.systemProductName; } /** @@ -319,16 +330,11 @@ boolean isCredentialDirectPathCompatible() { @VisibleForTesting static boolean isOnComputeEngine() { String osName = System.getProperty("os.name"); - if ("Linux".equals(osName)) { - try { - String result = - Files.asCharSource(new File("/sys/class/dmi/id/product_name"), StandardCharsets.UTF_8) - .readFirstLine(); - return result.contains(GCE_PRODUCTION_NAME_PRIOR_2016) - || result.contains(GCE_PRODUCTION_NAME_AFTER_2016); - } catch (IOException ignored) { - return false; - } + // systemProductName null check is in case there is an IOException + // IOException will set the systemProductName to null and should return false + if ("Linux".equals(osName) && systemProductName != null) { + return systemProductName.contains(GCE_PRODUCTION_NAME_PRIOR_2016) + || systemProductName.contains(GCE_PRODUCTION_NAME_AFTER_2016); } return false; } @@ -370,10 +376,7 @@ private ManagedChannel createSingleChannel() throws IOException { // Check DirectPath traffic. boolean useDirectPathXds = false; - if (isDirectPathEnabled() - && isCredentialDirectPathCompatible() - && isOnComputeEngine() - && canUseDirectPathWithUniverseDomain()) { + if (canUseDirectPath()) { CallCredentials callCreds = MoreCallCredentials.from(credentials); ChannelCredentials channelCreds = GoogleDefaultChannelCredentials.newBuilder().callCredentials(callCreds).build(); @@ -446,6 +449,24 @@ && canUseDirectPathWithUniverseDomain()) { return managedChannel; } + /** + * Marked as Internal Api and intended for internal use. DirectPath must be enabled via the + * settings and a few other configurations/settings must also be valid for the request to go + * through DirectPath. + * + *

Checks: 1. Credentials are compatible 2.Running on Compute Engine 3. Universe Domain is + * configured to for the Google Default Universe + * + * @return if DirectPath is enabled for the client AND if the configurations are valid + */ + @InternalApi + public boolean canUseDirectPath() { + return isDirectPathEnabled() + && isCredentialDirectPathCompatible() + && isOnComputeEngine() + && canUseDirectPathWithUniverseDomain(); + } + /** The endpoint to be used for the channel. */ @Override public String getEndpoint() { @@ -513,6 +534,7 @@ public static final class Builder { @Nullable private Boolean attemptDirectPathXds; @Nullable private Boolean allowNonDefaultServiceAccount; @Nullable private ImmutableMap directPathServiceConfig; + @Nullable private String systemProductName; private Builder() { processorCount = Runtime.getRuntime().availableProcessors(); @@ -541,6 +563,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) { this.allowNonDefaultServiceAccount = provider.allowNonDefaultServiceAccount; this.directPathServiceConfig = provider.directPathServiceConfig; this.mtlsProvider = provider.mtlsProvider; + this.systemProductName = null; } /** @@ -753,6 +776,12 @@ public Builder setAttemptDirectPathXds() { return this; } + @VisibleForTesting + Builder setSystemProductName(String systemProductName) { + this.systemProductName = systemProductName; + return this; + } + /** * Sets a service config for direct path. If direct path is not enabled, the provided service * config will be ignored. diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java index 2647ac6d13..0daddd6629 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java @@ -29,6 +29,7 @@ */ package com.google.api.gax.grpc; +import static com.google.api.gax.grpc.InstantiatingGrpcChannelProvider.GCE_PRODUCTION_NAME_AFTER_2016; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -37,13 +38,16 @@ import com.google.api.core.ApiFunction; import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider.Builder; import com.google.api.gax.rpc.HeaderProvider; +import com.google.api.gax.rpc.TransportChannel; import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.rpc.mtls.AbstractMtlsTransportChannelTest; import com.google.api.gax.rpc.mtls.MtlsProvider; +import com.google.auth.Credentials; import com.google.auth.oauth2.CloudShellCredentials; import com.google.auth.oauth2.ComputeEngineCredentials; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.truth.Truth; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.alts.ComputeEngineChannelBuilder; @@ -57,16 +61,40 @@ import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.logging.Handler; import java.util.logging.LogRecord; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.threeten.bp.Duration; class InstantiatingGrpcChannelProviderTest extends AbstractMtlsTransportChannelTest { + private static final String DEFAULT_ENDPOINT = "test.googleapis.com:443"; + private static String originalOSName; + private ComputeEngineCredentials computeEngineCredentials; + + @BeforeAll + public static void setupClass() { + originalOSName = System.getProperty("os.name"); + } + + @BeforeEach + public void setup() throws IOException { + System.setProperty("os.name", "Linux"); + computeEngineCredentials = Mockito.mock(ComputeEngineCredentials.class); + } + + @AfterEach + public void cleanup() { + System.setProperty("os.name", originalOSName); + } @Test void testEndpoint() { @@ -300,7 +328,7 @@ void testDirectPathWithGDUEndpoint() { InstantiatingGrpcChannelProvider.newBuilder() .setAttemptDirectPath(true) .setAttemptDirectPathXds() - .setEndpoint("test.googleapis.com:443") + .setEndpoint(DEFAULT_ENDPOINT) .build(); assertThat(provider.canUseDirectPathWithUniverseDomain()).isTrue(); } @@ -322,7 +350,7 @@ void testDirectPathXdsEnabled() throws IOException { InstantiatingGrpcChannelProvider.newBuilder() .setAttemptDirectPath(true) .setAttemptDirectPathXds() - .setEndpoint("test.googleapis.com:443") + .setEndpoint(DEFAULT_ENDPOINT) .build(); assertThat(provider.isDirectPathXdsEnabled()).isTrue(); @@ -552,13 +580,16 @@ void testLogDirectPathMisconfigAttrempDirectPathNotSet() throws Exception { .setEndpoint("localhost:8080") .build(); - provider.getTransportChannel(); + TransportChannel transportChannel = provider.getTransportChannel(); assertThat(logHandler.getAllMessages()) .contains( "DirectPath is misconfigured. Please set the attemptDirectPath option along with the" + " attemptDirectPathXds option."); InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler); + + transportChannel.close(); + transportChannel.awaitTermination(10, TimeUnit.SECONDS); } @Test @@ -584,16 +615,19 @@ void testLogDirectPathMisconfigWrongCredential() throws Exception { .setAttemptDirectPath(true) .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) - .setEndpoint("test.googleapis.com:443") + .setEndpoint(DEFAULT_ENDPOINT) .build(); - provider.getTransportChannel(); + TransportChannel transportChannel = provider.getTransportChannel(); assertThat(logHandler.getAllMessages()) .contains( "DirectPath is misconfigured. Please make sure the credential is an instance of" + " com.google.auth.oauth2.ComputeEngineCredentials ."); InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler); + + transportChannel.close(); + transportChannel.awaitTermination(10, TimeUnit.SECONDS); } @Test @@ -607,10 +641,10 @@ void testLogDirectPathMisconfigNotOnGCE() throws Exception { .setAllowNonDefaultServiceAccount(true) .setHeaderProvider(Mockito.mock(HeaderProvider.class)) .setExecutor(Mockito.mock(Executor.class)) - .setEndpoint("test.googleapis.com:443") + .setEndpoint(DEFAULT_ENDPOINT) .build(); - provider.getTransportChannel(); + TransportChannel transportChannel = provider.getTransportChannel(); if (!InstantiatingGrpcChannelProvider.isOnComputeEngine()) { assertThat(logHandler.getAllMessages()) @@ -618,6 +652,138 @@ void testLogDirectPathMisconfigNotOnGCE() throws Exception { "DirectPath is misconfigured. DirectPath is only available in a GCE environment."); } InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler); + + transportChannel.close(); + transportChannel.awaitTermination(10, TimeUnit.SECONDS); + } + + @Test + public void canUseDirectPath_happyPath() { + InstantiatingGrpcChannelProvider provider = + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPath(true) + .setCredentials(computeEngineCredentials) + .setEndpoint(DEFAULT_ENDPOINT) + .setSystemProductName(GCE_PRODUCTION_NAME_AFTER_2016) + .build(); + Truth.assertThat(provider.canUseDirectPath()).isTrue(); + } + + @Test + @SetEnvironmentVariable( + key = InstantiatingGrpcChannelProvider.DIRECT_PATH_ENV_DISABLE_DIRECT_PATH, + value = "true") + public void canUseDirectPath_directPathEnvVarDisabled() { + InstantiatingGrpcChannelProvider provider = + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPath(true) + .setCredentials(computeEngineCredentials) + .setEndpoint(DEFAULT_ENDPOINT) + .setSystemProductName(GCE_PRODUCTION_NAME_AFTER_2016) + .build(); + Truth.assertThat(provider.canUseDirectPath()).isFalse(); + } + + @Test + public void canUseDirectPath_directPathEnvVarNotSet_attemptDirectPathIsTrue() { + InstantiatingGrpcChannelProvider provider = + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPath(true) + .setCredentials(computeEngineCredentials) + .setEndpoint(DEFAULT_ENDPOINT) + .setSystemProductName(GCE_PRODUCTION_NAME_AFTER_2016) + .build(); + Truth.assertThat(provider.canUseDirectPath()).isTrue(); + } + + @Test + public void canUseDirectPath_directPathEnvVarNotSet_attemptDirectPathIsFalse() { + InstantiatingGrpcChannelProvider provider = + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPath(false) + .setCredentials(computeEngineCredentials) + .setEndpoint(DEFAULT_ENDPOINT) + .setSystemProductName(GCE_PRODUCTION_NAME_AFTER_2016) + .build(); + Truth.assertThat(provider.canUseDirectPath()).isFalse(); + } + + @Test + @SetEnvironmentVariable( + key = InstantiatingGrpcChannelProvider.DIRECT_PATH_ENV_DISABLE_DIRECT_PATH, + value = "false") + public void canUseDirectPath_nonComputeCredentials() { + Credentials credentials = Mockito.mock(Credentials.class); + InstantiatingGrpcChannelProvider provider = + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPath(true) + .setCredentials(credentials) + .setEndpoint(DEFAULT_ENDPOINT) + .setSystemProductName(GCE_PRODUCTION_NAME_AFTER_2016) + .build(); + Truth.assertThat(provider.canUseDirectPath()).isFalse(); + } + + @Test + @SetEnvironmentVariable( + key = InstantiatingGrpcChannelProvider.DIRECT_PATH_ENV_DISABLE_DIRECT_PATH, + value = "false") + public void canUseDirectPath_isNotOnComputeEngine_invalidOsNameSystemProperty() { + System.setProperty("os.name", "Not Linux"); + InstantiatingGrpcChannelProvider provider = + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPath(true) + .setCredentials(computeEngineCredentials) + .setEndpoint(DEFAULT_ENDPOINT) + .setSystemProductName(GCE_PRODUCTION_NAME_AFTER_2016) + .build(); + Truth.assertThat(provider.canUseDirectPath()).isFalse(); + } + + @Test + @SetEnvironmentVariable( + key = InstantiatingGrpcChannelProvider.DIRECT_PATH_ENV_DISABLE_DIRECT_PATH, + value = "false") + public void canUseDirectPath_isNotOnComputeEngine_invalidSystemProductName() { + InstantiatingGrpcChannelProvider provider = + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPath(true) + .setCredentials(computeEngineCredentials) + .setEndpoint(DEFAULT_ENDPOINT) + .setSystemProductName("testing") + .build(); + Truth.assertThat(provider.canUseDirectPath()).isFalse(); + } + + @Test + @SetEnvironmentVariable( + key = InstantiatingGrpcChannelProvider.DIRECT_PATH_ENV_DISABLE_DIRECT_PATH, + value = "false") + public void canUseDirectPath_isNotOnComputeEngine_unableToGetSystemProductName() { + InstantiatingGrpcChannelProvider provider = + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPath(true) + .setCredentials(computeEngineCredentials) + .setEndpoint(DEFAULT_ENDPOINT) + .setSystemProductName(null) + .build(); + Truth.assertThat(provider.canUseDirectPath()).isFalse(); + } + + @Test + @SetEnvironmentVariable( + key = InstantiatingGrpcChannelProvider.DIRECT_PATH_ENV_DISABLE_DIRECT_PATH, + value = "false") + public void canUseDirectPath_nonGDUUniverseDomain() { + String nonGDUEndpoint = "test.random.com:443"; + InstantiatingGrpcChannelProvider provider = + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPath(true) + .setCredentials(computeEngineCredentials) + .setEndpoint(nonGDUEndpoint) + .setSystemProductName(GCE_PRODUCTION_NAME_AFTER_2016) + .build(); + Truth.assertThat(provider.canUseDirectPath()).isFalse(); } private static class FakeLogHandler extends Handler { diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java index 7938bde82b..abbc9138dc 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java @@ -61,6 +61,7 @@ public class MetricsTracer implements ApiTracer { "Operation has already been completed"; private Stopwatch attemptTimer; private final Stopwatch operationTimer = Stopwatch.createStarted(); + // These are RPC specific attributes and pertain to a specific API Trace private final Map attributes = new HashMap<>(); private final MetricsRecorder metricsRecorder; private final AtomicBoolean operationFinished; diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracerFactory.java index d2b8d87fb4..3aa17bfb6c 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracerFactory.java @@ -31,6 +31,8 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; +import com.google.common.collect.ImmutableMap; +import java.util.Map; /** * A {@link ApiTracerFactory} to build instances of {@link MetricsTracer}. @@ -45,13 +47,29 @@ public class MetricsTracerFactory implements ApiTracerFactory { protected MetricsRecorder metricsRecorder; + /** Mapping of client attributes that are set for every MetricsTracer */ + private final Map attributes; + + /** Creates a MetricsTracerFactory with no additional client level attributes. */ public MetricsTracerFactory(MetricsRecorder metricsRecorder) { + this(metricsRecorder, ImmutableMap.of()); + } + + /** + * Pass in a Map of client level attributes which will be added to every single MetricsTracer + * created from the ApiTracerFactory. + */ + public MetricsTracerFactory(MetricsRecorder metricsRecorder, Map attributes) { this.metricsRecorder = metricsRecorder; + this.attributes = ImmutableMap.copyOf(attributes); } @Override public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { - return new MetricsTracer( - MethodName.of(spanName.getClientName(), spanName.getMethodName()), metricsRecorder); + MetricsTracer metricsTracer = + new MetricsTracer( + MethodName.of(spanName.getClientName(), spanName.getMethodName()), metricsRecorder); + attributes.forEach(metricsTracer::addAttributes); + return metricsTracer; } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerFactoryTest.java index 16e2078bc0..d5459921e5 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerFactoryTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerFactoryTest.java @@ -33,12 +33,16 @@ import static org.mockito.Mockito.when; import com.google.api.gax.tracing.ApiTracerFactory.OperationType; +import com.google.common.collect.ImmutableMap; import com.google.common.truth.Truth; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; class MetricsTracerFactoryTest { + private static final int DEFAULT_ATTRIBUTES_COUNT = 2; + @Mock private MetricsRecorder metricsRecorder; @Mock private ApiTracer parent; private SpanName spanName; @@ -60,22 +64,36 @@ void testNewTracer_notNull() { ApiTracer apiTracer = metricsTracerFactory.newTracer(parent, spanName, OperationType.Unary); // Assert that the apiTracer created has expected type and not null - Truth.assertThat(apiTracer).isInstanceOf(MetricsTracer.class); Truth.assertThat(apiTracer).isNotNull(); + Truth.assertThat(apiTracer).isInstanceOf(MetricsTracer.class); } @Test - void testNewTracer_HasCorrectParameters() { - - // Call the newTracer method - ApiTracer apiTracer = metricsTracerFactory.newTracer(parent, spanName, OperationType.Unary); + void testNewTracer_hasCorrectNumberAttributes_hasDefaultAttributes() { + MetricsTracer metricsTracer = + (MetricsTracer) metricsTracerFactory.newTracer(parent, spanName, OperationType.Unary); + Map attributes = metricsTracer.getAttributes(); + Truth.assertThat(attributes.size()).isEqualTo(DEFAULT_ATTRIBUTES_COUNT); + Truth.assertThat(attributes.get(MetricsTracer.METHOD_NAME_ATTRIBUTE)) + .isEqualTo("testService.testMethod"); + Truth.assertThat(attributes.get(MetricsTracer.LANGUAGE_ATTRIBUTE)) + .isEqualTo(MetricsTracer.DEFAULT_LANGUAGE); + } - // Assert that the apiTracer created has expected type and not null - Truth.assertThat(apiTracer).isInstanceOf(MetricsTracer.class); - Truth.assertThat(apiTracer).isNotNull(); + @Test + void testClientAttributes_additionalClientAttributes() { + Map clientAttributes = + ImmutableMap.of("attribute1", "value1", "attribute2", "value2"); + MetricsTracerFactory metricsTracerFactory = + new MetricsTracerFactory(metricsRecorder, clientAttributes); - MetricsTracer metricsTracer = (MetricsTracer) apiTracer; - Truth.assertThat(metricsTracer.getAttributes().get("method_name")) - .isEqualTo("testService.testMethod"); + MetricsTracer metricsTracer = + (MetricsTracer) metricsTracerFactory.newTracer(parent, spanName, OperationType.Unary); + Map attributes = metricsTracer.getAttributes(); + Truth.assertThat(attributes.size()) + .isEqualTo(DEFAULT_ATTRIBUTES_COUNT + clientAttributes.size()); + // Default attributes already tested above + Truth.assertThat(attributes.containsKey("attribute1")).isTrue(); + Truth.assertThat(attributes.containsKey("attribute2")).isTrue(); } } diff --git a/gax-java/pom.xml b/gax-java/pom.xml index 8737de90a3..52259b896f 100644 --- a/gax-java/pom.xml +++ b/gax-java/pom.xml @@ -189,6 +189,12 @@ mockito-junit-jupiter test + + org.junit-pioneer + junit-pioneer + 2.2.0 + test + com.google.truth truth diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java index c2ecff034d..30a9776092 100644 --- a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITOtelMetrics.java @@ -35,6 +35,7 @@ import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.core.ApiFuture; import com.google.api.gax.core.NoCredentialsProvider; +import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.InvalidArgumentException; import com.google.api.gax.rpc.StatusCode.Code; @@ -69,7 +70,9 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.data.PointData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -100,7 +103,7 @@ class ITOtelMetrics { private static final String OPERATION_COUNT = SERVICE_NAME + "/operation_count"; private static final String ATTEMPT_LATENCY = SERVICE_NAME + "/attempt_latency"; private static final String OPERATION_LATENCY = SERVICE_NAME + "/operation_latency"; - private static final int NUM_METRICS = 4; + private static final int NUM_DEFAULT_METRICS = 4; private static final int NUM_COLLECTION_FLUSH_ATTEMPTS = 10; private InMemoryMetricReader inMemoryMetricReader; private EchoClient grpcClient; @@ -272,16 +275,22 @@ private void verifyStatusAttribute( } } + /** Uses the default InMemoryMetricReader configured for showcase tests. */ + private List getMetricDataList() throws InterruptedException { + return getMetricDataList(inMemoryMetricReader); + } + /** - * Attempts to retrieve the metrics from the InMemoryMetricsReader. Sleep every second for at most - * 10s to try and retrieve all the metrics available. If it is unable to retrieve all the metrics, - * fail the test. + * Attempts to retrieve the metrics from a custom InMemoryMetricsReader. Sleep every second for at + * most 10s to try and retrieve all the metrics available. If it is unable to retrieve all the + * metrics, fail the test. */ - private List getMetricDataList() throws InterruptedException { + private List getMetricDataList(InMemoryMetricReader metricReader) + throws InterruptedException { for (int i = 0; i < NUM_COLLECTION_FLUSH_ATTEMPTS; i++) { Thread.sleep(1000L); - List metricData = new ArrayList<>(inMemoryMetricReader.collectAllMetrics()); - if (metricData.size() == NUM_METRICS) { + List metricData = new ArrayList<>(metricReader.collectAllMetrics()); + if (metricData.size() == NUM_DEFAULT_METRICS) { return metricData; } } @@ -296,19 +305,19 @@ void testGrpc_operationSucceeded_recordsMetrics() throws InterruptedException { EchoRequest.newBuilder().setContent("test_grpc_operation_succeeded").build(); grpcClient.echo(echoRequest); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "Echo.Echo", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); List statusCountList = ImmutableList.of(new StatusCount(Code.OK)); - verifyStatusAttribute(metricDataList, statusCountList); + verifyStatusAttribute(actualMetricDataList, statusCountList); } @Disabled("https://github.com/googleapis/sdk-platform-java/issues/2503") @@ -319,19 +328,19 @@ void testHttpJson_operationSucceeded_recordsMetrics() throws InterruptedExceptio EchoRequest.newBuilder().setContent("test_http_operation_succeeded").build(); httpClient.echo(echoRequest); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "google.showcase.v1beta1.Echo/Echo", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); List statusCountList = ImmutableList.of(new StatusCount(Code.OK)); - verifyStatusAttribute(metricDataList, statusCountList); + verifyStatusAttribute(actualMetricDataList, statusCountList); } @Test @@ -349,19 +358,19 @@ void testGrpc_operationCancelled_recordsMetrics() throws Exception { Thread.sleep(1000); blockResponseApiFuture.cancel(true); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "Echo.Block", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); List statusCountList = ImmutableList.of(new StatusCount(Code.CANCELLED)); - verifyStatusAttribute(metricDataList, statusCountList); + verifyStatusAttribute(actualMetricDataList, statusCountList); } @Disabled("https://github.com/googleapis/sdk-platform-java/issues/2503") @@ -377,19 +386,19 @@ void testHttpJson_operationCancelled_recordsMetrics() throws Exception { Thread.sleep(1000); blockResponseApiFuture.cancel(true); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "google.showcase.v1beta1.Echo/Block", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); List statusCountList = ImmutableList.of(new StatusCount(Code.CANCELLED)); - verifyStatusAttribute(metricDataList, statusCountList); + verifyStatusAttribute(actualMetricDataList, statusCountList); } @Test @@ -406,19 +415,19 @@ void testGrpc_operationFailed_recordsMetrics() throws InterruptedException { ApiFuture blockResponseApiFuture = blockCallable.futureCall(blockRequest); assertThrows(ExecutionException.class, blockResponseApiFuture::get); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "Echo.Block", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); List statusCountList = ImmutableList.of(new StatusCount(statusCode)); - verifyStatusAttribute(metricDataList, statusCountList); + verifyStatusAttribute(actualMetricDataList, statusCountList); } @Disabled("https://github.com/googleapis/sdk-platform-java/issues/2503") @@ -436,19 +445,19 @@ void testHttpJson_operationFailed_recordsMetrics() throws InterruptedException { ApiFuture blockResponseApiFuture = blockCallable.futureCall(blockRequest); assertThrows(ExecutionException.class, blockResponseApiFuture::get); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "google.showcase.v1beta1.Echo/Block", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); List statusCountList = ImmutableList.of(new StatusCount(statusCode)); - verifyStatusAttribute(metricDataList, statusCountList); + verifyStatusAttribute(actualMetricDataList, statusCountList); } @Test @@ -499,19 +508,19 @@ void testGrpc_attemptFailedRetriesExhausted_recordsMetrics() throws Exception { assertThrows(UnavailableException.class, () -> grpcClient.echo(echoRequest)); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "Echo.Echo", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); List statusCountList = ImmutableList.of(new StatusCount(statusCode, 3)); - verifyStatusAttribute(metricDataList, statusCountList); + verifyStatusAttribute(actualMetricDataList, statusCountList); grpcClient.close(); grpcClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); @@ -567,19 +576,19 @@ void testHttpJson_attemptFailedRetriesExhausted_recordsMetrics() throws Exceptio assertThrows(UnavailableException.class, () -> httpClient.echo(echoRequest)); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "google.showcase.v1beta1.Echo/Echo", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); List statusCountList = ImmutableList.of(new StatusCount(statusCode, 3)); - verifyStatusAttribute(metricDataList, statusCountList); + verifyStatusAttribute(actualMetricDataList, statusCountList); httpClient.close(); httpClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); @@ -597,19 +606,19 @@ void testGrpc_attemptPermanentFailure_recordsMetrics() throws InterruptedExcepti assertThrows(InvalidArgumentException.class, () -> grpcClient.block(blockRequest)); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "Echo.Block", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); List statusCountList = ImmutableList.of(new StatusCount(statusCode)); - verifyStatusAttribute(metricDataList, statusCountList); + verifyStatusAttribute(actualMetricDataList, statusCountList); } @Disabled("https://github.com/googleapis/sdk-platform-java/issues/2503") @@ -625,19 +634,19 @@ void testHttpJson_attemptPermanentFailure_recordsMetrics() throws InterruptedExc assertThrows(InvalidArgumentException.class, () -> httpClient.block(blockRequest)); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "google.showcase.v1beta1.Echo/Block", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); List statusCountList = ImmutableList.of(new StatusCount(statusCode)); - verifyStatusAttribute(metricDataList, statusCountList); + verifyStatusAttribute(actualMetricDataList, statusCountList); } @Test @@ -694,20 +703,20 @@ void testGrpc_multipleFailedAttempts_successfulOperation() throws Exception { grpcClient.block(blockRequest); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "Echo.Block", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); List statusCountList = ImmutableList.of(new StatusCount(Code.DEADLINE_EXCEEDED, 2), new StatusCount(Code.OK)); - verifyStatusAttribute(metricDataList, statusCountList); + verifyStatusAttribute(actualMetricDataList, statusCountList); grpcClient.close(); grpcClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); @@ -764,18 +773,80 @@ void testHttpJson_multipleFailedAttempts_successfulOperation() throws Exception grpcClient.block(blockRequest); - List metricDataList = getMetricDataList(); - verifyPointDataSum(metricDataList, attemptCount); + List actualMetricDataList = getMetricDataList(); + verifyPointDataSum(actualMetricDataList, attemptCount); - Map attributeMapping = + Map expectedAttributes = ImmutableMap.of( MetricsTracer.METHOD_NAME_ATTRIBUTE, "google.showcase.v1beta1.Echo/Block", MetricsTracer.LANGUAGE_ATTRIBUTE, MetricsTracer.DEFAULT_LANGUAGE); - verifyDefaultMetricsAttributes(metricDataList, attributeMapping); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); httpClient.close(); httpClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); } + + @Test + void recordsCustomAttributes() throws InterruptedException, IOException { + InstantiatingGrpcChannelProvider channelProvider = + EchoSettings.defaultGrpcTransportProviderBuilder() + .setChannelConfigurator(ManagedChannelBuilder::usePlaintext) + .build(); + + // Add custom attributes to be added as client level attributes + Map customAttributes = new HashMap<>(); + String directpathEnabled = "directpath_enabled"; + customAttributes.put(directpathEnabled, String.valueOf(channelProvider.canUseDirectPath())); + String randomAttributeKey1 = "testing"; + String randomAttributeValue1 = "showcase"; + String randomAttributeKey2 = "hello"; + String randomAttributeValue2 = "world"; + customAttributes.put(randomAttributeKey1, randomAttributeValue1); + customAttributes.put(randomAttributeKey2, randomAttributeValue2); + + InMemoryMetricReader inMemoryMetricReader = InMemoryMetricReader.create(); + OpenTelemetryMetricsRecorder otelMetricsRecorder = + createOtelMetricsRecorder(inMemoryMetricReader); + MetricsTracerFactory metricsTracerFactory = + new MetricsTracerFactory(otelMetricsRecorder, customAttributes); + + EchoSettings grpcEchoSettings = + EchoSettings.newBuilder() + .setCredentialsProvider(NoCredentialsProvider.create()) + .setTransportChannelProvider(channelProvider) + .setEndpoint(TestClientInitializer.DEFAULT_GRPC_ENDPOINT) + .build(); + + EchoStubSettings echoStubSettings = + (EchoStubSettings) + grpcEchoSettings + .getStubSettings() + .toBuilder() + .setTracerFactory(metricsTracerFactory) + .build(); + EchoStub stub = echoStubSettings.createStub(); + EchoClient grpcClient = EchoClient.create(stub); + + EchoRequest echoRequest = EchoRequest.newBuilder().setContent("content").build(); + grpcClient.echo(echoRequest); + + List actualMetricDataList = getMetricDataList(inMemoryMetricReader); + Map expectedAttributes = + ImmutableMap.of( + MetricsTracer.METHOD_NAME_ATTRIBUTE, + "Echo.Echo", + MetricsTracer.LANGUAGE_ATTRIBUTE, + MetricsTracer.DEFAULT_LANGUAGE, + directpathEnabled, + "false", + randomAttributeKey1, + randomAttributeValue1, + randomAttributeKey2, + randomAttributeValue2); + verifyDefaultMetricsAttributes(actualMetricDataList, expectedAttributes); + + inMemoryMetricReader.close(); + } }