Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Google LLC
* Copyright 2026 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
Expand Down Expand Up @@ -44,6 +44,7 @@
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;

/**
Expand All @@ -55,6 +56,9 @@
@BetaApi
@InternalApi
public class MetricsTracer implements ApiTracer {
private static final Logger LOGGER = Logger.getLogger(MetricsTracer.class.getName());
private static final AtomicBoolean WARNING_LOGGED = new AtomicBoolean();

public static final String METHOD_ATTRIBUTE = "method";
public static final String LANGUAGE_ATTRIBUTE = "language";
public static final String STATUS_ATTRIBUTE = "status";
Expand All @@ -69,6 +73,14 @@ public class MetricsTracer implements ApiTracer {
private final AtomicBoolean operationFinished;

public MetricsTracer(MethodName methodName, MetricsRecorder metricsRecorder) {
if (!MetricsUtils.isMetricsEnabled()) {
if (WARNING_LOGGED.compareAndSet(false, true)) {
LOGGER.info(
"Metrics are currently disabled. To enable metrics, set the environment variable "
+ "GOOGLE_CLOUD_ENABLE_METRICS=true or the system property "
+ "GOOGLE_CLOUD_ENABLE_METRICS=true.");
}
}
this.attributes.put(METHOD_ATTRIBUTE, methodName.toString());
this.attributes.put(LANGUAGE_ATTRIBUTE, DEFAULT_LANGUAGE);
this.metricsRecorder = metricsRecorder;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Google LLC
* Copyright 2026 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
Expand Down Expand Up @@ -66,6 +66,9 @@ public MetricsTracerFactory(MetricsRecorder metricsRecorder, Map<String, String>

@Override
public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) {
if (!MetricsUtils.isMetricsEnabled()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to change how existing metrics works, the flag is intended to protect new metric instrumentation from people taking dependencies on it while it stabilizes.

return BaseApiTracer.getInstance();
}
MetricsTracer metricsTracer =
new MetricsTracer(
MethodName.of(spanName.getClientName(), spanName.getMethodName()), metricsRecorder);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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.InternalApi;
import com.google.common.annotations.VisibleForTesting;

/** Internal utility class to manage metrics configuration. */
@InternalApi
public class MetricsUtils {
static final String GOOGLE_CLOUD_ENABLE_METRICS = "GOOGLE_CLOUD_ENABLE_METRICS";

private MetricsUtils() {}

/**
* Returns true if metrics are enabled via the GOOGLE_CLOUD_ENABLE_METRICS environment variable or
* system property.
*/
public static boolean isMetricsEnabled() {
String enableMetrics = System.getProperty(GOOGLE_CLOUD_ENABLE_METRICS);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like this to have the words "JAVA" and "EXPERIMENTAL" or "UNSTABLE" in it. The Go one is GOOGLE_SDK_GO_EXPERIMENTAL_FOO

Copy link

@westarle westarle Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "JAVA" part of this comment is intended to apply to the env variable rather than the Java property...

if (enableMetrics == null) {
enableMetrics = System.getenv(GOOGLE_CLOUD_ENABLE_METRICS);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to cache the result, or can we put a note on the method that it should be cached by clients (I'd like to avoid a read to env or properties on every RPC).

}
return isMetricsEnabled(enableMetrics);
}

@VisibleForTesting
static boolean isMetricsEnabled(String envValue) {
return "true".equalsIgnoreCase(envValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,16 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.truth.Truth;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

@ExtendWith(MockitoExtension.class)
class MetricsTracerFactoryTest {
private static final int DEFAULT_ATTRIBUTES_COUNT = 2;

Expand All @@ -50,6 +56,9 @@ class MetricsTracerFactoryTest {

@BeforeEach
void setUp() {
// Enable metrics for tests by default, as most tests expect MetricsTracer
System.setProperty("GOOGLE_CLOUD_ENABLE_METRICS", "true");

// Create an instance of MetricsTracerFactory with the mocked MetricsRecorder
metricsTracerFactory = new MetricsTracerFactory(metricsRecorder);

Expand All @@ -58,6 +67,11 @@ void setUp() {
when(spanName.getMethodName()).thenReturn("testMethod");
}

@AfterEach
void tearDown() {
System.clearProperty("GOOGLE_CLOUD_ENABLE_METRICS");
}

@Test
void testNewTracer_notNull() {
// Call the newTracer method
Expand All @@ -68,6 +82,19 @@ void testNewTracer_notNull() {
Truth.assertThat(apiTracer).isInstanceOf(MetricsTracer.class);
}

/**
* The LENIENT strictness flag avoids unnecessary mocks (from setUp() method) from being called
* out in this test. This test renders them unecessary because metrics are disabled here.
*/
@Test
@MockitoSettings(strictness = Strictness.LENIENT)
void testNewTracer_disabledMetrics_returnsBaseApiTracer() {
System.setProperty("GOOGLE_CLOUD_ENABLE_METRICS", "false");
ApiTracer apiTracer = metricsTracerFactory.newTracer(parent, spanName, OperationType.Unary);

Truth.assertThat(apiTracer).isSameInstanceAs(BaseApiTracer.getInstance());
}

@Test
void testNewTracer_hasCorrectNumberAttributes_hasDefaultAttributes() {
MetricsTracer metricsTracer =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Google LLC
* Copyright 2026 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
Expand Down Expand Up @@ -30,6 +30,7 @@

package com.google.showcase.v1beta1.it;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.api.client.http.javanet.NetHttpTransport;
Expand All @@ -42,9 +43,13 @@
import com.google.api.gax.rpc.StatusCode.Code;
import com.google.api.gax.rpc.UnaryCallable;
import com.google.api.gax.rpc.UnavailableException;
import com.google.api.gax.tracing.ApiTracer;
import com.google.api.gax.tracing.ApiTracerFactory;
import com.google.api.gax.tracing.BaseApiTracer;
import com.google.api.gax.tracing.MetricsTracer;
import com.google.api.gax.tracing.MetricsTracerFactory;
import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder;
import com.google.api.gax.tracing.SpanName;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
Expand Down Expand Up @@ -154,6 +159,9 @@ private OpenTelemetryMetricsRecorder createOtelMetricsRecorder(

@BeforeEach
void setup() throws Exception {
// Enable metrics for these tests
System.setProperty("GOOGLE_CLOUD_ENABLE_METRICS", "true");

inMemoryMetricReader = InMemoryMetricReader.create();
OpenTelemetryMetricsRecorder otelMetricsRecorder =
createOtelMetricsRecorder(inMemoryMetricReader);
Expand All @@ -167,6 +175,8 @@ void setup() throws Exception {

@AfterEach
void cleanup() throws InterruptedException, IOException {
System.clearProperty("GOOGLE_CLOUD_ENABLE_METRICS");

inMemoryMetricReader.close();
inMemoryMetricReader.shutdown();

Expand Down Expand Up @@ -919,4 +929,32 @@ void grpcOpenTelemetryImplementation_setSampledToLocalTracing_methodFullNameIsRe
echoClient.close();
echoClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS);
}

@Test
void testMetricsFeatureFlag() throws Exception {
// Test metrics disabled
System.setProperty("GOOGLE_CLOUD_ENABLE_METRICS", "false");

try (InMemoryMetricReader reader = InMemoryMetricReader.create()) {

OpenTelemetryMetricsRecorder recorder = createOtelMetricsRecorder(reader);
MetricsTracerFactory factory = new MetricsTracerFactory(recorder);
ApiTracer tracer =
factory.newTracer(
BaseApiTracer.getInstance(),
SpanName.of("EchoClient", "Echo"),
ApiTracerFactory.OperationType.Unary);
assertThat(tracer).isNotInstanceOf(MetricsTracer.class);
assertThat(tracer).isSameInstanceAs(BaseApiTracer.getInstance());

// Test metrics enabled
System.setProperty("GOOGLE_CLOUD_ENABLE_METRICS", "true");
tracer =
factory.newTracer(
BaseApiTracer.getInstance(),
SpanName.of("EchoClient", "Echo"),
ApiTracerFactory.OperationType.Unary);
assertThat(tracer).isInstanceOf(MetricsTracer.class);
}
}
}
Loading