Skip to content

Commit 7ca8f25

Browse files
authored
Expose option to provide MetricServiceSettings in MetricConfiguration (#356)
* Add option to provide MetricServiceSettings in MetricConfiguration * Add unit test * Update example with setMetricServiceSettings usage * Add another unit test
1 parent e3d5d6b commit 7ca8f25

5 files changed

Lines changed: 220 additions & 37 deletions

File tree

examples/metrics/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ export GOOGLE_CLOUD_PROJECT="my-awesome-gcp-project-id"
3232
You can run the example application via gradle. From the project root:
3333

3434
```shell
35+
# Running with default exporter config
3536
cd examples/metrics/ && gradle run
37+
38+
# Running with custom exporter config
39+
cd examples/metrics/ && gradle run --args='--custom-config'
3640
```
3741

3842
#### Run the example as a Cloud Run Job

examples/metrics/src/main/java/com/google/cloud/opentelemetry/example/metrics/MetricsExporterExample.java

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,23 @@
1515
*/
1616
package com.google.cloud.opentelemetry.example.metrics;
1717

18+
import static com.google.api.client.util.Preconditions.checkNotNull;
19+
20+
import com.google.api.gax.core.FixedCredentialsProvider;
21+
import com.google.api.gax.grpc.GrpcTransportChannel;
22+
import com.google.api.gax.rpc.FixedTransportChannelProvider;
23+
import com.google.auth.Credentials;
24+
import com.google.auth.oauth2.GoogleCredentials;
25+
import com.google.cloud.monitoring.v3.MetricServiceSettings;
1826
import com.google.cloud.opentelemetry.metric.GoogleCloudMetricExporter;
27+
import com.google.cloud.opentelemetry.metric.MetricConfiguration;
28+
import io.grpc.ManagedChannelBuilder;
1929
import io.opentelemetry.api.metrics.LongCounter;
2030
import io.opentelemetry.api.metrics.Meter;
2131
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
2232
import io.opentelemetry.sdk.metrics.export.MetricExporter;
2333
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
34+
import java.io.IOException;
2435
import java.util.Random;
2536

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

32-
private static void setupMetricExporter() {
33-
MetricExporter metricExporter = GoogleCloudMetricExporter.createWithDefaultConfiguration();
43+
private static MetricConfiguration generateMetricExporterConfig(boolean useDefaultConfig)
44+
throws IOException {
45+
if (useDefaultConfig) {
46+
System.out.println("Using default exporter configuration");
47+
return MetricConfiguration.builder().build();
48+
}
49+
50+
System.out.println("Using custom configuration");
51+
// Configuring exporter through MetricServiceSettings
52+
Credentials credentials = GoogleCredentials.getApplicationDefault();
53+
MetricServiceSettings.Builder metricServiceSettingsBuilder = MetricServiceSettings.newBuilder();
54+
metricServiceSettingsBuilder
55+
.setCredentialsProvider(
56+
FixedCredentialsProvider.create(checkNotNull(credentials, "Credentials not provided.")))
57+
.setTransportChannelProvider(
58+
FixedTransportChannelProvider.create(
59+
GrpcTransportChannel.create(
60+
ManagedChannelBuilder.forTarget(
61+
MetricConfiguration.DEFAULT_METRIC_SERVICE_ENDPOINT)
62+
// default 8 KiB
63+
.maxInboundMetadataSize(16 * 1000)
64+
.build())))
65+
.createMetricDescriptorSettings()
66+
.setSimpleTimeoutNoRetries(
67+
org.threeten.bp.Duration.ofMillis(MetricConfiguration.DEFAULT_DEADLINE.toMillis()))
68+
.build();
69+
70+
// Any properties not set would be retrieved from the default configuration of the exporter.
71+
return MetricConfiguration.builder()
72+
.setMetricServiceSettings(metricServiceSettingsBuilder.build())
73+
.build();
74+
}
75+
76+
private static void setupMetricExporter(MetricConfiguration metricConfiguration) {
77+
MetricExporter metricExporter =
78+
GoogleCloudMetricExporter.createWithConfiguration(metricConfiguration);
3479
METER_PROVIDER =
3580
SdkMeterProvider.builder()
3681
.registerMetricReader(
@@ -68,9 +113,16 @@ private static void doWork(LongCounter counter) {
68113
}
69114

70115
// to run this from command line, execute `gradle run`
71-
public static void main(String[] args) throws InterruptedException {
116+
public static void main(String[] args) throws InterruptedException, IOException {
72117
System.out.println("Starting the metrics-example application");
73-
setupMetricExporter();
118+
boolean useDefaultConfig = true;
119+
if (args.length > 0) {
120+
if (args[0].equals("--custom-config")) {
121+
useDefaultConfig = false;
122+
}
123+
}
124+
setupMetricExporter(generateMetricExporterConfig(useDefaultConfig));
125+
74126
try {
75127
int i = 0;
76128
while (i < 2) {

exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/InternalMetricExporter.java

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -91,36 +91,15 @@ static InternalMetricExporter createWithConfiguration(MetricConfiguration config
9191
throws IOException {
9292
String projectId = configuration.getProjectId();
9393
String prefix = configuration.getPrefix();
94-
MetricServiceSettings.Builder builder = MetricServiceSettings.newBuilder();
95-
// For testing, we need to hack around our gRPC config.
96-
if (configuration.getInsecureEndpoint()) {
97-
builder.setCredentialsProvider(NoCredentialsProvider.create());
98-
builder.setTransportChannelProvider(
99-
FixedTransportChannelProvider.create(
100-
GrpcTransportChannel.create(
101-
ManagedChannelBuilder.forTarget(configuration.getMetricServiceEndpoint())
102-
.usePlaintext()
103-
.build())));
104-
} else {
105-
// For any other endpoint, we force credentials to exist.
106-
Credentials credentials =
107-
configuration.getCredentials() == null
108-
? GoogleCredentials.getApplicationDefault()
109-
: configuration.getCredentials();
110-
111-
builder.setCredentialsProvider(
112-
FixedCredentialsProvider.create(checkNotNull(credentials, "Credentials not provided.")));
113-
builder.setEndpoint(configuration.getMetricServiceEndpoint());
114-
}
115-
builder
116-
.createMetricDescriptorSettings()
117-
.setSimpleTimeoutNoRetries(
118-
org.threeten.bp.Duration.ofMillis(configuration.getDeadline().toMillis()));
94+
MetricServiceSettings serviceClientSettings =
95+
configuration.getMetricServiceSettings() == null
96+
? generateMetricServiceSettings(configuration)
97+
: configuration.getMetricServiceSettings();
11998

12099
return new InternalMetricExporter(
121100
projectId,
122101
prefix,
123-
new CloudMetricClientImpl(MetricServiceClient.create(builder.build())),
102+
new CloudMetricClientImpl(MetricServiceClient.create(serviceClientSettings)),
124103
configuration.getDescriptorStrategy(),
125104
configuration.getResourceAttributesFilter(),
126105
configuration.getUseServiceTimeSeries(),
@@ -146,6 +125,36 @@ static InternalMetricExporter createWithClient(
146125
monitoredResourceDescription);
147126
}
148127

128+
private static MetricServiceSettings generateMetricServiceSettings(
129+
MetricConfiguration configuration) throws IOException {
130+
MetricServiceSettings.Builder builder = MetricServiceSettings.newBuilder();
131+
// For testing, we need to hack around our gRPC config.
132+
if (configuration.getInsecureEndpoint()) {
133+
builder.setCredentialsProvider(NoCredentialsProvider.create());
134+
builder.setTransportChannelProvider(
135+
FixedTransportChannelProvider.create(
136+
GrpcTransportChannel.create(
137+
ManagedChannelBuilder.forTarget(configuration.getMetricServiceEndpoint())
138+
.usePlaintext()
139+
.build())));
140+
} else {
141+
// For any other endpoint, we force credentials to exist.
142+
Credentials credentials =
143+
configuration.getCredentials() == null
144+
? GoogleCredentials.getApplicationDefault()
145+
: configuration.getCredentials();
146+
147+
builder.setCredentialsProvider(
148+
FixedCredentialsProvider.create(checkNotNull(credentials, "Credentials not provided.")));
149+
builder.setEndpoint(configuration.getMetricServiceEndpoint());
150+
}
151+
builder
152+
.createMetricDescriptorSettings()
153+
.setSimpleTimeoutNoRetries(
154+
org.threeten.bp.Duration.ofMillis(configuration.getDeadline().toMillis()));
155+
return builder.build();
156+
}
157+
149158
private void exportDescriptor(MetricDescriptor descriptor) {
150159
logger.trace("Creating metric descriptor: {}", descriptor);
151160
metricServiceClient.createMetricDescriptor(

exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/MetricConfiguration.java

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.auth.Credentials;
2121
import com.google.auto.value.AutoValue;
2222
import com.google.cloud.ServiceOptions;
23+
import com.google.cloud.monitoring.v3.MetricServiceSettings;
2324
import com.google.cloud.monitoring.v3.stub.MetricServiceStubSettings;
2425
import com.google.common.annotations.VisibleForTesting;
2526
import com.google.common.base.Preconditions;
@@ -43,6 +44,14 @@
4344
@AutoValue
4445
@Immutable
4546
public abstract class MetricConfiguration {
47+
static final String DEFAULT_PREFIX = "workload.googleapis.com";
48+
49+
public static final Duration DEFAULT_DEADLINE =
50+
Duration.ofSeconds(12, 0); // Consistent with Cloud Monitoring's timeout
51+
52+
public static final String DEFAULT_METRIC_SERVICE_ENDPOINT =
53+
MetricServiceStubSettings.getDefaultEndpoint();
54+
4655
/** Resource attribute filter that disables addition of resource attributes to metric labels. */
4756
public static final Predicate<AttributeKey<?>> NO_RESOURCE_ATTRIBUTES = attributeKey -> false;
4857

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

62-
static final String DEFAULT_PREFIX = "workload.googleapis.com";
63-
64-
private static final Duration DEFAULT_DEADLINE =
65-
Duration.ofSeconds(12, 0); // Consistent with Cloud Monitoring's timeout
66-
6771
MetricConfiguration() {}
6872

6973
/**
@@ -168,6 +172,16 @@ public final String getProjectId() {
168172
*/
169173
public abstract MonitoredResourceDescription getMonitoredResourceDescription();
170174

175+
/**
176+
* Returns the {@link MetricServiceSettings} instance used to configure the service client used to
177+
* connect to Monitoring API.
178+
*
179+
* @return The {@link MetricServiceSettings} object that is used to configure the internal service
180+
* client.
181+
*/
182+
@Nullable
183+
public abstract MetricServiceSettings getMetricServiceSettings();
184+
171185
@VisibleForTesting
172186
abstract boolean getInsecureEndpoint();
173187

@@ -194,7 +208,7 @@ public static Builder builder() {
194208
.setUseServiceTimeSeries(false)
195209
.setResourceAttributesFilter(DEFAULT_RESOURCE_ATTRIBUTES_FILTER)
196210
.setMonitoredResourceDescription(EMPTY_MONITORED_RESOURCE_DESCRIPTION)
197-
.setMetricServiceEndpoint(MetricServiceStubSettings.getDefaultEndpoint());
211+
.setMetricServiceEndpoint(DEFAULT_METRIC_SERVICE_ENDPOINT);
198212
}
199213

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

299+
/**
300+
* Sets the options used to configure the {@link
301+
* com.google.cloud.monitoring.v3.MetricServiceClient} used to interact with the Cloud
302+
* Monitoring API. This is for advanced usage and must be configured carefully.
303+
*
304+
* <p>Providing MetricServiceSettings will cause the exporter to ignore the values configured
305+
* using:
306+
*
307+
* <ul>
308+
* <li>{@link MetricConfiguration.Builder#setInsecureEndpoint(boolean)}
309+
* <li>{@link MetricConfiguration.Builder#setCredentials(Credentials)}
310+
* <li>{@link MetricConfiguration.Builder#setMetricServiceEndpoint(String)}
311+
* </ul>
312+
*
313+
* The intended effect of setting these values in the configuration should instead be achieved
314+
* by configuring the {@link MetricServiceSettings} object.
315+
*
316+
* @param metricServiceSettings the {@link MetricServiceSettings} containing the configured
317+
* options.
318+
* @return this.
319+
*/
320+
public abstract Builder setMetricServiceSettings(MetricServiceSettings metricServiceSettings);
321+
285322
@VisibleForTesting
286323
abstract Builder setInsecureEndpoint(boolean value);
287324

exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/GoogleCloudMetricExporterTest.java

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import static com.google.cloud.opentelemetry.metric.FakeData.aTraceId;
3434
import static com.google.cloud.opentelemetry.metric.FakeData.anInstrumentationLibraryInfo;
3535
import static com.google.cloud.opentelemetry.metric.FakeData.googleComputeServiceMetricData;
36+
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_DEADLINE;
37+
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_METRIC_SERVICE_ENDPOINT;
3638
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_PREFIX;
3739
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_RESOURCE_ATTRIBUTES_FILTER;
3840
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.EMPTY_MONITORED_RESOURCE_DESCRIPTION;
@@ -44,6 +46,8 @@
4446
import static org.junit.Assert.assertNotNull;
4547
import static org.junit.Assert.assertTrue;
4648
import static org.mockito.ArgumentMatchers.any;
49+
import static org.mockito.ArgumentMatchers.eq;
50+
import static org.mockito.Mockito.mockStatic;
4751
import static org.mockito.Mockito.times;
4852
import static org.mockito.Mockito.verify;
4953
import static org.mockito.Mockito.when;
@@ -58,6 +62,7 @@
5862
import com.google.api.MetricDescriptor;
5963
import com.google.api.MetricDescriptor.MetricKind;
6064
import com.google.api.MonitoredResource;
65+
import com.google.api.gax.core.FixedCredentialsProvider;
6166
import com.google.auth.oauth2.GoogleCredentials;
6267
import com.google.cloud.ServiceOptions;
6368
import com.google.cloud.monitoring.v3.MetricServiceClient;
@@ -98,6 +103,7 @@
98103
import org.mockito.MockedStatic;
99104
import org.mockito.Mockito;
100105
import org.mockito.junit.MockitoJUnitRunner;
106+
import org.threeten.bp.Duration;
101107

102108
@RunWith(MockitoJUnitRunner.class)
103109
public class GoogleCloudMetricExporterTest {
@@ -120,7 +126,7 @@ public void setUp() {
120126
}
121127

122128
@Test
123-
public void testCreateWithConfigurationSucceeds() throws IOException {
129+
public void testCreateWithConfigurationSucceeds() {
124130
MetricConfiguration configuration =
125131
MetricConfiguration.builder()
126132
.setProjectId(aProjectId)
@@ -130,6 +136,81 @@ public void testCreateWithConfigurationSucceeds() throws IOException {
130136
assertNotNull(exporter);
131137
}
132138

139+
@Test
140+
public void testCreateWithMetricServiceSettingExportSucceeds() throws IOException {
141+
try (MockedStatic<MetricServiceClient> mockedServiceClientClass =
142+
mockStatic(MetricServiceClient.class)) {
143+
MetricServiceSettings.Builder builder = MetricServiceSettings.newBuilder();
144+
builder
145+
.setCredentialsProvider(FixedCredentialsProvider.create(aFakeCredential))
146+
.setEndpoint(MetricConfiguration.DEFAULT_METRIC_SERVICE_ENDPOINT)
147+
.createMetricDescriptorSettings()
148+
.setSimpleTimeoutNoRetries(
149+
Duration.ofMillis(MetricConfiguration.DEFAULT_DEADLINE.toMillis()));
150+
MetricServiceSettings serviceSettings = builder.build();
151+
152+
MetricConfiguration configuration =
153+
MetricConfiguration.builder()
154+
.setProjectId(aProjectId)
155+
.setMetricServiceEndpoint("https://fake-endpoint")
156+
.setInsecureEndpoint(true)
157+
.setCredentials(null)
158+
.setMetricServiceSettings(serviceSettings)
159+
.setDescriptorStrategy(MetricDescriptorStrategy.SEND_ONCE)
160+
.build();
161+
assertNotNull(configuration.getMetricServiceSettings());
162+
163+
// Mock the static method to create a MetricServiceClient to return a mocked object
164+
mockedServiceClientClass
165+
.when(() -> MetricServiceClient.create(eq(configuration.getMetricServiceSettings())))
166+
.thenReturn(mockMetricServiceClient);
167+
168+
MetricExporter exporter = InternalMetricExporter.createWithConfiguration(configuration);
169+
assertNotNull(exporter);
170+
171+
// verify that the MetricServiceClient used in the exporter was created using the
172+
// MetricServiceSettings provided in configuration
173+
mockedServiceClientClass.verify(
174+
times(1), () -> MetricServiceClient.create(eq(configuration.getMetricServiceSettings())));
175+
176+
// verify that export operation on the resulting exporter can still be called
177+
CompletableResultCode result = exporter.export(ImmutableList.of(aMetricData, aHistogram));
178+
assertTrue(result.isSuccess());
179+
180+
// verify that the CreateTimeseries call was invoked on the client generated from the supplied
181+
// MetricServiceSettings object
182+
verify(mockMetricServiceClient, times(1))
183+
.createTimeSeries(projectNameArgCaptor.capture(), timeSeriesArgCaptor.capture());
184+
}
185+
}
186+
187+
@Test
188+
public void testCreateWithMetricServiceSettingRespectsDefaults() throws IOException {
189+
MetricServiceSettings.Builder builder = MetricServiceSettings.newBuilder();
190+
builder
191+
.setCredentialsProvider(FixedCredentialsProvider.create(aFakeCredential))
192+
.setEndpoint("http://custom-endpoint/")
193+
.createMetricDescriptorSettings()
194+
.setSimpleTimeoutNoRetries(Duration.ofMillis(5000));
195+
MetricServiceSettings serviceSettings = builder.build();
196+
197+
MetricConfiguration configuration =
198+
MetricConfiguration.builder()
199+
.setProjectId(aProjectId)
200+
.setMetricServiceSettings(serviceSettings)
201+
.setMetricServiceEndpoint(DEFAULT_METRIC_SERVICE_ENDPOINT)
202+
.build();
203+
204+
// expect the following property values of configuration object to not be affected by setting
205+
// MetricServiceSettings.
206+
assertEquals(DEFAULT_METRIC_SERVICE_ENDPOINT, configuration.getMetricServiceEndpoint());
207+
assertEquals(DEFAULT_RESOURCE_ATTRIBUTES_FILTER, configuration.getResourceAttributesFilter());
208+
assertEquals(DEFAULT_DEADLINE, configuration.getDeadline());
209+
assertEquals(MetricDescriptorStrategy.SEND_ONCE, configuration.getDescriptorStrategy());
210+
assertEquals(DEFAULT_METRIC_SERVICE_ENDPOINT, configuration.getMetricServiceEndpoint());
211+
assertFalse(configuration.getUseServiceTimeSeries());
212+
}
213+
133214
@Test
134215
public void testExportSendsAllDescriptorsOnce() {
135216
MetricExporter exporter =

0 commit comments

Comments
 (0)