diff --git a/google-cloud-bigtable/clirr-ignored-differences.xml b/google-cloud-bigtable/clirr-ignored-differences.xml index e8e7f3ab0a..7bc32ca2be 100644 --- a/google-cloud-bigtable/clirr-ignored-differences.xml +++ b/google-cloud-bigtable/clirr-ignored-differences.xml @@ -186,4 +186,10 @@ com/google/cloud/bigtable/data/v2/models/MutateRowsException * + + + 7004 + com/google/cloud/bigtable/data/v2/stub/metrics/ErrorCountPerConnectionMetricTracker + * + diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactory.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactory.java index e0b5ee398c..45ec5af814 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactory.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactory.java @@ -19,6 +19,7 @@ import com.google.api.gax.core.BackgroundResource; import com.google.api.gax.rpc.ClientContext; import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub; +import io.opentelemetry.api.OpenTelemetry; import java.io.IOException; import javax.annotation.Nonnull; @@ -64,6 +65,7 @@ public final class BigtableDataClientFactory implements AutoCloseable { private final BigtableDataSettings defaultSettings; private final ClientContext sharedClientContext; + private final OpenTelemetry openTelemetry; /** * Create a instance of this factory. @@ -75,13 +77,21 @@ public static BigtableDataClientFactory create(BigtableDataSettings defaultSetti throws IOException { ClientContext sharedClientContext = EnhancedBigtableStub.createClientContext(defaultSettings.getStubSettings()); - return new BigtableDataClientFactory(sharedClientContext, defaultSettings); + OpenTelemetry openTelemetry = + EnhancedBigtableStub.getOpenTelemetry( + defaultSettings.getProjectId(), + defaultSettings.getMetricsProvider(), + sharedClientContext.getCredentials()); + return new BigtableDataClientFactory(sharedClientContext, defaultSettings, openTelemetry); } private BigtableDataClientFactory( - ClientContext sharedClientContext, BigtableDataSettings defaultSettings) { + ClientContext sharedClientContext, + BigtableDataSettings defaultSettings, + OpenTelemetry openTelemetry) { this.sharedClientContext = sharedClientContext; this.defaultSettings = defaultSettings; + this.openTelemetry = openTelemetry; } /** @@ -112,7 +122,7 @@ public BigtableDataClient createDefault() { .toBuilder() .setTracerFactory( EnhancedBigtableStub.createBigtableTracerFactory( - defaultSettings.getStubSettings(), sharedClientContext)) + defaultSettings.getStubSettings(), openTelemetry)) .build(); return BigtableDataClient.createWithClientContext(defaultSettings, clientContext); @@ -141,7 +151,7 @@ public BigtableDataClient createForAppProfile(@Nonnull String appProfileId) thro .toBuilder() .setTracerFactory( EnhancedBigtableStub.createBigtableTracerFactory( - settings.getStubSettings(), sharedClientContext)) + settings.getStubSettings(), openTelemetry)) .build(); return BigtableDataClient.createWithClientContext(settings, clientContext); } @@ -170,7 +180,7 @@ public BigtableDataClient createForInstance(@Nonnull String projectId, @Nonnull .toBuilder() .setTracerFactory( EnhancedBigtableStub.createBigtableTracerFactory( - settings.getStubSettings(), sharedClientContext)) + settings.getStubSettings(), openTelemetry)) .build(); return BigtableDataClient.createWithClientContext(settings, clientContext); @@ -200,7 +210,7 @@ public BigtableDataClient createForInstance( .toBuilder() .setTracerFactory( EnhancedBigtableStub.createBigtableTracerFactory( - settings.getStubSettings(), sharedClientContext)) + settings.getStubSettings(), openTelemetry)) .build(); return BigtableDataClient.createWithClientContext(settings, clientContext); } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java index b39a9e7933..172152b318 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java @@ -16,6 +16,7 @@ package com.google.cloud.bigtable.data.v2.stub; import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsConstants.APP_PROFILE_KEY; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsConstants.CLIENT_NAME_KEY; import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsConstants.INSTANCE_ID_KEY; import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsConstants.PROJECT_ID_KEY; @@ -99,12 +100,11 @@ import com.google.cloud.bigtable.data.v2.stub.metrics.BigtableTracerStreamingCallable; import com.google.cloud.bigtable.data.v2.stub.metrics.BigtableTracerUnaryCallable; import com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsTracerFactory; -import com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsView; import com.google.cloud.bigtable.data.v2.stub.metrics.CompositeTracerFactory; import com.google.cloud.bigtable.data.v2.stub.metrics.CustomOpenTelemetryMetricsProvider; import com.google.cloud.bigtable.data.v2.stub.metrics.DefaultMetricsProvider; -import com.google.cloud.bigtable.data.v2.stub.metrics.MetricsProvider; import com.google.cloud.bigtable.data.v2.stub.metrics.ErrorCountPerConnectionMetricTracker; +import com.google.cloud.bigtable.data.v2.stub.metrics.MetricsProvider; import com.google.cloud.bigtable.data.v2.stub.metrics.MetricsTracerFactory; import com.google.cloud.bigtable.data.v2.stub.metrics.NoopMetricsProvider; import com.google.cloud.bigtable.data.v2.stub.metrics.RpcMeasureConstants; @@ -138,9 +138,6 @@ import io.opencensus.tags.Tags; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.metrics.SdkMeterProvider; -import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -194,10 +191,13 @@ public class EnhancedBigtableStub implements AutoCloseable { public static EnhancedBigtableStub create(EnhancedBigtableStubSettings settings) throws IOException { ClientContext clientContext = createClientContext(settings); + OpenTelemetry openTelemetry = + getOpenTelemetry( + settings.getProjectId(), settings.getMetricsProvider(), clientContext.getCredentials()); ClientContext contextWithTracer = clientContext .toBuilder() - .setTracerFactory(createBigtableTracerFactory(settings, clientContext)) + .setTracerFactory(createBigtableTracerFactory(settings, openTelemetry)) .build(); return new EnhancedBigtableStub(settings, contextWithTracer); @@ -219,15 +219,26 @@ public static ClientContext createClientContext(EnhancedBigtableStubSettings set // workaround JWT audience issues patchCredentials(builder); + // Fix the credentials so that they can be shared + Credentials credentials = null; + if (builder.getCredentialsProvider() != null) { + credentials = builder.getCredentialsProvider().getCredentials(); + } + builder.setCredentialsProvider(FixedCredentialsProvider.create(credentials)); + InstantiatingGrpcChannelProvider.Builder transportProvider = builder.getTransportChannelProvider() instanceof InstantiatingGrpcChannelProvider ? ((InstantiatingGrpcChannelProvider) builder.getTransportChannelProvider()).toBuilder() : null; + OpenTelemetry openTelemetry = + getOpenTelemetry(settings.getProjectId(), settings.getMetricsProvider(), credentials); ErrorCountPerConnectionMetricTracker errorCountPerConnectionMetricTracker; - if (transportProvider != null) { + // Skip setting up ErrorCountPerConnectionMetricTracker if openTelemetry is null + if (openTelemetry != null && transportProvider != null) { errorCountPerConnectionMetricTracker = - new ErrorCountPerConnectionMetricTracker(createBuiltinAttributes(builder)); + new ErrorCountPerConnectionMetricTracker( + openTelemetry, createBuiltinAttributes(settings)); ApiFunction oldChannelConfigurator = transportProvider.getChannelConfigurator(); transportProvider.setChannelConfigurator( @@ -249,12 +260,6 @@ public static ClientContext createClientContext(EnhancedBigtableStubSettings set // Inject channel priming if (settings.isRefreshingChannel()) { - // Fix the credentials so that they can be shared - Credentials credentials = null; - if (builder.getCredentialsProvider() != null) { - credentials = builder.getCredentialsProvider().getCredentials(); - } - builder.setCredentialsProvider(FixedCredentialsProvider.create(credentials)); if (transportProvider != null) { transportProvider.setChannelPrimer( @@ -279,9 +284,9 @@ public static ClientContext createClientContext(EnhancedBigtableStubSettings set } public static ApiTracerFactory createBigtableTracerFactory( - EnhancedBigtableStubSettings settings, ClientContext clientContext) throws IOException { + EnhancedBigtableStubSettings settings, OpenTelemetry openTelemetry) throws IOException { return createBigtableTracerFactory( - settings, Tags.getTagger(), Stats.getStatsRecorder(), clientContext); + settings, Tags.getTagger(), Stats.getStatsRecorder(), openTelemetry); } @VisibleForTesting @@ -289,7 +294,7 @@ public static ApiTracerFactory createBigtableTracerFactory( EnhancedBigtableStubSettings settings, Tagger tagger, StatsRecorder stats, - ClientContext clientContext) + OpenTelemetry openTelemetry) throws IOException { String projectId = settings.getProjectId(); String instanceId = settings.getInstanceId(); @@ -302,8 +307,6 @@ public static ApiTracerFactory createBigtableTracerFactory( .put(RpcMeasureConstants.BIGTABLE_APP_PROFILE_ID, TagValue.create(appProfileId)) .build(); - ImmutableMap builtinAttributes = createBuiltinAttributes(settings.toBuilder()); - ImmutableList.Builder tracerFactories = ImmutableList.builder(); tracerFactories .add( @@ -323,53 +326,47 @@ public static ApiTracerFactory createBigtableTracerFactory( .add(MetricsTracerFactory.create(tagger, stats, attributes)) // Add user configured tracer .add(settings.getTracerFactory()); - Attributes otelAttributes = - Attributes.of( - PROJECT_ID_KEY, projectId, INSTANCE_ID_KEY, instanceId, APP_PROFILE_KEY, appProfileId); BuiltinMetricsTracerFactory builtinMetricsTracerFactory = - createBuiltinMetricsTracerFactory( - projectId, settings.getMetricsProvider(), otelAttributes, clientContext); + openTelemetry != null + ? BuiltinMetricsTracerFactory.create(openTelemetry, createBuiltinAttributes(settings)) + : null; if (builtinMetricsTracerFactory != null) { tracerFactories.add(builtinMetricsTracerFactory); } return new CompositeTracerFactory(tracerFactories.build()); } - private static BuiltinMetricsTracerFactory createBuiltinMetricsTracerFactory( - String projectId, - MetricsProvider metricsProvider, - Attributes attributes, - ClientContext clientContext) + @Nullable + public static OpenTelemetry getOpenTelemetry( + String projectId, MetricsProvider metricsProvider, @Nullable Credentials defaultCredentials) throws IOException { if (metricsProvider instanceof CustomOpenTelemetryMetricsProvider) { CustomOpenTelemetryMetricsProvider customMetricsProvider = (CustomOpenTelemetryMetricsProvider) metricsProvider; - return BuiltinMetricsTracerFactory.create( - customMetricsProvider.getOpenTelemetry(), attributes); + return customMetricsProvider.getOpenTelemetry(); } else if (metricsProvider instanceof DefaultMetricsProvider) { - SdkMeterProviderBuilder meterProvider = SdkMeterProvider.builder(); Credentials credentials = BigtableDataSettings.getMetricsCredentials() != null ? BigtableDataSettings.getMetricsCredentials() - : clientContext.getCredentials(); - BuiltinMetricsView.registerBuiltinMetrics(projectId, credentials, meterProvider); - OpenTelemetry openTelemetry = - OpenTelemetrySdk.builder().setMeterProvider(meterProvider.build()).build(); - return BuiltinMetricsTracerFactory.create(openTelemetry, attributes); + : defaultCredentials; + DefaultMetricsProvider defaultMetricsProvider = (DefaultMetricsProvider) metricsProvider; + return defaultMetricsProvider.getOpenTelemetry(projectId, credentials); } else if (metricsProvider instanceof NoopMetricsProvider) { return null; } throw new IOException("Invalid MetricsProvider type " + metricsProvider); } - private static ImmutableMap createBuiltinAttributes( - EnhancedBigtableStubSettings.Builder builder) { - return ImmutableMap.builder() - .put("project_id", builder.getProjectId()) - .put("instance", builder.getInstanceId()) - .put("app_profile", builder.getAppProfileId()) - .put("client_name", "bigtable-java/" + Version.VERSION) - .build(); + private static Attributes createBuiltinAttributes(EnhancedBigtableStubSettings settings) { + return Attributes.of( + PROJECT_ID_KEY, + settings.getProjectId(), + INSTANCE_ID_KEY, + settings.getInstanceId(), + APP_PROFILE_KEY, + settings.getAppProfileId(), + CLIENT_NAME_KEY, + "bigtable-java/" + Version.VERSION); } private static void patchCredentials(EnhancedBigtableStubSettings.Builder settings) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java index 019fe88a50..72d4432c44 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableCloudMonitoringExporter.java @@ -20,7 +20,9 @@ import com.google.api.core.ApiFutureCallback; import com.google.api.core.ApiFutures; import com.google.api.core.InternalApi; +import com.google.api.gax.core.CredentialsProvider; import com.google.api.gax.core.FixedCredentialsProvider; +import com.google.api.gax.core.NoCredentialsProvider; import com.google.auth.Credentials; import com.google.cloud.monitoring.v3.MetricServiceClient; import com.google.cloud.monitoring.v3.MetricServiceSettings; @@ -43,6 +45,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nullable; import org.threeten.bp.Duration; /** @@ -75,10 +78,16 @@ public final class BigtableCloudMonitoringExporter implements MetricExporter { private CompletableResultCode lastExportCode; - public static BigtableCloudMonitoringExporter create(String projectId, Credentials credentials) - throws IOException { + public static BigtableCloudMonitoringExporter create( + String projectId, @Nullable Credentials credentials) throws IOException { MetricServiceSettings.Builder settingsBuilder = MetricServiceSettings.newBuilder(); - settingsBuilder.setCredentialsProvider(FixedCredentialsProvider.create(credentials)); + CredentialsProvider credentialsProvider; + if (credentials == null) { + credentialsProvider = NoCredentialsProvider.create(); + } else { + credentialsProvider = FixedCredentialsProvider.create(credentials); + } + settingsBuilder.setCredentialsProvider(credentialsProvider); settingsBuilder.setEndpoint(MONITORING_ENDPOINT); org.threeten.bp.Duration timeout = Duration.ofMinutes(1); diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsConstants.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsConstants.java index 807191b7e2..b26557999a 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsConstants.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsConstants.java @@ -44,9 +44,9 @@ public class BuiltinMetricsConstants { // IT tests, so they're public. public static final AttributeKey APP_PROFILE_KEY = AttributeKey.stringKey("app_profile"); public static final AttributeKey STREAMING_KEY = AttributeKey.booleanKey("streaming"); + public static final AttributeKey CLIENT_NAME_KEY = AttributeKey.stringKey("client_name"); static final AttributeKey METHOD_KEY = AttributeKey.stringKey("method"); static final AttributeKey STATUS_KEY = AttributeKey.stringKey("status"); - static final AttributeKey CLIENT_NAME_KEY = AttributeKey.stringKey("client_name"); static final AttributeKey CLIENT_UID_KEY = AttributeKey.stringKey("client_uid"); // Metric names @@ -58,6 +58,7 @@ public class BuiltinMetricsConstants { static final String FIRST_RESPONSE_LATENCIES_NAME = "first_response_latencies"; static final String APPLICATION_BLOCKING_LATENCIES_NAME = "application_latencies"; static final String CLIENT_BLOCKING_LATENCIES_NAME = "throttling_latencies"; + static final String PER_CONNECTION_ERROR_COUNT_NAME = "per_connection_error_count"; // Buckets under 100,000 are identical to buckets for server side metrics handler_latencies. // Extending client side bucket to up to 3,200,000. @@ -69,6 +70,31 @@ public class BuiltinMetricsConstants { 800.0, 1000.0, 2000.0, 5000.0, 10000.0, 20000.0, 50000.0, 100000.0, 200000.0, 400000.0, 800000.0, 1600000.0, 3200000.0)); // max is 53.3 minutes + private static final Aggregation AGGREGATION_PER_CONNECTION_ERROR_COUNT_HISTOGRAM = + Aggregation.explicitBucketHistogram( + ImmutableList.of( + 1.0, + 2.0, + 4.0, + 8.0, + 16.0, + 32.0, + 64.0, + 125.0, + 250.0, + 500.0, + 1_000.0, + 2_000.0, + 4_000.0, + 8_000.0, + 16_000.0, + 32_000.0, + 64_000.0, + 128_000.0, + 250_000.0, + 500_000.0, + 1_000_000.0)); + public static final String METER_NAME = "bigtable.googleapis.com/internal/client/"; static final Set COMMON_ATTRIBUTES = @@ -88,7 +114,7 @@ static void defineView( Aggregation aggregation, InstrumentType type, String unit, - Set extraAttributes) { + Set attributes) { InstrumentSelector selector = InstrumentSelector.builder() .setName(id) @@ -100,7 +126,7 @@ static void defineView( ImmutableSet.builder() .addAll( COMMON_ATTRIBUTES.stream().map(AttributeKey::getKey).collect(Collectors.toSet())) - .addAll(extraAttributes.stream().map(AttributeKey::getKey).collect(Collectors.toSet())) + .addAll(attributes.stream().map(AttributeKey::getKey).collect(Collectors.toSet())) .build(); View view = View.builder() @@ -121,56 +147,72 @@ public static Map getAllViews() { AGGREGATION_WITH_MILLIS_HISTOGRAM, InstrumentType.HISTOGRAM, "ms", - ImmutableSet.of(STREAMING_KEY, STATUS_KEY)); + ImmutableSet.builder() + .addAll(COMMON_ATTRIBUTES) + .add(STREAMING_KEY, STATUS_KEY) + .build()); defineView( views, ATTEMPT_LATENCIES_NAME, AGGREGATION_WITH_MILLIS_HISTOGRAM, InstrumentType.HISTOGRAM, "ms", - ImmutableSet.of(STREAMING_KEY, STATUS_KEY)); + ImmutableSet.builder() + .addAll(COMMON_ATTRIBUTES) + .add(STREAMING_KEY, STATUS_KEY) + .build()); defineView( views, SERVER_LATENCIES_NAME, AGGREGATION_WITH_MILLIS_HISTOGRAM, InstrumentType.HISTOGRAM, "ms", - ImmutableSet.of(STATUS_KEY)); + ImmutableSet.builder().addAll(COMMON_ATTRIBUTES).add(STATUS_KEY).build()); defineView( views, FIRST_RESPONSE_LATENCIES_NAME, AGGREGATION_WITH_MILLIS_HISTOGRAM, InstrumentType.HISTOGRAM, "ms", - ImmutableSet.of(STATUS_KEY)); + ImmutableSet.builder().addAll(COMMON_ATTRIBUTES).add(STATUS_KEY).build()); defineView( views, APPLICATION_BLOCKING_LATENCIES_NAME, AGGREGATION_WITH_MILLIS_HISTOGRAM, InstrumentType.HISTOGRAM, "ms", - ImmutableSet.of()); + ImmutableSet.builder().addAll(COMMON_ATTRIBUTES).build()); defineView( views, CLIENT_BLOCKING_LATENCIES_NAME, AGGREGATION_WITH_MILLIS_HISTOGRAM, InstrumentType.HISTOGRAM, "ms", - ImmutableSet.of()); + ImmutableSet.builder().addAll(COMMON_ATTRIBUTES).build()); defineView( views, RETRY_COUNT_NAME, Aggregation.sum(), InstrumentType.COUNTER, "1", - ImmutableSet.of(STATUS_KEY)); + ImmutableSet.builder().addAll(COMMON_ATTRIBUTES).add(STATUS_KEY).build()); defineView( views, CONNECTIVITY_ERROR_COUNT_NAME, Aggregation.sum(), InstrumentType.COUNTER, "1", - ImmutableSet.of(STATUS_KEY)); + ImmutableSet.builder().addAll(COMMON_ATTRIBUTES).add(STATUS_KEY).build()); + + defineView( + views, + PER_CONNECTION_ERROR_COUNT_NAME, + AGGREGATION_PER_CONNECTION_ERROR_COUNT_HISTOGRAM, + InstrumentType.HISTOGRAM, + "1", + ImmutableSet.builder() + .add(PROJECT_ID_KEY, INSTANCE_ID_KEY, APP_PROFILE_KEY, CLIENT_NAME_KEY) + .build()); return views.build(); } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsView.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsView.java index 8c7c552841..445160a146 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsView.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsView.java @@ -24,6 +24,7 @@ import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; import java.io.IOException; import java.util.Map; +import javax.annotation.Nullable; /** * A util class to register built-in metrics on a custom OpenTelemetry instance. This is for @@ -46,7 +47,7 @@ public static void registerBuiltinMetrics(String projectId, SdkMeterProviderBuil /** Register built-in metrics on the {@link SdkMeterProviderBuilder} with credentials. */ public static void registerBuiltinMetrics( - String projectId, Credentials credentials, SdkMeterProviderBuilder builder) + String projectId, @Nullable Credentials credentials, SdkMeterProviderBuilder builder) throws IOException { MetricExporter metricExporter = BigtableCloudMonitoringExporter.create(projectId, credentials); for (Map.Entry entry : diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CustomOpenTelemetryMetricsProvider.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CustomOpenTelemetryMetricsProvider.java index bcd1fe488f..ceade9b2c0 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CustomOpenTelemetryMetricsProvider.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/CustomOpenTelemetryMetricsProvider.java @@ -25,7 +25,7 @@ *
{@code
  * SdkMeterProviderBuilder sdkMeterProvider = SdkMeterProvider.builder();
  *
- * // register Builtin metrics on your meter provider
+ * // register Builtin metrics on your meter provider with default credentials
  * BuiltinMetricsViews.registerBuiltinMetrics("project-id", sdkMeterProvider);
  *
  * // register other metrics reader and views
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DefaultMetricsProvider.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DefaultMetricsProvider.java
index 0f3ee0c98f..68e37a8f3c 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DefaultMetricsProvider.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DefaultMetricsProvider.java
@@ -15,6 +15,14 @@
  */
 package com.google.cloud.bigtable.data.v2.stub.metrics;
 
+import com.google.auth.Credentials;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
+import java.io.IOException;
+import javax.annotation.Nullable;
+
 /**
  * Set {@link
  * com.google.cloud.bigtable.data.v2.BigtableDataSettings.Builder#setMetricsProvider(MetricsProvider)},
@@ -26,8 +34,20 @@ public final class DefaultMetricsProvider implements MetricsProvider {
 
   public static DefaultMetricsProvider INSTANCE = new DefaultMetricsProvider();
 
+  private OpenTelemetry openTelemetry;
+
   private DefaultMetricsProvider() {}
 
+  public OpenTelemetry getOpenTelemetry(String projectId, @Nullable Credentials credentials)
+      throws IOException {
+    if (openTelemetry == null) {
+      SdkMeterProviderBuilder meterProvider = SdkMeterProvider.builder();
+      BuiltinMetricsView.registerBuiltinMetrics(projectId, credentials, meterProvider);
+      openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(meterProvider.build()).build();
+    }
+    return openTelemetry;
+  }
+
   @Override
   public String toString() {
     return "DefaultMetricsProvider";
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/ErrorCountPerConnectionMetricTracker.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/ErrorCountPerConnectionMetricTracker.java
index cab3b0bbd0..df37ee3e9d 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/ErrorCountPerConnectionMetricTracker.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/ErrorCountPerConnectionMetricTracker.java
@@ -15,12 +15,15 @@
  */
 package com.google.cloud.bigtable.data.v2.stub.metrics;
 
+import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsConstants.METER_NAME;
+import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsConstants.PER_CONNECTION_ERROR_COUNT_NAME;
+
 import com.google.api.core.InternalApi;
-import com.google.cloud.bigtable.stats.StatsRecorderWrapperForConnection;
-import com.google.cloud.bigtable.stats.StatsWrapper;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableMap;
 import io.grpc.ClientInterceptor;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongHistogram;
+import io.opentelemetry.api.metrics.Meter;
 import java.util.Collections;
 import java.util.Set;
 import java.util.WeakHashMap;
@@ -31,23 +34,28 @@
 @InternalApi("For internal use only")
 public class ErrorCountPerConnectionMetricTracker implements Runnable {
   private static final Integer PER_CONNECTION_ERROR_COUNT_PERIOD_SECONDS = 60;
+
+  private final LongHistogram perConnectionErrorCountHistogram;
+  private final Attributes attributes;
+
   private final Set connectionErrorCountInterceptors;
   private final Object interceptorsLock = new Object();
-  // This is not final so that it can be updated and mocked during testing.
-  private StatsRecorderWrapperForConnection statsRecorderWrapperForConnection;
 
-  @VisibleForTesting
-  void setStatsRecorderWrapperForConnection(
-      StatsRecorderWrapperForConnection statsRecorderWrapperForConnection) {
-    this.statsRecorderWrapperForConnection = statsRecorderWrapperForConnection;
-  }
-
-  public ErrorCountPerConnectionMetricTracker(ImmutableMap builtinAttributes) {
+  public ErrorCountPerConnectionMetricTracker(OpenTelemetry openTelemetry, Attributes attributes) {
     connectionErrorCountInterceptors =
         Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
 
-    this.statsRecorderWrapperForConnection =
-        StatsWrapper.createRecorderForConnection(builtinAttributes);
+    Meter meter = openTelemetry.getMeter(METER_NAME);
+
+    perConnectionErrorCountHistogram =
+        meter
+            .histogramBuilder(PER_CONNECTION_ERROR_COUNT_NAME)
+            .ofLongs()
+            .setDescription("Distribution of counts of channels per 'error count per minute'.")
+            .setUnit("1")
+            .build();
+
+    this.attributes = attributes;
   }
 
   public void startConnectionErrorCountTracker(ScheduledExecutorService scheduler) {
@@ -75,7 +83,7 @@ public void run() {
         if (errors > 0 || successes > 0) {
           // TODO: add a metric to also keep track of the number of successful requests per each
           // connection.
-          statsRecorderWrapperForConnection.putAndRecordPerConnectionErrorCount(errors);
+          perConnectionErrorCountHistogram.record(errors, attributes);
         }
       }
     }
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java
index 9173e6f6af..fea66e82bf 100644
--- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java
@@ -320,7 +320,7 @@ public void testFeatureFlags() throws Exception {
   @Test
   public void testBulkMutationFlowControllerConfigured() throws Exception {
     BigtableDataSettings settings =
-        BigtableDataSettings.newBuilder()
+        BigtableDataSettings.newBuilderForEmulator(server.getPort())
             .setProjectId("my-project")
             .setInstanceId("my-instance")
             .setCredentialsProvider(credentialsProvider)
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerCallableTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerCallableTest.java
index 14f4c698b2..3e1d59b133 100644
--- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerCallableTest.java
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableTracerCallableTest.java
@@ -135,7 +135,7 @@ public void sendHeaders(Metadata headers) {
                     settings.getStubSettings(),
                     Tags.getTagger(),
                     localStats.getStatsRecorder(),
-                    clientContext))
+                    null))
             .build();
     attempts = settings.getStubSettings().readRowsSettings().getRetrySettings().getMaxAttempts();
     stub = new EnhancedBigtableStub(settings.getStubSettings(), clientContext);
@@ -161,7 +161,7 @@ public void sendHeaders(Metadata headers) {
                     noHeaderSettings.getStubSettings(),
                     Tags.getTagger(),
                     localStats.getStatsRecorder(),
-                    noHeaderClientContext))
+                    null))
             .build();
     noHeaderStub =
         new EnhancedBigtableStub(noHeaderSettings.getStubSettings(), noHeaderClientContext);
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/ErrorCountPerConnectionTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/ErrorCountPerConnectionTest.java
index a6670182b8..dec5120b1c 100644
--- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/ErrorCountPerConnectionTest.java
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/ErrorCountPerConnectionTest.java
@@ -23,17 +23,29 @@
 import com.google.api.gax.grpc.ChannelPoolSettings;
 import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
 import com.google.bigtable.v2.*;
+import com.google.cloud.bigtable.Version;
 import com.google.cloud.bigtable.data.v2.BigtableDataSettings;
 import com.google.cloud.bigtable.data.v2.FakeServiceBuilder;
 import com.google.cloud.bigtable.data.v2.models.*;
 import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub;
 import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings;
-import com.google.cloud.bigtable.stats.StatsRecorderWrapperForConnection;
 import io.grpc.Server;
 import io.grpc.Status;
 import io.grpc.StatusRuntimeException;
 import io.grpc.stub.StreamObserver;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.metrics.InstrumentSelector;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
+import io.opentelemetry.sdk.metrics.View;
+import io.opentelemetry.sdk.metrics.data.HistogramPointData;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ScheduledExecutorService;
 import org.junit.After;
 import org.junit.Before;
@@ -51,25 +63,50 @@ public class ErrorCountPerConnectionTest {
   private final FakeService fakeService = new FakeService();
   private EnhancedBigtableStubSettings.Builder builder;
   private ArgumentCaptor runnableCaptor;
-  private StatsRecorderWrapperForConnection statsRecorderWrapperForConnection;
+
+  private InMemoryMetricReader metricReader;
+
+  private Attributes attributes;
 
   @Before
   public void setup() throws Exception {
     server = FakeServiceBuilder.create(fakeService).start();
 
     ScheduledExecutorService executors = Mockito.mock(ScheduledExecutorService.class);
+
+    attributes =
+        Attributes.builder()
+            .put(BuiltinMetricsConstants.PROJECT_ID_KEY, "fake-project")
+            .put(BuiltinMetricsConstants.INSTANCE_ID_KEY, "fake-instance")
+            .put(BuiltinMetricsConstants.APP_PROFILE_KEY, "")
+            .put(BuiltinMetricsConstants.CLIENT_NAME_KEY, "bigtable-java/" + Version.VERSION)
+            .build();
+
+    metricReader = InMemoryMetricReader.create();
+
+    SdkMeterProviderBuilder meterProvider =
+        SdkMeterProvider.builder().registerMetricReader(metricReader);
+
+    for (Map.Entry entry :
+        BuiltinMetricsConstants.getAllViews().entrySet()) {
+      meterProvider.registerView(entry.getKey(), entry.getValue());
+    }
+
+    OpenTelemetrySdk otel =
+        OpenTelemetrySdk.builder().setMeterProvider(meterProvider.build()).build();
+
     builder =
         BigtableDataSettings.newBuilderForEmulator(server.getPort())
             .stubSettings()
             .setBackgroundExecutorProvider(FixedExecutorProvider.create(executors))
             .setProjectId("fake-project")
-            .setInstanceId("fake-instance");
+            .setInstanceId("fake-instance")
+            .setMetricsProvider(CustomOpenTelemetryMetricsProvider.create(otel));
+
     runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
     Mockito.when(
             executors.scheduleAtFixedRate(runnableCaptor.capture(), anyLong(), anyLong(), any()))
         .thenReturn(null);
-
-    statsRecorderWrapperForConnection = Mockito.mock(StatsRecorderWrapperForConnection.class);
   }
 
   @After
@@ -98,14 +135,21 @@ public void readWithOneChannel() throws Exception {
         // noop
       }
     }
-    ArgumentCaptor errorCountCaptor = ArgumentCaptor.forClass(long.class);
-    Mockito.doNothing()
-        .when(statsRecorderWrapperForConnection)
-        .putAndRecordPerConnectionErrorCount(errorCountCaptor.capture());
+
     runInterceptorTasksAndAssertCount();
-    List allErrorCounts = errorCountCaptor.getAllValues();
-    assertThat(allErrorCounts.size()).isEqualTo(1);
-    assertThat(allErrorCounts.get(0)).isEqualTo(errorCount);
+
+    Collection allMetrics = metricReader.collectAllMetrics();
+    MetricData metricData =
+        BuiltinMetricsTestUtils.getMetricData(
+            allMetrics, BuiltinMetricsConstants.PER_CONNECTION_ERROR_COUNT_NAME);
+
+    // Make sure the correct bucket is updated with the correct number of data points
+    ArrayList histogramPointData =
+        new ArrayList<>(metricData.getHistogramData().getPoints());
+    assertThat(histogramPointData.size()).isEqualTo(1);
+    HistogramPointData point = histogramPointData.get(0);
+    int index = findDataPointIndex(point.getBoundaries(), errorCount);
+    assertThat(point.getCounts().get(index)).isEqualTo(1);
   }
 
   @Test
