From 2bffab23ca5f90c46e89751d5f9387b187144e5e Mon Sep 17 00:00:00 2001 From: blakeli Date: Fri, 6 Mar 2026 23:39:36 -0500 Subject: [PATCH 01/12] feat: Add basic golden telemetry metrics. --- gax-java/gax/pom.xml | 5 + .../tracing/GoldenTelemetryMetricsTracer.java | 108 ++++++++++++++ .../GoldenTelemetryMetricsTracerFactory.java | 62 ++++++++ .../google/api/gax/tracing/MetricsTracer.java | 29 +--- .../gax/tracing/ObservabilityAttributes.java | 6 + .../api/gax/tracing/ObservabilityUtils.java | 69 +++++++++ .../tracing/OpenTelemetryMetricsRecorder.java | 21 +-- ...ldenTelemetryMetricsTracerFactoryTest.java | 56 +++++++ .../GoldenTelemetryMetricsTracerTest.java | 139 ++++++++++++++++++ .../api/gax/tracing/MetricsTracerTest.java | 23 --- .../gax/tracing/ObservabilityUtilsTest.java | 94 ++++++++++++ .../OpenTelemetryMetricsRecorderTest.java | 32 +--- 12 files changed, 552 insertions(+), 92 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracer.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactory.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactoryTest.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerTest.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java diff --git a/gax-java/gax/pom.xml b/gax-java/gax/pom.xml index 17fd8a65d0..d364536982 100644 --- a/gax-java/gax/pom.xml +++ b/gax-java/gax/pom.xml @@ -78,6 +78,11 @@ opentelemetry-context true + + io.opentelemetry + opentelemetry-sdk-testing + test + org.slf4j diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracer.java new file mode 100644 index 0000000000..a588eaa2bc --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracer.java @@ -0,0 +1,108 @@ +/* + * Copyright 2026 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 static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; +import static com.google.api.gax.tracing.ObservabilityUtils.toOtelAttributes; + +import com.google.api.gax.rpc.StatusCode; +import com.google.common.base.Stopwatch; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.Meter; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class GoldenTelemetryMetricsTracer implements ApiTracer { + + static final String CLIENT_REQUEST_DURATION_METRIC_NAME = "gcp.client.request.duration"; + static final String CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION = + "Measures the total time taken for a logical client request, including any retries, backoff, and pre/post-processing"; + private final Stopwatch clientRequestTimer = Stopwatch.createStarted(); + private final AtomicBoolean clientRequestFinished; + final DoubleHistogram clientRequestDurationRecorder; + private final Map attributes = new HashMap<>(); + + /** + * Creates the following instruments for the following metrics: + * + *
    + *
  • Client Request Duration: Histogram + *
+ * + * @param openTelemetry OpenTelemetry + * @param apiTracerContext ApiTracerContext + */ + public GoldenTelemetryMetricsTracer( + OpenTelemetry openTelemetry, ApiTracerContext apiTracerContext) { + this.clientRequestFinished = new AtomicBoolean(); + Meter meter = + openTelemetry.meterBuilder(apiTracerContext.libraryMetadata().artifactName()).build(); + + this.clientRequestDurationRecorder = + meter + .histogramBuilder(CLIENT_REQUEST_DURATION_METRIC_NAME) + .setDescription(CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION) + .setUnit("s") + .build(); + } + + @Override + public void operationSucceeded() { + if (clientRequestFinished.getAndSet(true)) { + throw new IllegalStateException(); + } + attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, StatusCode.Code.OK.toString()); + clientRequestDurationRecorder.record( + clientRequestTimer.elapsed(TimeUnit.SECONDS), toOtelAttributes(attributes)); + } + + @Override + public void operationCancelled() { + if (clientRequestFinished.getAndSet(true)) { + throw new IllegalStateException(); + } + attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, StatusCode.Code.CANCELLED.toString()); + clientRequestDurationRecorder.record( + clientRequestTimer.elapsed(TimeUnit.SECONDS), toOtelAttributes(attributes)); + } + + @Override + public void operationFailed(Throwable error) { + if (clientRequestFinished.getAndSet(true)) { + throw new IllegalStateException(); + } + attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); + clientRequestDurationRecorder.record( + clientRequestTimer.elapsed(TimeUnit.SECONDS), toOtelAttributes(attributes)); + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactory.java new file mode 100644 index 0000000000..17ea66dc80 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactory.java @@ -0,0 +1,62 @@ +/* + * Copyright 2026 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; +import com.google.api.core.InternalApi; +import io.opentelemetry.api.OpenTelemetry; + +/** + * A {@link ApiTracerFactory} to build instances of {@link GoldenTelemetryMetricsTracer}. + * + *

This class is expected to be initialized once during client initialization. + */ +@BetaApi +@InternalApi +public class GoldenTelemetryMetricsTracerFactory implements ApiTracerFactory { + + private ApiTracerContext apiTracerContext; + private final OpenTelemetry openTelemetry; + + public GoldenTelemetryMetricsTracerFactory(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + @Override + public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { + return new GoldenTelemetryMetricsTracer(openTelemetry, apiTracerContext); + } + + @Override + public ApiTracerFactory withContext(ApiTracerContext context) { + this.apiTracerContext = context; + return this; + } +} 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 16553dd118..e9ad908c21 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 @@ -35,16 +35,13 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.api.core.ObsoleteApi; -import com.google.api.gax.rpc.ApiException; import com.google.api.gax.rpc.StatusCode; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import javax.annotation.Nullable; /** * This class computes generic metrics that can be observed in the lifecycle of an RPC operation. @@ -123,7 +120,7 @@ public void operationFailed(Throwable error) { if (operationFinished.getAndSet(true)) { throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE); } - attributes.put(STATUS_ATTRIBUTE, extractStatus(error)); + attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); metricsRecorder.recordOperationLatency( operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordOperationCount(1, attributes); @@ -175,7 +172,7 @@ public void attemptCancelled() { */ @Override public void attemptFailedDuration(Throwable error, java.time.Duration delay) { - attributes.put(STATUS_ATTRIBUTE, extractStatus(error)); + attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } @@ -199,7 +196,7 @@ public void attemptFailed(Throwable error, org.threeten.bp.Duration delay) { */ @Override public void attemptFailedRetriesExhausted(Throwable error) { - attributes.put(STATUS_ATTRIBUTE, extractStatus(error)); + attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } @@ -213,29 +210,11 @@ public void attemptFailedRetriesExhausted(Throwable error) { */ @Override public void attemptPermanentFailure(Throwable error) { - attributes.put(STATUS_ATTRIBUTE, extractStatus(error)); + attributes.put(STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); metricsRecorder.recordAttemptCount(1, attributes); } - /** Function to extract the status of the error as a string */ - @VisibleForTesting - static String extractStatus(@Nullable Throwable error) { - final String statusString; - - if (error == null) { - return StatusCode.Code.OK.toString(); - } else if (error instanceof CancellationException) { - statusString = StatusCode.Code.CANCELLED.toString(); - } else if (error instanceof ApiException) { - statusString = ((ApiException) error).getStatusCode().getCode().toString(); - } else { - statusString = StatusCode.Code.UNKNOWN.toString(); - } - - return statusString; - } - /** * Add attributes that will be attached to all metrics. This is expected to be called by * handwritten client teams to add additional attributes that are not supposed be collected by diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java index 072dcdc098..77f6185ac3 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityAttributes.java @@ -49,4 +49,10 @@ public class ObservabilityAttributes { /** The artifact name of the client library (e.g., "google-cloud-vision"). */ public static final String ARTIFACT_ATTRIBUTE = "gcp.client.artifact"; + + /** + * The error codes of the request. The value will be the string representation of the canonical + * gRPC status code (e.g., "OK", "INTERNAL"). + */ + public static final String RPC_RESPONSE_STATUS_ATTRIBUTE = "rpc.response.status_code"; } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java new file mode 100644 index 0000000000..2fbdce8667 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java @@ -0,0 +1,69 @@ +/* + * Copyright 2026 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.api.gax.rpc.StatusCode; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import java.util.Map; +import java.util.concurrent.CancellationException; +import javax.annotation.Nullable; + +class ObservabilityUtils { + + /** Function to extract the status of the error as a string */ + @VisibleForTesting + static String extractStatus(@Nullable Throwable error) { + final String statusString; + + if (error == null) { + return StatusCode.Code.OK.toString(); + } else if (error instanceof CancellationException) { + statusString = StatusCode.Code.CANCELLED.toString(); + } else if (error instanceof ApiException) { + statusString = ((ApiException) error).getStatusCode().getCode().toString(); + } else { + statusString = StatusCode.Code.UNKNOWN.toString(); + } + + return statusString; + } + + @VisibleForTesting + static Attributes toOtelAttributes(Map attributes) { + Preconditions.checkNotNull(attributes, "Attributes map cannot be null"); + AttributesBuilder attributesBuilder = Attributes.builder(); + attributes.forEach(attributesBuilder::put); + return attributesBuilder.build(); + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryMetricsRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryMetricsRecorder.java index a029f42fde..3ccd6a6e7d 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryMetricsRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryMetricsRecorder.java @@ -33,11 +33,7 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.api.gax.core.GaxProperties; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.Meter; @@ -116,7 +112,7 @@ public OpenTelemetryMetricsRecorder(OpenTelemetry openTelemetry, String serviceN */ @Override public void recordAttemptLatency(double attemptLatency, Map attributes) { - attemptLatencyRecorder.record(attemptLatency, toOtelAttributes(attributes)); + attemptLatencyRecorder.record(attemptLatency, ObservabilityUtils.toOtelAttributes(attributes)); } /** @@ -129,7 +125,7 @@ public void recordAttemptLatency(double attemptLatency, Map attr */ @Override public void recordAttemptCount(long count, Map attributes) { - attemptCountRecorder.add(count, toOtelAttributes(attributes)); + attemptCountRecorder.add(count, ObservabilityUtils.toOtelAttributes(attributes)); } /** @@ -141,7 +137,8 @@ public void recordAttemptCount(long count, Map attributes) { */ @Override public void recordOperationLatency(double operationLatency, Map attributes) { - operationLatencyRecorder.record(operationLatency, toOtelAttributes(attributes)); + operationLatencyRecorder.record( + operationLatency, ObservabilityUtils.toOtelAttributes(attributes)); } /** @@ -154,14 +151,6 @@ public void recordOperationLatency(double operationLatency, Map */ @Override public void recordOperationCount(long count, Map attributes) { - operationCountRecorder.add(count, toOtelAttributes(attributes)); - } - - @VisibleForTesting - Attributes toOtelAttributes(Map attributes) { - Preconditions.checkNotNull(attributes, "Attributes map cannot be null"); - AttributesBuilder attributesBuilder = Attributes.builder(); - attributes.forEach(attributesBuilder::put); - return attributesBuilder.build(); + operationCountRecorder.add(count, ObservabilityUtils.toOtelAttributes(attributes)); } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactoryTest.java new file mode 100644 index 0000000000..338a14bdc6 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactoryTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2026 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 static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + +import io.opentelemetry.api.OpenTelemetry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class GoldenTelemetryMetricsTracerFactoryTest { + + private GoldenTelemetryMetricsTracerFactory tracerFactory; + + @BeforeEach + void setUp() { + tracerFactory = new GoldenTelemetryMetricsTracerFactory(OpenTelemetry.noop()); + tracerFactory.withContext(ApiTracerContext.empty()); + } + + @Test + void newTracer_createsTracer_successfully() { + ApiTracer actual = + tracerFactory.newTracer( + mock(ApiTracer.class), mock(SpanName.class), ApiTracerFactory.OperationType.Unary); + assertThat(actual).isInstanceOf(GoldenTelemetryMetricsTracer.class); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerTest.java new file mode 100644 index 0000000000..97ee02ad18 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2026 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 static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.LibraryMetadata; +import com.google.api.gax.rpc.StatusCode; +import com.google.api.gax.rpc.testing.FakeStatusCode; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import java.util.Collection; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class GoldenTelemetryMetricsTracerTest { + private static final String ARTIFACT_NAME = "test-library"; + private static final LibraryMetadata LIBRARY_METADATA = + LibraryMetadata.newBuilder().setArtifactName(ARTIFACT_NAME).build(); + private static final ApiTracerContext CONTEXT = + ApiTracerContext.newBuilder().setLibraryMetadata(LIBRARY_METADATA).build(); + + private InMemoryMetricReader metricReader; + + private GoldenTelemetryMetricsTracer tracer; + + @BeforeEach + void setUp() { + metricReader = InMemoryMetricReader.create(); + SdkMeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build(); + tracer = new GoldenTelemetryMetricsTracer(openTelemetry, CONTEXT); + } + + @Test + void operationSucceeded_recordsDuration() { + tracer.operationSucceeded(); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + verifyMetricDataContainsMeterInfo(metricData); + assertThat(metricData.getHistogramData().getPoints()).hasSize(1); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) + .isEqualTo( + Attributes.of( + AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), + StatusCode.Code.OK.toString())); + } + + @Test + void operationCancelled_recordsDuration() { + tracer.operationCancelled(); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + verifyMetricDataContainsMeterInfo(metricData); + assertThat(metricData.getHistogramData().getPoints()).hasSize(1); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) + .isEqualTo( + Attributes.of( + AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), + StatusCode.Code.CANCELLED.toString())); + } + + @Test + void operationFailed_recordsDuration() { + ApiException error = + new ApiException("test error", null, new FakeStatusCode(StatusCode.Code.INTERNAL), false); + tracer.operationFailed(error); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + verifyMetricDataContainsMeterInfo(metricData); + assertThat(metricData.getHistogramData().getPoints()).hasSize(1); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) + .isEqualTo( + Attributes.of( + AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), + StatusCode.Code.INTERNAL.toString())); + } + + @Test + void operationCompleted_throwsExceptionOnSecondCall() { + tracer.operationSucceeded(); + assertThrows(IllegalStateException.class, () -> tracer.operationSucceeded()); + } + + private void verifyMetricDataContainsMeterInfo(MetricData metricData) { + assertThat(metricData.getName()) + .isEqualTo(GoldenTelemetryMetricsTracer.CLIENT_REQUEST_DURATION_METRIC_NAME); + assertThat(metricData.getDescription()) + .isEqualTo(GoldenTelemetryMetricsTracer.CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION); + assertThat(metricData.getUnit()).isEqualTo("s"); + assertThat(metricData.getInstrumentationScopeInfo().getName()).isEqualTo(ARTIFACT_NAME); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerTest.java index 1864eba7e1..b16820c624 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerTest.java @@ -238,27 +238,4 @@ void testAddAttributes_recordsAttributesWithMap() { assertThat(metricsTracer.getAttributes().get("FakeTableId")).isEqualTo("12345"); assertThat(metricsTracer.getAttributes().get("FakeInstanceId")).isEqualTo("67890"); } - - @Test - void testExtractStatus_errorConversion_apiExceptions() { - ApiException error = - new ApiException("fake_error", null, new FakeStatusCode(Code.INVALID_ARGUMENT), false); - String errorCode = metricsTracer.extractStatus(error); - assertThat(errorCode).isEqualTo(Code.INVALID_ARGUMENT.toString()); - } - - @Test - void testExtractStatus_errorConversion_noError() { - // test "OK", which corresponds to a "null" error. - String successCode = metricsTracer.extractStatus(null); - assertThat(successCode).isEqualTo(Code.OK.toString()); - } - - @Test - void testExtractStatus_errorConversion_unknownException() { - // test "UNKNOWN" - Throwable unknownException = new RuntimeException(); - String errorCode2 = metricsTracer.extractStatus(unknownException); - assertThat(errorCode2).isEqualTo(Code.UNKNOWN.toString()); - } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java new file mode 100644 index 0000000000..3fb2eb2367 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2026 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 static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.StatusCode; +import com.google.api.gax.rpc.testing.FakeStatusCode; +import com.google.common.collect.ImmutableMap; +import com.google.common.truth.Truth; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ObservabilityUtilsTest { + @Test + void testExtractStatus_errorConversion_apiExceptions() { + ApiException error = + new ApiException( + "fake_error", null, new FakeStatusCode(StatusCode.Code.INVALID_ARGUMENT), false); + String errorCode = ObservabilityUtils.extractStatus(error); + assertThat(errorCode).isEqualTo(StatusCode.Code.INVALID_ARGUMENT.toString()); + } + + @Test + void testExtractStatus_errorConversion_noError() { + // test "OK", which corresponds to a "null" error. + String successCode = ObservabilityUtils.extractStatus(null); + assertThat(successCode).isEqualTo(StatusCode.Code.OK.toString()); + } + + @Test + void testExtractStatus_errorConversion_unknownException() { + // test "UNKNOWN" + Throwable unknownException = new RuntimeException(); + String errorCode2 = ObservabilityUtils.extractStatus(unknownException); + assertThat(errorCode2).isEqualTo(StatusCode.Code.UNKNOWN.toString()); + } + + @Test + void testToOtelAttributes_correctConversion() { + String attribute1 = "attribute_1"; + String attribute2 = "attribute_2"; + String attribute1Value = "Today is a good day"; + String attribute2Value = "Does not matter"; + Map attributes = + ImmutableMap.of(attribute1, attribute1Value, attribute2, attribute2Value); + ; + + Attributes otelAttributes = ObservabilityUtils.toOtelAttributes(attributes); + + Truth.assertThat(otelAttributes.get(AttributeKey.stringKey(attribute1))) + .isEqualTo(attribute1Value); + Truth.assertThat(otelAttributes.get(AttributeKey.stringKey(attribute2))) + .isEqualTo(attribute2Value); + } + + @Test + void testToOtelAttributes_nullInput() { + Throwable thrown = + assertThrows(NullPointerException.class, () -> ObservabilityUtils.toOtelAttributes(null)); + Truth.assertThat(thrown).hasMessageThat().contains("Attributes map cannot be null"); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryMetricsRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryMetricsRecorderTest.java index 3eea5955a4..7a49105a6c 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryMetricsRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryMetricsRecorderTest.java @@ -29,15 +29,12 @@ */ package com.google.api.gax.tracing; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import com.google.common.collect.ImmutableMap; -import com.google.common.truth.Truth; import com.google.rpc.Code; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.DoubleHistogramBuilder; @@ -104,7 +101,7 @@ private Map getAttributes(Code statusCode) { void testAttemptCountRecorder_recordsAttributes() { Map attributes = getAttributes(Code.OK); - Attributes otelAttributes = otelMetricsRecorder.toOtelAttributes(attributes); + Attributes otelAttributes = ObservabilityUtils.toOtelAttributes(attributes); otelMetricsRecorder.recordAttemptCount(1, attributes); verify(attemptCountRecorder).add(1, otelAttributes); @@ -115,7 +112,7 @@ void testAttemptCountRecorder_recordsAttributes() { void testAttemptLatencyRecorder_recordsAttributes() { Map attributes = getAttributes(Code.NOT_FOUND); - Attributes otelAttributes = otelMetricsRecorder.toOtelAttributes(attributes); + Attributes otelAttributes = ObservabilityUtils.toOtelAttributes(attributes); otelMetricsRecorder.recordAttemptLatency(1.1, attributes); verify(attemptLatencyRecorder).record(1.1, otelAttributes); @@ -126,7 +123,7 @@ void testAttemptLatencyRecorder_recordsAttributes() { void testOperationCountRecorder_recordsAttributes() { Map attributes = getAttributes(Code.OK); - Attributes otelAttributes = otelMetricsRecorder.toOtelAttributes(attributes); + Attributes otelAttributes = ObservabilityUtils.toOtelAttributes(attributes); otelMetricsRecorder.recordOperationCount(1, attributes); verify(operationCountRecorder).add(1, otelAttributes); @@ -137,34 +134,13 @@ void testOperationCountRecorder_recordsAttributes() { void testOperationLatencyRecorder_recordsAttributes() { Map attributes = getAttributes(Code.INVALID_ARGUMENT); - Attributes otelAttributes = otelMetricsRecorder.toOtelAttributes(attributes); + Attributes otelAttributes = ObservabilityUtils.toOtelAttributes(attributes); otelMetricsRecorder.recordOperationLatency(1.7, attributes); verify(operationLatencyRecorder).record(1.7, otelAttributes); verifyNoMoreInteractions(operationLatencyRecorder); } - @Test - void testToOtelAttributes_correctConversion() { - Map attributes = getAttributes(Code.OK); - - Attributes otelAttributes = otelMetricsRecorder.toOtelAttributes(attributes); - - Truth.assertThat(otelAttributes.get(AttributeKey.stringKey("status"))) - .isEqualTo(Code.OK.toString()); - Truth.assertThat(otelAttributes.get(AttributeKey.stringKey("method_name"))) - .isEqualTo(DEFAULT_METHOD_NAME); - Truth.assertThat(otelAttributes.get(AttributeKey.stringKey("language"))) - .isEqualTo(MetricsTracer.DEFAULT_LANGUAGE); - } - - @Test - void testToOtelAttributes_nullInput() { - Throwable thrown = - assertThrows(NullPointerException.class, () -> otelMetricsRecorder.toOtelAttributes(null)); - Truth.assertThat(thrown).hasMessageThat().contains("Attributes map cannot be null"); - } - private void setupAttemptCountRecorder() { // Configure chained mocking for AttemptCountRecorder Mockito.when(meter.counterBuilder(ATTEMPT_COUNT)).thenReturn(attemptCountRecorderBuilder); From cb368abe88f69c2c233b936d8da463100350ab46 Mon Sep 17 00:00:00 2001 From: blakeli Date: Fri, 6 Mar 2026 23:52:55 -0500 Subject: [PATCH 02/12] fix: Add error message for IllegalStateException in GoldenTelemetryMetricsTracer --- .../api/gax/tracing/GoldenTelemetryMetricsTracer.java | 9 ++++++--- .../gax/tracing/GoldenTelemetryMetricsTracerTest.java | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracer.java index a588eaa2bc..ab490767d5 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracer.java @@ -47,6 +47,9 @@ public class GoldenTelemetryMetricsTracer implements ApiTracer { static final String CLIENT_REQUEST_DURATION_METRIC_NAME = "gcp.client.request.duration"; static final String CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION = "Measures the total time taken for a logical client request, including any retries, backoff, and pre/post-processing"; + static final String OPERATION_FINISHED_STATUS_MESSAGE = + "Operation has already been completed"; + private final Stopwatch clientRequestTimer = Stopwatch.createStarted(); private final AtomicBoolean clientRequestFinished; final DoubleHistogram clientRequestDurationRecorder; @@ -79,7 +82,7 @@ public GoldenTelemetryMetricsTracer( @Override public void operationSucceeded() { if (clientRequestFinished.getAndSet(true)) { - throw new IllegalStateException(); + throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE); } attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, StatusCode.Code.OK.toString()); clientRequestDurationRecorder.record( @@ -89,7 +92,7 @@ public void operationSucceeded() { @Override public void operationCancelled() { if (clientRequestFinished.getAndSet(true)) { - throw new IllegalStateException(); + throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE); } attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, StatusCode.Code.CANCELLED.toString()); clientRequestDurationRecorder.record( @@ -99,7 +102,7 @@ public void operationCancelled() { @Override public void operationFailed(Throwable error) { if (clientRequestFinished.getAndSet(true)) { - throw new IllegalStateException(); + throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE); } attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); clientRequestDurationRecorder.record( diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerTest.java index 97ee02ad18..ca64ab78bc 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerTest.java @@ -29,6 +29,7 @@ */ package com.google.api.gax.tracing; +import static com.google.api.gax.tracing.GoldenTelemetryMetricsTracer.OPERATION_FINISHED_STATUS_MESSAGE; import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -125,7 +126,8 @@ void operationFailed_recordsDuration() { @Test void operationCompleted_throwsExceptionOnSecondCall() { tracer.operationSucceeded(); - assertThrows(IllegalStateException.class, () -> tracer.operationSucceeded()); + IllegalStateException actualException = assertThrows(IllegalStateException.class, () -> tracer.operationSucceeded()); + assertThat(actualException).hasMessageThat().isEqualTo(OPERATION_FINISHED_STATUS_MESSAGE); } private void verifyMetricDataContainsMeterInfo(MetricData metricData) { From d17ebd9049cd38fe48ce00779287041078f71faf Mon Sep 17 00:00:00 2001 From: blakeli Date: Fri, 6 Mar 2026 23:54:51 -0500 Subject: [PATCH 03/12] fix: Add opentelemetry_sdk_testing to dependencies.properties --- gax-java/dependencies.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/gax-java/dependencies.properties b/gax-java/dependencies.properties index 919c0da00b..deea56f5cc 100644 --- a/gax-java/dependencies.properties +++ b/gax-java/dependencies.properties @@ -41,6 +41,7 @@ maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-aut maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.42.1 maven.io_opentelemetry_opentelemetry_api=io.opentelemetry:opentelemetry-api:1.47.0 maven.io_opentelemetry_opentelemetry_context=io.opentelemetry:opentelemetry-context:1.47.0 +maven.io_opentelemetry_opentelemetry_sdk_testing=io.opentelemetry:opentelemetry_sdk_testing:1.47.0 maven.io_opencensus_opencensus_api=io.opencensus:opencensus-api:0.31.1 maven.io_opencensus_opencensus_contrib_grpc_metrics=io.opencensus:opencensus-contrib-grpc-metrics:0.31.1 maven.io_opencensus_opencensus_contrib_http_util=io.opencensus:opencensus-contrib-http-util:0.31.1 From 2ba106aa984302f918150c1fc0a63acc3bf25459 Mon Sep 17 00:00:00 2001 From: blakeli Date: Mon, 9 Mar 2026 01:18:54 -0400 Subject: [PATCH 04/12] fix: Rename to GoldenSignal --- ...icsTracer.java => GoldenSignalMetricsTracer.java} | 4 ++-- ...ry.java => GoldenSignalMetricsTracerFactory.java} | 8 ++++---- ...ava => GoldenSignalMetricsTracerFactoryTest.java} | 8 ++++---- ...rTest.java => GoldenSignalMetricsTracerTest.java} | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) rename gax-java/gax/src/main/java/com/google/api/gax/tracing/{GoldenTelemetryMetricsTracer.java => GoldenSignalMetricsTracer.java} (97%) rename gax-java/gax/src/main/java/com/google/api/gax/tracing/{GoldenTelemetryMetricsTracerFactory.java => GoldenSignalMetricsTracerFactory.java} (89%) rename gax-java/gax/src/test/java/com/google/api/gax/tracing/{GoldenTelemetryMetricsTracerFactoryTest.java => GoldenSignalMetricsTracerFactoryTest.java} (88%) rename gax-java/gax/src/test/java/com/google/api/gax/tracing/{GoldenTelemetryMetricsTracerTest.java => GoldenSignalMetricsTracerTest.java} (92%) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java similarity index 97% rename from gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracer.java rename to gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java index ab490767d5..e5d85e8b9f 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java @@ -42,7 +42,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -public class GoldenTelemetryMetricsTracer implements ApiTracer { +public class GoldenSignalMetricsTracer implements ApiTracer { static final String CLIENT_REQUEST_DURATION_METRIC_NAME = "gcp.client.request.duration"; static final String CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION = @@ -65,7 +65,7 @@ public class GoldenTelemetryMetricsTracer implements ApiTracer { * @param openTelemetry OpenTelemetry * @param apiTracerContext ApiTracerContext */ - public GoldenTelemetryMetricsTracer( + public GoldenSignalMetricsTracer( OpenTelemetry openTelemetry, ApiTracerContext apiTracerContext) { this.clientRequestFinished = new AtomicBoolean(); Meter meter = diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactory.java similarity index 89% rename from gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactory.java rename to gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactory.java index 17ea66dc80..409684b564 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactory.java @@ -34,24 +34,24 @@ import io.opentelemetry.api.OpenTelemetry; /** - * A {@link ApiTracerFactory} to build instances of {@link GoldenTelemetryMetricsTracer}. + * A {@link ApiTracerFactory} to build instances of {@link GoldenSignalMetricsTracer}. * *

This class is expected to be initialized once during client initialization. */ @BetaApi @InternalApi -public class GoldenTelemetryMetricsTracerFactory implements ApiTracerFactory { +public class GoldenSignalMetricsTracerFactory implements ApiTracerFactory { private ApiTracerContext apiTracerContext; private final OpenTelemetry openTelemetry; - public GoldenTelemetryMetricsTracerFactory(OpenTelemetry openTelemetry) { + public GoldenSignalMetricsTracerFactory(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; } @Override public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { - return new GoldenTelemetryMetricsTracer(openTelemetry, apiTracerContext); + return new GoldenSignalMetricsTracer(openTelemetry, apiTracerContext); } @Override diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactoryTest.java similarity index 88% rename from gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactoryTest.java rename to gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactoryTest.java index 338a14bdc6..53ec5ab71d 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerFactoryTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactoryTest.java @@ -36,13 +36,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class GoldenTelemetryMetricsTracerFactoryTest { +class GoldenSignalMetricsTracerFactoryTest { - private GoldenTelemetryMetricsTracerFactory tracerFactory; + private GoldenSignalMetricsTracerFactory tracerFactory; @BeforeEach void setUp() { - tracerFactory = new GoldenTelemetryMetricsTracerFactory(OpenTelemetry.noop()); + tracerFactory = new GoldenSignalMetricsTracerFactory(OpenTelemetry.noop()); tracerFactory.withContext(ApiTracerContext.empty()); } @@ -51,6 +51,6 @@ void newTracer_createsTracer_successfully() { ApiTracer actual = tracerFactory.newTracer( mock(ApiTracer.class), mock(SpanName.class), ApiTracerFactory.OperationType.Unary); - assertThat(actual).isInstanceOf(GoldenTelemetryMetricsTracer.class); + assertThat(actual).isInstanceOf(GoldenSignalMetricsTracer.class); } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java similarity index 92% rename from gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerTest.java rename to gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java index ca64ab78bc..f4d407f975 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenTelemetryMetricsTracerTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java @@ -29,7 +29,7 @@ */ package com.google.api.gax.tracing; -import static com.google.api.gax.tracing.GoldenTelemetryMetricsTracer.OPERATION_FINISHED_STATUS_MESSAGE; +import static com.google.api.gax.tracing.GoldenSignalMetricsTracer.OPERATION_FINISHED_STATUS_MESSAGE; import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -49,7 +49,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class GoldenTelemetryMetricsTracerTest { +class GoldenSignalMetricsTracerTest { private static final String ARTIFACT_NAME = "test-library"; private static final LibraryMetadata LIBRARY_METADATA = LibraryMetadata.newBuilder().setArtifactName(ARTIFACT_NAME).build(); @@ -58,7 +58,7 @@ class GoldenTelemetryMetricsTracerTest { private InMemoryMetricReader metricReader; - private GoldenTelemetryMetricsTracer tracer; + private GoldenSignalMetricsTracer tracer; @BeforeEach void setUp() { @@ -67,7 +67,7 @@ void setUp() { SdkMeterProvider.builder().registerMetricReader(metricReader).build(); OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build(); - tracer = new GoldenTelemetryMetricsTracer(openTelemetry, CONTEXT); + tracer = new GoldenSignalMetricsTracer(openTelemetry, CONTEXT); } @Test @@ -132,9 +132,9 @@ void operationCompleted_throwsExceptionOnSecondCall() { private void verifyMetricDataContainsMeterInfo(MetricData metricData) { assertThat(metricData.getName()) - .isEqualTo(GoldenTelemetryMetricsTracer.CLIENT_REQUEST_DURATION_METRIC_NAME); + .isEqualTo(GoldenSignalMetricsTracer.CLIENT_REQUEST_DURATION_METRIC_NAME); assertThat(metricData.getDescription()) - .isEqualTo(GoldenTelemetryMetricsTracer.CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION); + .isEqualTo(GoldenSignalMetricsTracer.CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION); assertThat(metricData.getUnit()).isEqualTo("s"); assertThat(metricData.getInstrumentationScopeInfo().getName()).isEqualTo(ARTIFACT_NAME); } From 2bbfda3d453c831cea8992ea15abb421b6354a65 Mon Sep 17 00:00:00 2001 From: blakeli Date: Mon, 9 Mar 2026 17:33:29 -0400 Subject: [PATCH 05/12] feat: Add GoldenSignalsMetricsRecorder to wrap OpenTelemetry related logics. --- .../tracing/GoldenSignalMetricsTracer.java | 61 +++++------------ .../GoldenSignalMetricsTracerFactory.java | 11 +++- .../tracing/GoldenSignalsMetricsRecorder.java | 65 +++++++++++++++++++ .../GoldenSignalMetricsTracerFactoryTest.java | 10 ++- .../GoldenSignalMetricsTracerTest.java | 21 ++---- 5 files changed, 108 insertions(+), 60 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorder.java diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java index e5d85e8b9f..630ccd8d9e 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java @@ -30,29 +30,22 @@ package com.google.api.gax.tracing; import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; -import static com.google.api.gax.tracing.ObservabilityUtils.toOtelAttributes; import com.google.api.gax.rpc.StatusCode; import com.google.common.base.Stopwatch; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.metrics.DoubleHistogram; -import io.opentelemetry.api.metrics.Meter; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -public class GoldenSignalMetricsTracer implements ApiTracer { - - static final String CLIENT_REQUEST_DURATION_METRIC_NAME = "gcp.client.request.duration"; - static final String CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION = - "Measures the total time taken for a logical client request, including any retries, backoff, and pre/post-processing"; - static final String OPERATION_FINISHED_STATUS_MESSAGE = - "Operation has already been completed"; +/** + * This class computes golden signal metrics that can be observed in the lifecycle of an RPC + * operation. The responsibility of recording metrics should delegate to {@link + * GoldenSignalsMetricsRecorder}, hence this class should not have any knowledge about the + * observability framework (e.g. OpenTelemetry). + */ +class GoldenSignalMetricsTracer implements ApiTracer { private final Stopwatch clientRequestTimer = Stopwatch.createStarted(); - private final AtomicBoolean clientRequestFinished; - final DoubleHistogram clientRequestDurationRecorder; + private final GoldenSignalsMetricsRecorder metricsRecorder; private final Map attributes = new HashMap<>(); /** @@ -62,50 +55,30 @@ public class GoldenSignalMetricsTracer implements ApiTracer { *

  • Client Request Duration: Histogram * * - * @param openTelemetry OpenTelemetry - * @param apiTracerContext ApiTracerContext + * @param metricsRecorder OpenTelemetry */ - public GoldenSignalMetricsTracer( - OpenTelemetry openTelemetry, ApiTracerContext apiTracerContext) { - this.clientRequestFinished = new AtomicBoolean(); - Meter meter = - openTelemetry.meterBuilder(apiTracerContext.libraryMetadata().artifactName()).build(); - - this.clientRequestDurationRecorder = - meter - .histogramBuilder(CLIENT_REQUEST_DURATION_METRIC_NAME) - .setDescription(CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION) - .setUnit("s") - .build(); + GoldenSignalMetricsTracer(GoldenSignalsMetricsRecorder metricsRecorder) { + this.metricsRecorder = metricsRecorder; } @Override public void operationSucceeded() { - if (clientRequestFinished.getAndSet(true)) { - throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE); - } attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, StatusCode.Code.OK.toString()); - clientRequestDurationRecorder.record( - clientRequestTimer.elapsed(TimeUnit.SECONDS), toOtelAttributes(attributes)); + metricsRecorder.recordOperationLatency( + clientRequestTimer.elapsed(TimeUnit.SECONDS), attributes); } @Override public void operationCancelled() { - if (clientRequestFinished.getAndSet(true)) { - throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE); - } attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, StatusCode.Code.CANCELLED.toString()); - clientRequestDurationRecorder.record( - clientRequestTimer.elapsed(TimeUnit.SECONDS), toOtelAttributes(attributes)); + metricsRecorder.recordOperationLatency( + clientRequestTimer.elapsed(TimeUnit.SECONDS), attributes); } @Override public void operationFailed(Throwable error) { - if (clientRequestFinished.getAndSet(true)) { - throw new IllegalStateException(OPERATION_FINISHED_STATUS_MESSAGE); - } attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); - clientRequestDurationRecorder.record( - clientRequestTimer.elapsed(TimeUnit.SECONDS), toOtelAttributes(attributes)); + metricsRecorder.recordOperationLatency( + clientRequestTimer.elapsed(TimeUnit.SECONDS), attributes); } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactory.java index 409684b564..166d1ffb88 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactory.java @@ -44,6 +44,7 @@ public class GoldenSignalMetricsTracerFactory implements ApiTracerFactory { private ApiTracerContext apiTracerContext; private final OpenTelemetry openTelemetry; + private GoldenSignalsMetricsRecorder metricsRecorder; public GoldenSignalMetricsTracerFactory(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -51,12 +52,20 @@ public GoldenSignalMetricsTracerFactory(OpenTelemetry openTelemetry) { @Override public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { - return new GoldenSignalMetricsTracer(openTelemetry, apiTracerContext); + if (metricsRecorder == null) { + // This should never happen, in case it happens, create a no-op api tracer to not block + // regular requests. + return new BaseApiTracer(); + } + return new GoldenSignalMetricsTracer(metricsRecorder); } @Override public ApiTracerFactory withContext(ApiTracerContext context) { this.apiTracerContext = context; + this.metricsRecorder = + new GoldenSignalsMetricsRecorder( + openTelemetry, apiTracerContext.libraryMetadata().artifactName()); return this; } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorder.java new file mode 100644 index 0000000000..ee5adf8e98 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorder.java @@ -0,0 +1,65 @@ +/* + * Copyright 2026 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 io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.Meter; +import java.util.Map; + +/** + * This class takes an OpenTelemetry object, and creates instruments (meters, histograms etc.) from + * it for recording golden signal metrics. There must be only one instance of + * GoldenSignalsMetricsRecorder per client, all the methods in this class are expected to be called + * from multiple threads, hence they need to be thread safe. + */ +class GoldenSignalsMetricsRecorder { + static final String CLIENT_REQUEST_DURATION_METRIC_NAME = "gcp.client.request.duration"; + static final String CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION = + "Measures the total time taken for a logical client request, including any retries, backoff, and pre/post-processing"; + + final DoubleHistogram clientRequestDurationRecorder; + + GoldenSignalsMetricsRecorder(OpenTelemetry openTelemetry, String libraryName) { + Meter meter = openTelemetry.meterBuilder(libraryName).build(); + + this.clientRequestDurationRecorder = + meter + .histogramBuilder(CLIENT_REQUEST_DURATION_METRIC_NAME) + .setDescription(CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION) + .setUnit("s") + .build(); + } + + void recordOperationLatency(double operationLatency, Map attributes) { + clientRequestDurationRecorder.record( + operationLatency, ObservabilityUtils.toOtelAttributes(attributes)); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactoryTest.java index 53ec5ab71d..322005ea39 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactoryTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactoryTest.java @@ -43,14 +43,22 @@ class GoldenSignalMetricsTracerFactoryTest { @BeforeEach void setUp() { tracerFactory = new GoldenSignalMetricsTracerFactory(OpenTelemetry.noop()); - tracerFactory.withContext(ApiTracerContext.empty()); } @Test void newTracer_createsTracer_successfully() { + tracerFactory.withContext(ApiTracerContext.empty()); ApiTracer actual = tracerFactory.newTracer( mock(ApiTracer.class), mock(SpanName.class), ApiTracerFactory.OperationType.Unary); assertThat(actual).isInstanceOf(GoldenSignalMetricsTracer.class); } + + @Test + void newTracer_createsBaseTracer_ifMetricsRecorderIsNull() { + ApiTracer actual = + tracerFactory.newTracer( + mock(ApiTracer.class), mock(SpanName.class), ApiTracerFactory.OperationType.Unary); + assertThat(actual).isInstanceOf(BaseApiTracer.class); + } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java index f4d407f975..3663d17519 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java @@ -29,10 +29,10 @@ */ package com.google.api.gax.tracing; -import static com.google.api.gax.tracing.GoldenSignalMetricsTracer.OPERATION_FINISHED_STATUS_MESSAGE; +import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION; +import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_NAME; import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; import static com.google.common.truth.Truth.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.api.gax.rpc.ApiException; import com.google.api.gax.rpc.LibraryMetadata; @@ -67,7 +67,9 @@ void setUp() { SdkMeterProvider.builder().registerMetricReader(metricReader).build(); OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build(); - tracer = new GoldenSignalMetricsTracer(openTelemetry, CONTEXT); + tracer = + new GoldenSignalMetricsTracer( + new GoldenSignalsMetricsRecorder(openTelemetry, ARTIFACT_NAME)); } @Test @@ -123,18 +125,9 @@ void operationFailed_recordsDuration() { StatusCode.Code.INTERNAL.toString())); } - @Test - void operationCompleted_throwsExceptionOnSecondCall() { - tracer.operationSucceeded(); - IllegalStateException actualException = assertThrows(IllegalStateException.class, () -> tracer.operationSucceeded()); - assertThat(actualException).hasMessageThat().isEqualTo(OPERATION_FINISHED_STATUS_MESSAGE); - } - private void verifyMetricDataContainsMeterInfo(MetricData metricData) { - assertThat(metricData.getName()) - .isEqualTo(GoldenSignalMetricsTracer.CLIENT_REQUEST_DURATION_METRIC_NAME); - assertThat(metricData.getDescription()) - .isEqualTo(GoldenSignalMetricsTracer.CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION); + assertThat(metricData.getName()).isEqualTo(CLIENT_REQUEST_DURATION_METRIC_NAME); + assertThat(metricData.getDescription()).isEqualTo(CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION); assertThat(metricData.getUnit()).isEqualTo("s"); assertThat(metricData.getInstrumentationScopeInfo().getName()).isEqualTo(ARTIFACT_NAME); } From 6123e58a8d9bc7f136bb8357cea174273d725077 Mon Sep 17 00:00:00 2001 From: blakeli Date: Mon, 9 Mar 2026 22:24:11 -0400 Subject: [PATCH 06/12] fix: Remove VisibleForTesting annotation. --- .../java/com/google/api/gax/tracing/ObservabilityUtils.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java index 2fbdce8667..ef52998e66 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java @@ -42,7 +42,6 @@ class ObservabilityUtils { /** Function to extract the status of the error as a string */ - @VisibleForTesting static String extractStatus(@Nullable Throwable error) { final String statusString; @@ -59,7 +58,6 @@ static String extractStatus(@Nullable Throwable error) { return statusString; } - @VisibleForTesting static Attributes toOtelAttributes(Map attributes) { Preconditions.checkNotNull(attributes, "Attributes map cannot be null"); AttributesBuilder attributesBuilder = Attributes.builder(); From 58951afcb21c14a5b12d47284ee5c98c031520be Mon Sep 17 00:00:00 2001 From: blakeli Date: Mon, 9 Mar 2026 22:25:19 -0400 Subject: [PATCH 07/12] fix: Remove unused fields in tests --- .../google/api/gax/tracing/GoldenSignalMetricsTracerTest.java | 2 -- .../java/com/google/api/gax/tracing/ObservabilityUtilsTest.java | 1 - 2 files changed, 3 deletions(-) diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java index 3663d17519..85ce3e529a 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java @@ -53,8 +53,6 @@ class GoldenSignalMetricsTracerTest { private static final String ARTIFACT_NAME = "test-library"; private static final LibraryMetadata LIBRARY_METADATA = LibraryMetadata.newBuilder().setArtifactName(ARTIFACT_NAME).build(); - private static final ApiTracerContext CONTEXT = - ApiTracerContext.newBuilder().setLibraryMetadata(LIBRARY_METADATA).build(); private InMemoryMetricReader metricReader; diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java index 3fb2eb2367..9d9a07686c 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/ObservabilityUtilsTest.java @@ -75,7 +75,6 @@ void testToOtelAttributes_correctConversion() { String attribute2Value = "Does not matter"; Map attributes = ImmutableMap.of(attribute1, attribute1Value, attribute2, attribute2Value); - ; Attributes otelAttributes = ObservabilityUtils.toOtelAttributes(attributes); From 3bd6f0a3ec95bc56e19793d57b7769cf801520c6 Mon Sep 17 00:00:00 2001 From: blakeli Date: Mon, 9 Mar 2026 22:55:04 -0400 Subject: [PATCH 08/12] test: Update unit tests. --- .../GoldenSignalMetricsTracerTest.java | 92 +++++++++------ .../GoldenSignalsMetricsRecorderTest.java | 109 ++++++++++++++++++ 2 files changed, 167 insertions(+), 34 deletions(-) create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java index 85ce3e529a..17f134d882 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java @@ -29,13 +29,7 @@ */ package com.google.api.gax.tracing; -import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION; -import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_NAME; -import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; -import static com.google.common.truth.Truth.assertThat; - import com.google.api.gax.rpc.ApiException; -import com.google.api.gax.rpc.LibraryMetadata; import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.testing.FakeStatusCode; import io.opentelemetry.api.OpenTelemetry; @@ -45,14 +39,16 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; -import java.util.Collection; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.Collection; + +import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; +import static com.google.common.truth.Truth.assertThat; + class GoldenSignalMetricsTracerTest { private static final String ARTIFACT_NAME = "test-library"; - private static final LibraryMetadata LIBRARY_METADATA = - LibraryMetadata.newBuilder().setArtifactName(ARTIFACT_NAME).build(); private InMemoryMetricReader metricReader; @@ -71,62 +67,90 @@ void setUp() { } @Test - void operationSucceeded_recordsDuration() { + void operationSucceeded_shouldRecordsDuration() { + tracer.operationSucceeded(); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + assertThat(metricData.getHistogramData().getPoints()).hasSize(1); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()).isNonZero(); + } + + @Test + void operationSucceeded_shouldRecordsOKStatus() { tracer.operationSucceeded(); Collection metrics = metricReader.collectAllMetrics(); assertThat(metrics).hasSize(1); MetricData metricData = metrics.iterator().next(); - verifyMetricDataContainsMeterInfo(metricData); assertThat(metricData.getHistogramData().getPoints()).hasSize(1); assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) - .isEqualTo( - Attributes.of( - AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), - StatusCode.Code.OK.toString())); + .isEqualTo( + Attributes.of( + AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), + StatusCode.Code.OK.toString())); + } + + @Test + void operationCancelled_shouldRecordsDuration() { + tracer.operationCancelled(); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + assertThat(metricData.getHistogramData().getPoints()).hasSize(1); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()).isNonZero(); } @Test - void operationCancelled_recordsDuration() { + void operationCancelled_shouldRecordsOKStatus() { tracer.operationCancelled(); Collection metrics = metricReader.collectAllMetrics(); assertThat(metrics).hasSize(1); MetricData metricData = metrics.iterator().next(); - verifyMetricDataContainsMeterInfo(metricData); assertThat(metricData.getHistogramData().getPoints()).hasSize(1); assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) - .isEqualTo( - Attributes.of( - AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), - StatusCode.Code.CANCELLED.toString())); + .isEqualTo( + Attributes.of( + AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), + StatusCode.Code.CANCELLED.toString())); } @Test - void operationFailed_recordsDuration() { + void operationFailed_shouldRecordsDuration() { ApiException error = - new ApiException("test error", null, new FakeStatusCode(StatusCode.Code.INTERNAL), false); + new ApiException("test error", null, new FakeStatusCode(StatusCode.Code.INTERNAL), false); tracer.operationFailed(error); Collection metrics = metricReader.collectAllMetrics(); assertThat(metrics).hasSize(1); MetricData metricData = metrics.iterator().next(); - verifyMetricDataContainsMeterInfo(metricData); assertThat(metricData.getHistogramData().getPoints()).hasSize(1); - assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) - .isEqualTo( - Attributes.of( - AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), - StatusCode.Code.INTERNAL.toString())); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()).isNonZero(); } - private void verifyMetricDataContainsMeterInfo(MetricData metricData) { - assertThat(metricData.getName()).isEqualTo(CLIENT_REQUEST_DURATION_METRIC_NAME); - assertThat(metricData.getDescription()).isEqualTo(CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION); - assertThat(metricData.getUnit()).isEqualTo("s"); - assertThat(metricData.getInstrumentationScopeInfo().getName()).isEqualTo(ARTIFACT_NAME); + @Test + void operationFailed_shouldRecordsOKStatus() { + ApiException error = + new ApiException("test error", null, new FakeStatusCode(StatusCode.Code.INTERNAL), false); + tracer.operationFailed(error); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + assertThat(metricData.getHistogramData().getPoints()).hasSize(1); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) + .isEqualTo( + Attributes.of( + AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), + StatusCode.Code.INTERNAL.toString())); } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java new file mode 100644 index 0000000000..7d7ad0fc35 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2026 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.StatusCode; +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.HistogramPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collection; + +import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION; +import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_NAME; +import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; +import static com.google.common.truth.Truth.assertThat; + +class GoldenSignalsMetricsRecorderTest { + private static final String ARTIFACT_NAME = "test-library"; + private static final String ATTRIBUTE_1 = "attribute_1"; + private static final String VALUE_1 = "value_1"; + + private InMemoryMetricReader metricReader; + + private GoldenSignalsMetricsRecorder recorder; + + @BeforeEach + void setUp() { + metricReader = InMemoryMetricReader.create(); + SdkMeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build(); + recorder = + new GoldenSignalsMetricsRecorder(openTelemetry, ARTIFACT_NAME); + } + + @Test + void recordOperationLatency_shouldRecordMeterInfo() { + recorder.recordOperationLatency(0.012, ImmutableMap.of(ATTRIBUTE_1, VALUE_1)); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + assertThat(metricData.getName()).isEqualTo(CLIENT_REQUEST_DURATION_METRIC_NAME); + assertThat(metricData.getDescription()).isEqualTo(CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION); + assertThat(metricData.getUnit()).isEqualTo("s"); + assertThat(metricData.getInstrumentationScopeInfo().getName()).isEqualTo(ARTIFACT_NAME); + } + + @Test + void recordOperationLatency_shouldRecordMetrics() { + recorder.recordOperationLatency(0.012, ImmutableMap.of(ATTRIBUTE_1, VALUE_1)); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + assertThat(metricData.getHistogramData().getPoints()).hasSize(1); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()).isNonZero(); + } + + @Test + void recordOperationLatency_shouldRecordMetricAttributes() { + recorder.recordOperationLatency(0.012, ImmutableMap.of(ATTRIBUTE_1, VALUE_1)); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) + .isEqualTo(Attributes.of(AttributeKey.stringKey(ATTRIBUTE_1), VALUE_1)); + } +} \ No newline at end of file From 60b4ab867b165f08021823f1e08f541d39d36b77 Mon Sep 17 00:00:00 2001 From: blakeli Date: Tue, 10 Mar 2026 18:24:09 -0400 Subject: [PATCH 09/12] fix: resolve comments. --- gax-java/gax/pom.xml | 6 + .../tracing/GoldenSignalMetricsTracer.java | 17 ++- .../tracing/GoldenSignalsMetricsRecorder.java | 7 + .../api/gax/tracing/ObservabilityUtils.java | 1 - .../GoldenSignalMetricsTracerTest.java | 57 +++++--- .../GoldenSignalsMetricsRecorderTest.java | 135 ++++++++++-------- 6 files changed, 132 insertions(+), 91 deletions(-) diff --git a/gax-java/gax/pom.xml b/gax-java/gax/pom.xml index d364536982..78c65c4f42 100644 --- a/gax-java/gax/pom.xml +++ b/gax-java/gax/pom.xml @@ -83,6 +83,12 @@ opentelemetry-sdk-testing test + + com.google.guava + guava-testlib + ${guava.version} + test + org.slf4j diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java index 630ccd8d9e..c8ee011b11 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java @@ -32,7 +32,9 @@ import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; import com.google.api.gax.rpc.StatusCode; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; +import com.google.common.base.Ticker; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -44,7 +46,7 @@ * observability framework (e.g. OpenTelemetry). */ class GoldenSignalMetricsTracer implements ApiTracer { - private final Stopwatch clientRequestTimer = Stopwatch.createStarted(); + private final Stopwatch clientRequestTimer; private final GoldenSignalsMetricsRecorder metricsRecorder; private final Map attributes = new HashMap<>(); @@ -58,6 +60,13 @@ class GoldenSignalMetricsTracer implements ApiTracer { * @param metricsRecorder OpenTelemetry */ GoldenSignalMetricsTracer(GoldenSignalsMetricsRecorder metricsRecorder) { + this.clientRequestTimer = Stopwatch.createStarted(); + this.metricsRecorder = metricsRecorder; + } + + @VisibleForTesting + GoldenSignalMetricsTracer(GoldenSignalsMetricsRecorder metricsRecorder, Ticker ticker) { + this.clientRequestTimer = Stopwatch.createStarted(ticker); this.metricsRecorder = metricsRecorder; } @@ -65,20 +74,20 @@ class GoldenSignalMetricsTracer implements ApiTracer { public void operationSucceeded() { attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, StatusCode.Code.OK.toString()); metricsRecorder.recordOperationLatency( - clientRequestTimer.elapsed(TimeUnit.SECONDS), attributes); + clientRequestTimer.elapsed(TimeUnit.NANOSECONDS) / 1_000_000_000.0, attributes); } @Override public void operationCancelled() { attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, StatusCode.Code.CANCELLED.toString()); metricsRecorder.recordOperationLatency( - clientRequestTimer.elapsed(TimeUnit.SECONDS), attributes); + clientRequestTimer.elapsed(TimeUnit.NANOSECONDS) / 1_000_000_000.0, attributes); } @Override public void operationFailed(Throwable error) { attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, ObservabilityUtils.extractStatus(error)); metricsRecorder.recordOperationLatency( - clientRequestTimer.elapsed(TimeUnit.SECONDS), attributes); + clientRequestTimer.elapsed(TimeUnit.NANOSECONDS) / 1_000_000_000.0, attributes); } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorder.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorder.java index ee5adf8e98..747170cade 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorder.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorder.java @@ -32,6 +32,8 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.Meter; +import java.util.Arrays; +import java.util.List; import java.util.Map; /** @@ -45,6 +47,10 @@ class GoldenSignalsMetricsRecorder { static final String CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION = "Measures the total time taken for a logical client request, including any retries, backoff, and pre/post-processing"; + static final List BOUNDARIES = + Arrays.asList( + 0.0, 0.0001, 0.0005, 0.0010, 0.005, 0.010, 0.050, 0.100, 0.5, 1.0, 5.0, 10.0, 60.0, 300.0, + 900.0, 3600.0); final DoubleHistogram clientRequestDurationRecorder; GoldenSignalsMetricsRecorder(OpenTelemetry openTelemetry, String libraryName) { @@ -55,6 +61,7 @@ class GoldenSignalsMetricsRecorder { .histogramBuilder(CLIENT_REQUEST_DURATION_METRIC_NAME) .setDescription(CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION) .setUnit("s") + .setExplicitBucketBoundariesAdvice(BOUNDARIES) .build(); } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java index ef52998e66..142a11ff11 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/ObservabilityUtils.java @@ -31,7 +31,6 @@ import com.google.api.gax.rpc.ApiException; import com.google.api.gax.rpc.StatusCode; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java index 17f134d882..8a2c5c5dd7 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java @@ -29,9 +29,13 @@ */ package com.google.api.gax.tracing; +import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; +import static com.google.common.truth.Truth.assertThat; + import com.google.api.gax.rpc.ApiException; import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.testing.FakeStatusCode; +import com.google.common.testing.FakeTicker; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -39,21 +43,21 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import java.util.Collection; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.Collection; - -import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; -import static com.google.common.truth.Truth.assertThat; - class GoldenSignalMetricsTracerTest { private static final String ARTIFACT_NAME = "test-library"; + public static final int TEST_REQUEST_DURATION_NANO = 2345698; + public static final double EXPECTED_REQUEST_DURATION_SECOND = 2345698 / 1_000_000_000.0; private InMemoryMetricReader metricReader; private GoldenSignalMetricsTracer tracer; + private FakeTicker ticker; + @BeforeEach void setUp() { metricReader = InMemoryMetricReader.create(); @@ -61,13 +65,15 @@ void setUp() { SdkMeterProvider.builder().registerMetricReader(metricReader).build(); OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build(); + ticker = new FakeTicker(); tracer = new GoldenSignalMetricsTracer( - new GoldenSignalsMetricsRecorder(openTelemetry, ARTIFACT_NAME)); + new GoldenSignalsMetricsRecorder(openTelemetry, ARTIFACT_NAME), ticker); } @Test void operationSucceeded_shouldRecordsDuration() { + ticker.advance(TEST_REQUEST_DURATION_NANO); tracer.operationSucceeded(); Collection metrics = metricReader.collectAllMetrics(); @@ -75,7 +81,8 @@ void operationSucceeded_shouldRecordsDuration() { MetricData metricData = metrics.iterator().next(); assertThat(metricData.getHistogramData().getPoints()).hasSize(1); - assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()).isNonZero(); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()) + .isEqualTo(EXPECTED_REQUEST_DURATION_SECOND); } @Test @@ -88,14 +95,15 @@ void operationSucceeded_shouldRecordsOKStatus() { assertThat(metricData.getHistogramData().getPoints()).hasSize(1); assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) - .isEqualTo( - Attributes.of( - AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), - StatusCode.Code.OK.toString())); + .isEqualTo( + Attributes.of( + AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), + StatusCode.Code.OK.toString())); } @Test void operationCancelled_shouldRecordsDuration() { + ticker.advance(TEST_REQUEST_DURATION_NANO); tracer.operationCancelled(); Collection metrics = metricReader.collectAllMetrics(); @@ -103,7 +111,8 @@ void operationCancelled_shouldRecordsDuration() { MetricData metricData = metrics.iterator().next(); assertThat(metricData.getHistogramData().getPoints()).hasSize(1); - assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()).isNonZero(); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()) + .isEqualTo(EXPECTED_REQUEST_DURATION_SECOND); } @Test @@ -116,16 +125,17 @@ void operationCancelled_shouldRecordsOKStatus() { assertThat(metricData.getHistogramData().getPoints()).hasSize(1); assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) - .isEqualTo( - Attributes.of( - AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), - StatusCode.Code.CANCELLED.toString())); + .isEqualTo( + Attributes.of( + AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), + StatusCode.Code.CANCELLED.toString())); } @Test void operationFailed_shouldRecordsDuration() { + ticker.advance(TEST_REQUEST_DURATION_NANO); ApiException error = - new ApiException("test error", null, new FakeStatusCode(StatusCode.Code.INTERNAL), false); + new ApiException("test error", null, new FakeStatusCode(StatusCode.Code.INTERNAL), false); tracer.operationFailed(error); Collection metrics = metricReader.collectAllMetrics(); @@ -133,13 +143,14 @@ void operationFailed_shouldRecordsDuration() { MetricData metricData = metrics.iterator().next(); assertThat(metricData.getHistogramData().getPoints()).hasSize(1); - assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()).isNonZero(); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()) + .isEqualTo(EXPECTED_REQUEST_DURATION_SECOND); } @Test void operationFailed_shouldRecordsOKStatus() { ApiException error = - new ApiException("test error", null, new FakeStatusCode(StatusCode.Code.INTERNAL), false); + new ApiException("test error", null, new FakeStatusCode(StatusCode.Code.INTERNAL), false); tracer.operationFailed(error); Collection metrics = metricReader.collectAllMetrics(); @@ -148,9 +159,9 @@ void operationFailed_shouldRecordsOKStatus() { assertThat(metricData.getHistogramData().getPoints()).hasSize(1); assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) - .isEqualTo( - Attributes.of( - AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), - StatusCode.Code.INTERNAL.toString())); + .isEqualTo( + Attributes.of( + AttributeKey.stringKey(RPC_RESPONSE_STATUS_ATTRIBUTE), + StatusCode.Code.INTERNAL.toString())); } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java index 7d7ad0fc35..f9a07b7e2c 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java @@ -29,81 +29,90 @@ */ package com.google.api.gax.tracing; -import com.google.api.gax.rpc.StatusCode; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.collect.ImmutableMap; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.metrics.SdkMeterProvider; -import io.opentelemetry.sdk.metrics.data.HistogramPointData; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import java.util.Collection; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.Collection; - import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION; import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_NAME; -import static com.google.api.gax.tracing.ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE; -import static com.google.common.truth.Truth.assertThat; +import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.BOUNDARIES; class GoldenSignalsMetricsRecorderTest { - private static final String ARTIFACT_NAME = "test-library"; - private static final String ATTRIBUTE_1 = "attribute_1"; - private static final String VALUE_1 = "value_1"; - - private InMemoryMetricReader metricReader; - - private GoldenSignalsMetricsRecorder recorder; - - @BeforeEach - void setUp() { - metricReader = InMemoryMetricReader.create(); - SdkMeterProvider meterProvider = - SdkMeterProvider.builder().registerMetricReader(metricReader).build(); - OpenTelemetry openTelemetry = - OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build(); - recorder = - new GoldenSignalsMetricsRecorder(openTelemetry, ARTIFACT_NAME); - } - - @Test - void recordOperationLatency_shouldRecordMeterInfo() { - recorder.recordOperationLatency(0.012, ImmutableMap.of(ATTRIBUTE_1, VALUE_1)); - - Collection metrics = metricReader.collectAllMetrics(); - assertThat(metrics).hasSize(1); - MetricData metricData = metrics.iterator().next(); - - assertThat(metricData.getName()).isEqualTo(CLIENT_REQUEST_DURATION_METRIC_NAME); - assertThat(metricData.getDescription()).isEqualTo(CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION); - assertThat(metricData.getUnit()).isEqualTo("s"); - assertThat(metricData.getInstrumentationScopeInfo().getName()).isEqualTo(ARTIFACT_NAME); - } - - @Test - void recordOperationLatency_shouldRecordMetrics() { - recorder.recordOperationLatency(0.012, ImmutableMap.of(ATTRIBUTE_1, VALUE_1)); - - Collection metrics = metricReader.collectAllMetrics(); - assertThat(metrics).hasSize(1); - MetricData metricData = metrics.iterator().next(); - - assertThat(metricData.getHistogramData().getPoints()).hasSize(1); - assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()).isNonZero(); - } - - @Test - void recordOperationLatency_shouldRecordMetricAttributes() { - recorder.recordOperationLatency(0.012, ImmutableMap.of(ATTRIBUTE_1, VALUE_1)); - - Collection metrics = metricReader.collectAllMetrics(); - assertThat(metrics).hasSize(1); - MetricData metricData = metrics.iterator().next(); - - assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) - .isEqualTo(Attributes.of(AttributeKey.stringKey(ATTRIBUTE_1), VALUE_1)); - } -} \ No newline at end of file + private static final String ARTIFACT_NAME = "test-library"; + private static final String ATTRIBUTE_1 = "attribute_1"; + private static final String VALUE_1 = "value_1"; + + private InMemoryMetricReader metricReader; + + private GoldenSignalsMetricsRecorder recorder; + + @BeforeEach + void setUp() { + metricReader = InMemoryMetricReader.create(); + SdkMeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build(); + recorder = new GoldenSignalsMetricsRecorder(openTelemetry, ARTIFACT_NAME); + } + + @Test + void recordOperationLatency_shouldRecordMeterInfo() { + recorder.recordOperationLatency(0.012, ImmutableMap.of(ATTRIBUTE_1, VALUE_1)); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + assertThat(metricData.getName()).isEqualTo(CLIENT_REQUEST_DURATION_METRIC_NAME); + assertThat(metricData.getDescription()).isEqualTo(CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION); + assertThat(metricData.getUnit()).isEqualTo("s"); + assertThat(metricData.getInstrumentationScopeInfo().getName()).isEqualTo(ARTIFACT_NAME); + } + + @Test + void recordOperationLatency_shouldRecordWithBoundaries() { + recorder.recordOperationLatency(0.012, ImmutableMap.of(ATTRIBUTE_1, VALUE_1)); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + assertThat(metricData.getHistogramData().getPoints().iterator().next().getBoundaries()) + .isEqualTo(BOUNDARIES); + } + + @Test + void recordOperationLatency_shouldRecordMetrics() { + recorder.recordOperationLatency(0.012, ImmutableMap.of(ATTRIBUTE_1, VALUE_1)); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + assertThat(metricData.getHistogramData().getPoints()).hasSize(1); + assertThat(metricData.getHistogramData().getPoints().iterator().next().getMax()).isNonZero(); + } + + @Test + void recordOperationLatency_shouldRecordMetricAttributes() { + recorder.recordOperationLatency(0.012, ImmutableMap.of(ATTRIBUTE_1, VALUE_1)); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics).hasSize(1); + MetricData metricData = metrics.iterator().next(); + + assertThat(metricData.getHistogramData().getPoints().iterator().next().getAttributes()) + .isEqualTo(Attributes.of(AttributeKey.stringKey(ATTRIBUTE_1), VALUE_1)); + } +} From a8deaa9e384374252fa1144059667915c0eaaf97 Mon Sep 17 00:00:00 2001 From: blakeli Date: Tue, 10 Mar 2026 18:33:24 -0400 Subject: [PATCH 10/12] fix: resolve comments. --- .../api/gax/tracing/GoldenSignalsMetricsRecorderTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java index f9a07b7e2c..3476e64a72 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsRecorderTest.java @@ -29,6 +29,9 @@ */ package com.google.api.gax.tracing; +import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.BOUNDARIES; +import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION; +import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_NAME; import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableMap; @@ -43,10 +46,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_DESCRIPTION; -import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_NAME; -import static com.google.api.gax.tracing.GoldenSignalsMetricsRecorder.BOUNDARIES; - class GoldenSignalsMetricsRecorderTest { private static final String ARTIFACT_NAME = "test-library"; private static final String ATTRIBUTE_1 = "attribute_1"; From b0263762f6f599994c827156301517c869de26e0 Mon Sep 17 00:00:00 2001 From: blakeli Date: Tue, 10 Mar 2026 23:19:53 -0400 Subject: [PATCH 11/12] fix: fix bazel build issues. --- gax-java/dependencies.properties | 10 +++++++--- gax-java/gax/BUILD.bazel | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/gax-java/dependencies.properties b/gax-java/dependencies.properties index deea56f5cc..7215a51375 100644 --- a/gax-java/dependencies.properties +++ b/gax-java/dependencies.properties @@ -39,9 +39,8 @@ maven.com_google_api_grpc_proto_google_common_protos=com.google.api.grpc:proto-g maven.com_google_api_grpc_grpc_google_common_protos=com.google.api.grpc:grpc-google-common-protos:2.63.2 maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.42.1 maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.42.1 -maven.io_opentelemetry_opentelemetry_api=io.opentelemetry:opentelemetry-api:1.47.0 -maven.io_opentelemetry_opentelemetry_context=io.opentelemetry:opentelemetry-context:1.47.0 -maven.io_opentelemetry_opentelemetry_sdk_testing=io.opentelemetry:opentelemetry_sdk_testing:1.47.0 +maven.io_opentelemetry_opentelemetry_api=io.opentelemetry:opentelemetry-api:1.51.0 +maven.io_opentelemetry_opentelemetry_context=io.opentelemetry:opentelemetry-context:1.51.0 maven.io_opencensus_opencensus_api=io.opencensus:opencensus-api:0.31.1 maven.io_opencensus_opencensus_contrib_grpc_metrics=io.opencensus:opencensus-contrib-grpc-metrics:0.31.1 maven.io_opencensus_opencensus_contrib_http_util=io.opencensus:opencensus-contrib-http-util:0.31.1 @@ -92,3 +91,8 @@ maven.net_bytebuddy_byte_buddy=net.bytebuddy:byte-buddy:1.17.0 maven.org_objenesis_objenesis=org.objenesis:objenesis:2.6 maven.org_junit_jupiter_junit_jupiter_api=org.junit.jupiter:junit-jupiter-api:5.11.4 maven.org_junit_jupiter_junit_jupiter_params=org.junit.jupiter:junit-jupiter-params:5.11.4 +maven.io_opentelemetry_opentelemetry_sdk_testing=io.opentelemetry:opentelemetry-sdk-testing:1.51.0 +maven.io_opentelemetry_opentelemetry_sdk=io.opentelemetry:opentelemetry-sdk:1.51.0 +maven.io_opentelemetry_opentelemetry_sdk_common=io.opentelemetry:opentelemetry-sdk-common:1.51.0 +maven.io_opentelemetry_opentelemetry_sdk_metrics=io.opentelemetry:opentelemetry-sdk-metrics:1.51.0 +maven.com_google_guava_guava_testlib=com.google.guava:guava-testlib:32.1.3-jre diff --git a/gax-java/gax/BUILD.bazel b/gax-java/gax/BUILD.bazel index 15ed36bcbd..6f60891329 100644 --- a/gax-java/gax/BUILD.bazel +++ b/gax-java/gax/BUILD.bazel @@ -44,6 +44,11 @@ _TEST_COMPILE_DEPS = [ "@net_bytebuddy_byte_buddy//jar", "@org_objenesis_objenesis//jar", "@com_googlecode_java_diff_utils_diffutils//jar", + "@io_opentelemetry_opentelemetry_sdk_testing//jar", + "@io_opentelemetry_opentelemetry_sdk//jar", + "@io_opentelemetry_opentelemetry_sdk_metrics//jar", + "@io_opentelemetry_opentelemetry_sdk_common//jar", + "@com_google_guava_guava_testlib//jar", ] java_library( From 70a5560c3b0f34ce07d230a958028217384737f5 Mon Sep 17 00:00:00 2001 From: blakeli Date: Wed, 11 Mar 2026 00:09:57 -0400 Subject: [PATCH 12/12] fix: Rename and add javadoc. --- ...icsTracer.java => GoldenSignalsMetricsTracer.java} | 11 ++++++++--- ...ry.java => GoldenSignalsMetricsTracerFactory.java} | 8 ++++---- ...ava => GoldenSignalsMetricsTracerFactoryTest.java} | 8 ++++---- ...rTest.java => GoldenSignalsMetricsTracerTest.java} | 6 +++--- 4 files changed, 19 insertions(+), 14 deletions(-) rename gax-java/gax/src/main/java/com/google/api/gax/tracing/{GoldenSignalMetricsTracer.java => GoldenSignalsMetricsTracer.java} (89%) rename gax-java/gax/src/main/java/com/google/api/gax/tracing/{GoldenSignalMetricsTracerFactory.java => GoldenSignalsMetricsTracerFactory.java} (91%) rename gax-java/gax/src/test/java/com/google/api/gax/tracing/{GoldenSignalMetricsTracerFactoryTest.java => GoldenSignalsMetricsTracerFactoryTest.java} (90%) rename gax-java/gax/src/test/java/com/google/api/gax/tracing/{GoldenSignalMetricsTracerTest.java => GoldenSignalsMetricsTracerTest.java} (98%) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java similarity index 89% rename from gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java rename to gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java index c8ee011b11..954feab58e 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracer.java @@ -45,7 +45,7 @@ * GoldenSignalsMetricsRecorder}, hence this class should not have any knowledge about the * observability framework (e.g. OpenTelemetry). */ -class GoldenSignalMetricsTracer implements ApiTracer { +class GoldenSignalsMetricsTracer implements ApiTracer { private final Stopwatch clientRequestTimer; private final GoldenSignalsMetricsRecorder metricsRecorder; private final Map attributes = new HashMap<>(); @@ -59,17 +59,22 @@ class GoldenSignalMetricsTracer implements ApiTracer { * * @param metricsRecorder OpenTelemetry */ - GoldenSignalMetricsTracer(GoldenSignalsMetricsRecorder metricsRecorder) { + GoldenSignalsMetricsTracer(GoldenSignalsMetricsRecorder metricsRecorder) { this.clientRequestTimer = Stopwatch.createStarted(); this.metricsRecorder = metricsRecorder; } @VisibleForTesting - GoldenSignalMetricsTracer(GoldenSignalsMetricsRecorder metricsRecorder, Ticker ticker) { + GoldenSignalsMetricsTracer(GoldenSignalsMetricsRecorder metricsRecorder, Ticker ticker) { this.clientRequestTimer = Stopwatch.createStarted(ticker); this.metricsRecorder = metricsRecorder; } + /** + * The concept of "operation" and "client request" are the same. They both represent the total + * time taken for a logical client request, including any retries, backoff, and + * pre/post-processing + */ @Override public void operationSucceeded() { attributes.put(RPC_RESPONSE_STATUS_ATTRIBUTE, StatusCode.Code.OK.toString()); diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactory.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracerFactory.java similarity index 91% rename from gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactory.java rename to gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracerFactory.java index 166d1ffb88..02aa9a1ff4 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactory.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracerFactory.java @@ -34,19 +34,19 @@ import io.opentelemetry.api.OpenTelemetry; /** - * A {@link ApiTracerFactory} to build instances of {@link GoldenSignalMetricsTracer}. + * A {@link ApiTracerFactory} to build instances of {@link GoldenSignalsMetricsTracer}. * *

    This class is expected to be initialized once during client initialization. */ @BetaApi @InternalApi -public class GoldenSignalMetricsTracerFactory implements ApiTracerFactory { +public class GoldenSignalsMetricsTracerFactory implements ApiTracerFactory { private ApiTracerContext apiTracerContext; private final OpenTelemetry openTelemetry; private GoldenSignalsMetricsRecorder metricsRecorder; - public GoldenSignalMetricsTracerFactory(OpenTelemetry openTelemetry) { + public GoldenSignalsMetricsTracerFactory(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; } @@ -57,7 +57,7 @@ public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType op // regular requests. return new BaseApiTracer(); } - return new GoldenSignalMetricsTracer(metricsRecorder); + return new GoldenSignalsMetricsTracer(metricsRecorder); } @Override diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracerFactoryTest.java similarity index 90% rename from gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactoryTest.java rename to gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracerFactoryTest.java index 322005ea39..8a39ff27f1 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerFactoryTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracerFactoryTest.java @@ -36,13 +36,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class GoldenSignalMetricsTracerFactoryTest { +class GoldenSignalsMetricsTracerFactoryTest { - private GoldenSignalMetricsTracerFactory tracerFactory; + private GoldenSignalsMetricsTracerFactory tracerFactory; @BeforeEach void setUp() { - tracerFactory = new GoldenSignalMetricsTracerFactory(OpenTelemetry.noop()); + tracerFactory = new GoldenSignalsMetricsTracerFactory(OpenTelemetry.noop()); } @Test @@ -51,7 +51,7 @@ void newTracer_createsTracer_successfully() { ApiTracer actual = tracerFactory.newTracer( mock(ApiTracer.class), mock(SpanName.class), ApiTracerFactory.OperationType.Unary); - assertThat(actual).isInstanceOf(GoldenSignalMetricsTracer.class); + assertThat(actual).isInstanceOf(GoldenSignalsMetricsTracer.class); } @Test diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracerTest.java similarity index 98% rename from gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java rename to gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracerTest.java index 8a2c5c5dd7..cfd9588abb 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalMetricsTracerTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/GoldenSignalsMetricsTracerTest.java @@ -47,14 +47,14 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class GoldenSignalMetricsTracerTest { +class GoldenSignalsMetricsTracerTest { private static final String ARTIFACT_NAME = "test-library"; public static final int TEST_REQUEST_DURATION_NANO = 2345698; public static final double EXPECTED_REQUEST_DURATION_SECOND = 2345698 / 1_000_000_000.0; private InMemoryMetricReader metricReader; - private GoldenSignalMetricsTracer tracer; + private GoldenSignalsMetricsTracer tracer; private FakeTicker ticker; @@ -67,7 +67,7 @@ void setUp() { OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build(); ticker = new FakeTicker(); tracer = - new GoldenSignalMetricsTracer( + new GoldenSignalsMetricsTracer( new GoldenSignalsMetricsRecorder(openTelemetry, ARTIFACT_NAME), ticker); }