diff --git a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricsImpl.java b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricsImpl.java index 2eef8c9a3f3a..3d677d6849ba 100644 --- a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricsImpl.java +++ b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricsImpl.java @@ -87,6 +87,7 @@ public class MetricsImpl implements Metrics, Closeable { public static final String CUSTOM_METRIC = "bigtable.internal.enable-custom-metric"; + public static final String CUSTOM_METRIC_PREFIX = "bigtable.custom"; private static final boolean enableCustomMetric = Optional.ofNullable(System.getProperty(CUSTOM_METRIC)) @@ -319,7 +320,13 @@ public static OpenTelemetrySdk createBuiltinOtel( new BigtablePeriodicReader( new BigtableFilteringExporter( exporter, - input -> input.getName().startsWith("bigtable.googleapis.com/internal/client")), + input -> { + // filter out custom metrics and keep everything else that's registered on metric + // registry + String name = input.getName(); + return metricRegistry.getMetric(name) != null + && !name.startsWith(CUSTOM_METRIC_PREFIX); + }), executor)); if (enableCustomMetric) { @@ -333,7 +340,7 @@ public static OpenTelemetrySdk createBuiltinOtel( PeriodicMetricReader.builder( new BigtableFilteringExporter( GoogleCloudMetricExporter.createWithConfiguration(metricConfig), - input -> input.getName().startsWith("bigtable.custom"))) + input -> input.getName().startsWith(CUSTOM_METRIC_PREFIX))) .setInterval(Duration.ofMinutes(1)) .build()); } diff --git a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/CustomAttemptLatency.java b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/CustomAttemptLatency.java index 453b3957a1fc..17f20a04b6c7 100644 --- a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/CustomAttemptLatency.java +++ b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/CustomAttemptLatency.java @@ -17,6 +17,7 @@ import com.google.bigtable.v2.ClusterInformation; import com.google.bigtable.v2.PeerInfo; +import com.google.cloud.bigtable.data.v2.internal.csm.MetricsImpl; import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo; import com.google.cloud.bigtable.data.v2.internal.csm.attributes.MethodInfo; import com.google.cloud.bigtable.data.v2.internal.csm.attributes.Util; @@ -33,7 +34,7 @@ * exported as a custom metric. */ public class CustomAttemptLatency extends MetricWrapper { - private static final String NAME = "bigtable.custom.attempt_latencies"; + private static final String NAME = MetricsImpl.CUSTOM_METRIC_PREFIX + ".attempt_latencies"; public CustomAttemptLatency() { super(CustomSchema.INSTANCE, NAME); diff --git a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistryExportTest.java b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistryExportTest.java index 2f157180faef..0eeb9c885c0e 100644 --- a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistryExportTest.java +++ b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistryExportTest.java @@ -775,6 +775,38 @@ void testPacemaker() { .build()); } + @Test + void testGrpcMetricExport() { + // 1. Record a gRPC metric using its internal name + // Note: gRPC metrics are usually recorded by gRPC-Java's OpenTelemetry integration, + // but we can simulate it by getting the instrument from the meter directly. + meterProvider + .get("grpc-java") + .histogramBuilder("grpc.client.attempt.duration") + .build() + .record( + 123.0, + io.opentelemetry.api.common.Attributes.of( + io.opentelemetry.api.common.AttributeKey.stringKey("grpc.status"), "OK", + io.opentelemetry.api.common.AttributeKey.stringKey("grpc.method"), + "Bigtable.ReadRows")); + + // 2. Flush the metrics to the exporter + metricReader.forceFlush().join(1, TimeUnit.MINUTES); + + // 3. Verify that the metric was exported under the Bigtable namespace + // The internal 'grpc.client.attempt.duration' should be renamed by GrpcMetric.java + TimeSeries timeSeries = + metricService.getSingleTimeSeriesByName( + "bigtable.googleapis.com/internal/client/grpc/client/attempt/duration"); + + // 4. Verify labels are correctly mapped (dots replaced with underscores) + assertThat(timeSeries.getMetric().getLabelsMap()) + .containsAtLeast( + "grpc_status", "OK", + "grpc_method", "Bigtable.ReadRows"); + } + private static class FakeMetricService extends MetricServiceImplBase { final BlockingDeque requests = new LinkedBlockingDeque<>();