@@ -131,28 +175,35 @@ public void readWithTwoChannels() throws Exception {
         // noop
       }
     }
-    ArgumentCaptor errorCountCaptor = ArgumentCaptor.forClass(long.class);
-    Mockito.doNothing()
-        .when(statsRecorderWrapperForConnection)
-        .putAndRecordPerConnectionErrorCount(errorCountCaptor.capture());
     runInterceptorTasksAndAssertCount();
 
-    List allErrorCounts = errorCountCaptor.getAllValues();
-    assertThat(allErrorCounts.size()).isEqualTo(2);
-    // Requests get assigned to channels using a Round Robin algorithm, so half to each.
-    assertThat(allErrorCounts).containsExactly(totalErrorCount / 2, totalErrorCount / 2);
+    long errorCountPerChannel = totalErrorCount / 2;
+
+    Collection allMetrics = metricReader.collectAllMetrics();
+    MetricData metricData =
+        BuiltinMetricsTestUtils.getMetricData(
+            allMetrics, BuiltinMetricsConstants.PER_CONNECTION_ERROR_COUNT_NAME);
+
+    // The 2 channels should get equal amount of errors, so the totalErrorCount / 2 bucket is
+    // updated twice.
+    ArrayList histogramPointData =
+        new ArrayList<>(metricData.getHistogramData().getPoints());
+    assertThat(histogramPointData.size()).isEqualTo(1);
+    HistogramPointData point = histogramPointData.get(0);
+    int index = findDataPointIndex(point.getBoundaries(), errorCountPerChannel);
+    assertThat(point.getCounts().get(index)).isEqualTo(2);
   }
 
   @Test
   public void readOverTwoPeriods() throws Exception {
     EnhancedBigtableStub stub = EnhancedBigtableStub.create(builder.build());
-    long errorCount = 0;
+    long errorCount1 = 0;
 
     for (int i = 0; i < 20; i++) {
       Query query;
       if (i % 3 == 0) {
         query = Query.create(ERROR_TABLE_NAME);
-        errorCount += 1;
+        errorCount1 += 1;
       } else {
         query = Query.create(SUCCESS_TABLE_NAME);
       }
@@ -162,16 +213,9 @@ public void readOverTwoPeriods() throws Exception {
         // noop
       }
     }
-    ArgumentCaptor errorCountCaptor = ArgumentCaptor.forClass(long.class);
-    Mockito.doNothing()
-        .when(statsRecorderWrapperForConnection)
-        .putAndRecordPerConnectionErrorCount(errorCountCaptor.capture());
-    runInterceptorTasksAndAssertCount();
-    List allErrorCounts = errorCountCaptor.getAllValues();
-    assertThat(allErrorCounts.size()).isEqualTo(1);
-    assertThat(allErrorCounts.get(0)).isEqualTo(errorCount);
 
-    errorCount = 0;
+    runInterceptorTasksAndAssertCount();
+    long errorCount2 = 0;
 
     for (int i = 0; i < 20; i++) {
       Query query;
@@ -179,7 +223,7 @@ public void readOverTwoPeriods() throws Exception {
         query = Query.create(SUCCESS_TABLE_NAME);
       } else {
         query = Query.create(ERROR_TABLE_NAME);
-        errorCount += 1;
+        errorCount2 += 1;
       }
       try {
         stub.readRowsCallable().call(query).iterator().hasNext();
@@ -187,27 +231,22 @@ public void readOverTwoPeriods() throws Exception {
         // noop
       }
     }
-    errorCountCaptor = ArgumentCaptor.forClass(long.class);
-    Mockito.doNothing()
-        .when(statsRecorderWrapperForConnection)
-        .putAndRecordPerConnectionErrorCount(errorCountCaptor.capture());
+
     runInterceptorTasksAndAssertCount();
-    allErrorCounts = errorCountCaptor.getAllValues();
-    assertThat(allErrorCounts.size()).isEqualTo(1);
-    assertThat(allErrorCounts.get(0)).isEqualTo(errorCount);
-  }
 
-  @Test
-  public void ignoreInactiveConnection() throws Exception {
-    EnhancedBigtableStub stub = EnhancedBigtableStub.create(builder.build());
+    Collection allMetrics = metricReader.collectAllMetrics();
+    MetricData metricData =
+        BuiltinMetricsTestUtils.getMetricData(
+            allMetrics, BuiltinMetricsConstants.PER_CONNECTION_ERROR_COUNT_NAME);
 
-    ArgumentCaptor errorCountCaptor = ArgumentCaptor.forClass(long.class);
-    Mockito.doNothing()
-        .when(statsRecorderWrapperForConnection)
-        .putAndRecordPerConnectionErrorCount(errorCountCaptor.capture());
-    runInterceptorTasksAndAssertCount();
-    List allErrorCounts = errorCountCaptor.getAllValues();
-    assertThat(allErrorCounts).isEmpty();
+    ArrayList histogramPointData =
+        new ArrayList<>(metricData.getHistogramData().getPoints());
+    assertThat(histogramPointData.size()).isEqualTo(1);
+    HistogramPointData point = histogramPointData.get(0);
+    int index1 = findDataPointIndex(point.getBoundaries(), errorCount1);
+    int index2 = findDataPointIndex(point.getBoundaries(), errorCount2);
+    assertThat(point.getCounts().get(index1)).isEqualTo(1);
+    assertThat(point.getCounts().get(index2)).isEqualTo(1);
   }
 
   @Test
@@ -221,22 +260,19 @@ public void noFailedRequests() throws Exception {
         // noop
       }
     }
