Skip to content

Commit

Permalink
Expose option to provide MetricServiceSettings in MetricConfiguration (
Browse files Browse the repository at this point in the history
…#356)

* Add option to provide MetricServiceSettings in MetricConfiguration

* Add unit test

* Update example with setMetricServiceSettings usage

* Add another unit test
  • Loading branch information
psx95 authored Jul 9, 2024
1 parent e3d5d6b commit 7ca8f25
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 37 deletions.
4 changes: 4 additions & 0 deletions examples/metrics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ export GOOGLE_CLOUD_PROJECT="my-awesome-gcp-project-id"
You can run the example application via gradle. From the project root:

```shell
# Running with default exporter config
cd examples/metrics/ && gradle run

# Running with custom exporter config
cd examples/metrics/ && gradle run --args='--custom-config'
```

#### Run the example as a Cloud Run Job
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,23 @@
*/
package com.google.cloud.opentelemetry.example.metrics;

import static com.google.api.client.util.Preconditions.checkNotNull;

import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.grpc.GrpcTransportChannel;
import com.google.api.gax.rpc.FixedTransportChannelProvider;
import com.google.auth.Credentials;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.monitoring.v3.MetricServiceSettings;
import com.google.cloud.opentelemetry.metric.GoogleCloudMetricExporter;
import com.google.cloud.opentelemetry.metric.MetricConfiguration;
import io.grpc.ManagedChannelBuilder;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
import java.io.IOException;
import java.util.Random;

public class MetricsExporterExample {
Expand All @@ -29,8 +40,42 @@ public class MetricsExporterExample {
private static Meter METER;
private static final Random RANDOM = new Random();

private static void setupMetricExporter() {
MetricExporter metricExporter = GoogleCloudMetricExporter.createWithDefaultConfiguration();
private static MetricConfiguration generateMetricExporterConfig(boolean useDefaultConfig)
throws IOException {
if (useDefaultConfig) {
System.out.println("Using default exporter configuration");
return MetricConfiguration.builder().build();
}

System.out.println("Using custom configuration");
// Configuring exporter through MetricServiceSettings
Credentials credentials = GoogleCredentials.getApplicationDefault();
MetricServiceSettings.Builder metricServiceSettingsBuilder = MetricServiceSettings.newBuilder();
metricServiceSettingsBuilder
.setCredentialsProvider(
FixedCredentialsProvider.create(checkNotNull(credentials, "Credentials not provided.")))
.setTransportChannelProvider(
FixedTransportChannelProvider.create(
GrpcTransportChannel.create(
ManagedChannelBuilder.forTarget(
MetricConfiguration.DEFAULT_METRIC_SERVICE_ENDPOINT)
// default 8 KiB
.maxInboundMetadataSize(16 * 1000)
.build())))
.createMetricDescriptorSettings()
.setSimpleTimeoutNoRetries(
org.threeten.bp.Duration.ofMillis(MetricConfiguration.DEFAULT_DEADLINE.toMillis()))
.build();

// Any properties not set would be retrieved from the default configuration of the exporter.
return MetricConfiguration.builder()
.setMetricServiceSettings(metricServiceSettingsBuilder.build())
.build();
}

private static void setupMetricExporter(MetricConfiguration metricConfiguration) {
MetricExporter metricExporter =
GoogleCloudMetricExporter.createWithConfiguration(metricConfiguration);
METER_PROVIDER =
SdkMeterProvider.builder()
.registerMetricReader(
Expand Down Expand Up @@ -68,9 +113,16 @@ private static void doWork(LongCounter counter) {
}

// to run this from command line, execute `gradle run`
public static void main(String[] args) throws InterruptedException {
public static void main(String[] args) throws InterruptedException, IOException {
System.out.println("Starting the metrics-example application");
setupMetricExporter();
boolean useDefaultConfig = true;
if (args.length > 0) {
if (args[0].equals("--custom-config")) {
useDefaultConfig = false;
}
}
setupMetricExporter(generateMetricExporterConfig(useDefaultConfig));

try {
int i = 0;
while (i < 2) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,36 +91,15 @@ static InternalMetricExporter createWithConfiguration(MetricConfiguration config
throws IOException {
String projectId = configuration.getProjectId();
String prefix = configuration.getPrefix();
MetricServiceSettings.Builder builder = MetricServiceSettings.newBuilder();
// For testing, we need to hack around our gRPC config.
if (configuration.getInsecureEndpoint()) {
builder.setCredentialsProvider(NoCredentialsProvider.create());
builder.setTransportChannelProvider(
FixedTransportChannelProvider.create(
GrpcTransportChannel.create(
ManagedChannelBuilder.forTarget(configuration.getMetricServiceEndpoint())
.usePlaintext()
.build())));
} else {
// For any other endpoint, we force credentials to exist.
Credentials credentials =
configuration.getCredentials() == null
? GoogleCredentials.getApplicationDefault()
: configuration.getCredentials();

builder.setCredentialsProvider(
FixedCredentialsProvider.create(checkNotNull(credentials, "Credentials not provided.")));
builder.setEndpoint(configuration.getMetricServiceEndpoint());
}
builder
.createMetricDescriptorSettings()
.setSimpleTimeoutNoRetries(
org.threeten.bp.Duration.ofMillis(configuration.getDeadline().toMillis()));
MetricServiceSettings serviceClientSettings =
configuration.getMetricServiceSettings() == null
? generateMetricServiceSettings(configuration)
: configuration.getMetricServiceSettings();

return new InternalMetricExporter(
projectId,
prefix,
new CloudMetricClientImpl(MetricServiceClient.create(builder.build())),
new CloudMetricClientImpl(MetricServiceClient.create(serviceClientSettings)),
configuration.getDescriptorStrategy(),
configuration.getResourceAttributesFilter(),
configuration.getUseServiceTimeSeries(),
Expand All @@ -146,6 +125,36 @@ static InternalMetricExporter createWithClient(
monitoredResourceDescription);
}

private static MetricServiceSettings generateMetricServiceSettings(
MetricConfiguration configuration) throws IOException {
MetricServiceSettings.Builder builder = MetricServiceSettings.newBuilder();
// For testing, we need to hack around our gRPC config.
if (configuration.getInsecureEndpoint()) {
builder.setCredentialsProvider(NoCredentialsProvider.create());
builder.setTransportChannelProvider(
FixedTransportChannelProvider.create(
GrpcTransportChannel.create(
ManagedChannelBuilder.forTarget(configuration.getMetricServiceEndpoint())
.usePlaintext()
.build())));
} else {
// For any other endpoint, we force credentials to exist.
Credentials credentials =
configuration.getCredentials() == null
? GoogleCredentials.getApplicationDefault()
: configuration.getCredentials();

builder.setCredentialsProvider(
FixedCredentialsProvider.create(checkNotNull(credentials, "Credentials not provided.")));
builder.setEndpoint(configuration.getMetricServiceEndpoint());
}
builder
.createMetricDescriptorSettings()
.setSimpleTimeoutNoRetries(
org.threeten.bp.Duration.ofMillis(configuration.getDeadline().toMillis()));
return builder.build();
}

private void exportDescriptor(MetricDescriptor descriptor) {
logger.trace("Creating metric descriptor: {}", descriptor);
metricServiceClient.createMetricDescriptor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.auth.Credentials;
import com.google.auto.value.AutoValue;
import com.google.cloud.ServiceOptions;
import com.google.cloud.monitoring.v3.MetricServiceSettings;
import com.google.cloud.monitoring.v3.stub.MetricServiceStubSettings;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
Expand All @@ -43,6 +44,14 @@
@AutoValue
@Immutable
public abstract class MetricConfiguration {
static final String DEFAULT_PREFIX = "workload.googleapis.com";

public static final Duration DEFAULT_DEADLINE =
Duration.ofSeconds(12, 0); // Consistent with Cloud Monitoring's timeout

public static final String DEFAULT_METRIC_SERVICE_ENDPOINT =
MetricServiceStubSettings.getDefaultEndpoint();

/** Resource attribute filter that disables addition of resource attributes to metric labels. */
public static final Predicate<AttributeKey<?>> NO_RESOURCE_ATTRIBUTES = attributeKey -> false;

Expand All @@ -59,11 +68,6 @@ public abstract class MetricConfiguration {
|| attributeKey.equals(ResourceAttributes.SERVICE_INSTANCE_ID))
&& !attributeKey.getKey().isEmpty();

static final String DEFAULT_PREFIX = "workload.googleapis.com";

private static final Duration DEFAULT_DEADLINE =
Duration.ofSeconds(12, 0); // Consistent with Cloud Monitoring's timeout

MetricConfiguration() {}

/**
Expand Down Expand Up @@ -168,6 +172,16 @@ public final String getProjectId() {
*/
public abstract MonitoredResourceDescription getMonitoredResourceDescription();

/**
* Returns the {@link MetricServiceSettings} instance used to configure the service client used to
* connect to Monitoring API.
*
* @return The {@link MetricServiceSettings} object that is used to configure the internal service
* client.
*/
@Nullable
public abstract MetricServiceSettings getMetricServiceSettings();

@VisibleForTesting
abstract boolean getInsecureEndpoint();

Expand All @@ -194,7 +208,7 @@ public static Builder builder() {
.setUseServiceTimeSeries(false)
.setResourceAttributesFilter(DEFAULT_RESOURCE_ATTRIBUTES_FILTER)
.setMonitoredResourceDescription(EMPTY_MONITORED_RESOURCE_DESCRIPTION)
.setMetricServiceEndpoint(MetricServiceStubSettings.getDefaultEndpoint());
.setMetricServiceEndpoint(DEFAULT_METRIC_SERVICE_ENDPOINT);
}

/** Builder for {@link MetricConfiguration}. */
Expand Down Expand Up @@ -282,6 +296,29 @@ public abstract Builder setMonitoredResourceDescription(
*/
public abstract Builder setResourceAttributesFilter(Predicate<AttributeKey<?>> filter);

/**
* Sets the options used to configure the {@link
* com.google.cloud.monitoring.v3.MetricServiceClient} used to interact with the Cloud
* Monitoring API. This is for advanced usage and must be configured carefully.
*
* <p>Providing MetricServiceSettings will cause the exporter to ignore the values configured
* using:
*
* <ul>
* <li>{@link MetricConfiguration.Builder#setInsecureEndpoint(boolean)}
* <li>{@link MetricConfiguration.Builder#setCredentials(Credentials)}
* <li>{@link MetricConfiguration.Builder#setMetricServiceEndpoint(String)}
* </ul>
*
* The intended effect of setting these values in the configuration should instead be achieved
* by configuring the {@link MetricServiceSettings} object.
*
* @param metricServiceSettings the {@link MetricServiceSettings} containing the configured
* options.
* @return this.
*/
public abstract Builder setMetricServiceSettings(MetricServiceSettings metricServiceSettings);

@VisibleForTesting
abstract Builder setInsecureEndpoint(boolean value);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import static com.google.cloud.opentelemetry.metric.FakeData.aTraceId;
import static com.google.cloud.opentelemetry.metric.FakeData.anInstrumentationLibraryInfo;
import static com.google.cloud.opentelemetry.metric.FakeData.googleComputeServiceMetricData;
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_DEADLINE;
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_METRIC_SERVICE_ENDPOINT;
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_PREFIX;
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_RESOURCE_ATTRIBUTES_FILTER;
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.EMPTY_MONITORED_RESOURCE_DESCRIPTION;
Expand All @@ -44,6 +46,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
Expand All @@ -58,6 +62,7 @@
import com.google.api.MetricDescriptor;
import com.google.api.MetricDescriptor.MetricKind;
import com.google.api.MonitoredResource;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.ServiceOptions;
import com.google.cloud.monitoring.v3.MetricServiceClient;
Expand Down Expand Up @@ -98,6 +103,7 @@
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.threeten.bp.Duration;

@RunWith(MockitoJUnitRunner.class)
public class GoogleCloudMetricExporterTest {
Expand All @@ -120,7 +126,7 @@ public void setUp() {
}

@Test
public void testCreateWithConfigurationSucceeds() throws IOException {
public void testCreateWithConfigurationSucceeds() {
MetricConfiguration configuration =
MetricConfiguration.builder()
.setProjectId(aProjectId)
Expand All @@ -130,6 +136,81 @@ public void testCreateWithConfigurationSucceeds() throws IOException {
assertNotNull(exporter);
}

@Test
public void testCreateWithMetricServiceSettingExportSucceeds() throws IOException {
try (MockedStatic<MetricServiceClient> mockedServiceClientClass =
mockStatic(MetricServiceClient.class)) {
MetricServiceSettings.Builder builder = MetricServiceSettings.newBuilder();
builder
.setCredentialsProvider(FixedCredentialsProvider.create(aFakeCredential))
.setEndpoint(MetricConfiguration.DEFAULT_METRIC_SERVICE_ENDPOINT)
.createMetricDescriptorSettings()
.setSimpleTimeoutNoRetries(
Duration.ofMillis(MetricConfiguration.DEFAULT_DEADLINE.toMillis()));
MetricServiceSettings serviceSettings = builder.build();

MetricConfiguration configuration =
MetricConfiguration.builder()
.setProjectId(aProjectId)
.setMetricServiceEndpoint("https://fake-endpoint")
.setInsecureEndpoint(true)
.setCredentials(null)
.setMetricServiceSettings(serviceSettings)
.setDescriptorStrategy(MetricDescriptorStrategy.SEND_ONCE)
.build();
assertNotNull(configuration.getMetricServiceSettings());

// Mock the static method to create a MetricServiceClient to return a mocked object
mockedServiceClientClass
.when(() -> MetricServiceClient.create(eq(configuration.getMetricServiceSettings())))
.thenReturn(mockMetricServiceClient);

MetricExporter exporter = InternalMetricExporter.createWithConfiguration(configuration);
assertNotNull(exporter);

// verify that the MetricServiceClient used in the exporter was created using the
// MetricServiceSettings provided in configuration
mockedServiceClientClass.verify(
times(1), () -> MetricServiceClient.create(eq(configuration.getMetricServiceSettings())));

// verify that export operation on the resulting exporter can still be called
CompletableResultCode result = exporter.export(ImmutableList.of(aMetricData, aHistogram));
assertTrue(result.isSuccess());

// verify that the CreateTimeseries call was invoked on the client generated from the supplied
// MetricServiceSettings object
verify(mockMetricServiceClient, times(1))
.createTimeSeries(projectNameArgCaptor.capture(), timeSeriesArgCaptor.capture());
}
}

@Test
public void testCreateWithMetricServiceSettingRespectsDefaults() throws IOException {
MetricServiceSettings.Builder builder = MetricServiceSettings.newBuilder();
builder
.setCredentialsProvider(FixedCredentialsProvider.create(aFakeCredential))
.setEndpoint("http://custom-endpoint/")
.createMetricDescriptorSettings()
.setSimpleTimeoutNoRetries(Duration.ofMillis(5000));
MetricServiceSettings serviceSettings = builder.build();

MetricConfiguration configuration =
MetricConfiguration.builder()
.setProjectId(aProjectId)
.setMetricServiceSettings(serviceSettings)
.setMetricServiceEndpoint(DEFAULT_METRIC_SERVICE_ENDPOINT)
.build();

// expect the following property values of configuration object to not be affected by setting
// MetricServiceSettings.
assertEquals(DEFAULT_METRIC_SERVICE_ENDPOINT, configuration.getMetricServiceEndpoint());
assertEquals(DEFAULT_RESOURCE_ATTRIBUTES_FILTER, configuration.getResourceAttributesFilter());
assertEquals(DEFAULT_DEADLINE, configuration.getDeadline());
assertEquals(MetricDescriptorStrategy.SEND_ONCE, configuration.getDescriptorStrategy());
assertEquals(DEFAULT_METRIC_SERVICE_ENDPOINT, configuration.getMetricServiceEndpoint());
assertFalse(configuration.getUseServiceTimeSeries());
}

@Test
public void testExportSendsAllDescriptorsOnce() {
MetricExporter exporter =
Expand Down

0 comments on commit 7ca8f25

Please sign in to comment.