-    ArgumentCaptor errorCountCaptor = ArgumentCaptor.forClass(long.class);
-    Mockito.doNothing()
-        .when(statsRecorderWrapperForConnection)
-        .putAndRecordPerConnectionErrorCount(errorCountCaptor.capture());
     runInterceptorTasksAndAssertCount();
-    List allErrorCounts = errorCountCaptor.getAllValues();
-    assertThat(allErrorCounts.size()).isEqualTo(1);
-    assertThat(allErrorCounts.get(0)).isEqualTo(0);
+    Collection allMetrics = metricReader.collectAllMetrics();
+    MetricData metricData =
+        BuiltinMetricsTestUtils.getMetricData(
+            allMetrics, BuiltinMetricsConstants.PER_CONNECTION_ERROR_COUNT_NAME);
+    long value = BuiltinMetricsTestUtils.getAggregatedValue(metricData, attributes);
+    assertThat(value).isEqualTo(0);
   }
 
   private void runInterceptorTasksAndAssertCount() {
     int actualNumOfTasks = 0;
     for (Runnable runnable : runnableCaptor.getAllValues()) {
       if (runnable instanceof ErrorCountPerConnectionMetricTracker) {
-        ((ErrorCountPerConnectionMetricTracker) runnable)
-            .setStatsRecorderWrapperForConnection(statsRecorderWrapperForConnection);
         runnable.run();
         actualNumOfTasks++;
       }
@@ -244,6 +280,16 @@ private void runInterceptorTasksAndAssertCount() {
     assertThat(actualNumOfTasks).isEqualTo(1);
   }
 
+  private int findDataPointIndex(List boundaries, long dataPoint) {
+    int index = 0;
+    for (; index < boundaries.size(); index++) {
+      if (boundaries.get(index) >= dataPoint) {
+        break;
+      }
+    }
+    return index;
+  }
+
   static class FakeService extends BigtableGrpc.BigtableImplBase {
     @Override
     public void readRows(
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracerTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracerTest.java
index a4e28703c8..ecf204d1e2 100644
--- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracerTest.java
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracerTest.java
@@ -130,7 +130,7 @@ public void setUp() throws Exception {
                     settings.getStubSettings(),
                     Tags.getTagger(),
                     localStats.getStatsRecorder(),
-                    clientContext))
+                    null))
             .build();
     stub = new EnhancedBigtableStub(settings.getStubSettings(), clientContext);
   }