Skip to content

Commit 4d1da47

Browse files
authored
Add support for createServiceTimeSeries (#318)
* Update export logic for createServiceTimeSeries * Add test case for use with CreateServiceTimeSeries * Make serviceTimeSeries builder config public * Update configuration test * Switch functional interface to Consumer
1 parent af5e104 commit 4d1da47

File tree

7 files changed

+129
-26
lines changed

7 files changed

+129
-26
lines changed

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,24 @@ public interface CloudMetricClient {
3131
MetricDescriptor createMetricDescriptor(CreateMetricDescriptorRequest request);
3232

3333
/**
34-
* Send a timeseries to Cloud Monitoring.
34+
* Send a time series to Cloud Monitoring.
3535
*
36-
* @param name The name of the project where we write the timeseries.
37-
* @param timeSeries The list of timeseries to write.
38-
* <p>Note: This can only take one point at per timeseries.
36+
* @param name The name of the project where we write the time series.
37+
* @param timeSeries The list of time series to write.
38+
* <p>Note: This can only take one point at per time series.
3939
*/
4040
void createTimeSeries(ProjectName name, List<TimeSeries> timeSeries);
4141

42+
/**
43+
* Send a service time series to Cloud Monitoring. A service time series is a time series for a
44+
* metric from a Google Cloud service. This method should not be used for sending custom metrics.
45+
*
46+
* @param name The name of the project where we write the time series.
47+
* @param timeSeries The list of time series to write.
48+
* <p>Note: This can only take one point at per time series.
49+
*/
50+
void createServiceTimeSeries(ProjectName name, List<TimeSeries> timeSeries);
51+
4252
/** Shutdown this client, cleaning up any resources. */
4353
void shutdown();
4454
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public void createTimeSeries(ProjectName name, List<TimeSeries> timeSeries) {
4040
this.metricServiceClient.createTimeSeries(name, timeSeries);
4141
}
4242

43+
@Override
44+
public void createServiceTimeSeries(ProjectName name, List<TimeSeries> timeSeries) {
45+
this.metricServiceClient.createServiceTimeSeries(name, timeSeries);
46+
}
47+
4348
@Override
4449
public void shutdown() {
4550
this.metricServiceClient.shutdown();

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

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.util.ArrayList;
4646
import java.util.Collection;
4747
import java.util.List;
48+
import java.util.function.Consumer;
4849
import java.util.function.Predicate;
4950
import javax.annotation.Nonnull;
5051
import org.slf4j.Logger;
@@ -66,18 +67,21 @@ class InternalMetricExporter implements MetricExporter {
6667
private final String prefix;
6768
private final MetricDescriptorStrategy metricDescriptorStrategy;
6869
private final Predicate<AttributeKey<?>> resourceAttributesFilter;
70+
private final boolean useCreateServiceTimeSeries;
6971

7072
InternalMetricExporter(
7173
String projectId,
7274
String prefix,
7375
CloudMetricClient client,
7476
MetricDescriptorStrategy descriptorStrategy,
75-
Predicate<AttributeKey<?>> resourceAttributesFilter) {
77+
Predicate<AttributeKey<?>> resourceAttributesFilter,
78+
boolean useCreateServiceTimeSeries) {
7679
this.projectId = projectId;
7780
this.prefix = prefix;
7881
this.metricServiceClient = client;
7982
this.metricDescriptorStrategy = descriptorStrategy;
8083
this.resourceAttributesFilter = resourceAttributesFilter;
84+
this.useCreateServiceTimeSeries = useCreateServiceTimeSeries;
8185
}
8286

8387
static InternalMetricExporter createWithConfiguration(MetricConfiguration configuration)
@@ -115,7 +119,8 @@ static InternalMetricExporter createWithConfiguration(MetricConfiguration config
115119
prefix,
116120
new CloudMetricClientImpl(MetricServiceClient.create(builder.build())),
117121
configuration.getDescriptorStrategy(),
118-
configuration.getResourceAttributesFilter());
122+
configuration.getResourceAttributesFilter(),
123+
configuration.getUseServiceTimeSeries());
119124
}
120125

121126
@VisibleForTesting
@@ -124,9 +129,15 @@ static InternalMetricExporter createWithClient(
124129
String prefix,
125130
CloudMetricClient metricServiceClient,
126131
MetricDescriptorStrategy descriptorStrategy,
127-
Predicate<AttributeKey<?>> resourceAttributesFilter) {
132+
Predicate<AttributeKey<?>> resourceAttributesFilter,
133+
boolean useCreateServiceTimeSeries) {
128134
return new InternalMetricExporter(
129-
projectId, prefix, metricServiceClient, descriptorStrategy, resourceAttributesFilter);
135+
projectId,
136+
prefix,
137+
metricServiceClient,
138+
descriptorStrategy,
139+
resourceAttributesFilter,
140+
useCreateServiceTimeSeries);
130141
}
131142

132143
private void exportDescriptor(MetricDescriptor descriptor) {
@@ -197,32 +208,46 @@ public CompletableResultCode export(Collection<MetricData> metrics) {
197208
// }
198209
}
199210
// Update metric descriptors based on configured strategy.
200-
try {
201-
Collection<MetricDescriptor> descriptors = builder.getDescriptors();
202-
if (!descriptors.isEmpty()) {
203-
metricDescriptorStrategy.exportDescriptors(descriptors, this::exportDescriptor);
204-
}
205-
} catch (Exception e) {
206-
logger.warn("Failed to create metric descriptors", e);
207-
}
211+
exportDescriptors(builder);
208212

209213
List<TimeSeries> series = builder.getTimeSeries();
210-
createTimeSeriesBatch(metricServiceClient, ProjectName.of(projectId), series);
214+
Consumer<List<TimeSeries>> timeSeriesGenerator =
215+
timeSeries -> {
216+
if (useCreateServiceTimeSeries) {
217+
metricServiceClient.createServiceTimeSeries(ProjectName.of(projectId), timeSeries);
218+
} else {
219+
metricServiceClient.createTimeSeries(ProjectName.of(projectId), timeSeries);
220+
}
221+
};
222+
createTimeSeriesBatch(series, timeSeriesGenerator);
211223
// TODO: better error reporting.
212224
if (series.size() < metrics.size()) {
213225
return CompletableResultCode.ofFailure();
214226
}
215227
return CompletableResultCode.ofSuccess();
216228
}
217229

230+
private void exportDescriptors(MetricTimeSeriesBuilder timeSeriesBuilder) {
231+
if (useCreateServiceTimeSeries) {
232+
// do not export metric descriptors when using createServiceTimeSeries
233+
return;
234+
}
235+
try {
236+
Collection<MetricDescriptor> descriptors = timeSeriesBuilder.getDescriptors();
237+
if (!descriptors.isEmpty()) {
238+
metricDescriptorStrategy.exportDescriptors(descriptors, this::exportDescriptor);
239+
}
240+
} catch (Exception e) {
241+
logger.warn("Failed to create metric descriptors", e);
242+
}
243+
}
244+
218245
// Fragment metrics into batches and send to GCM.
219-
private static void createTimeSeriesBatch(
220-
CloudMetricClient metricServiceClient,
221-
ProjectName projectName,
222-
List<TimeSeries> allTimesSeries) {
246+
private void createTimeSeriesBatch(
247+
List<TimeSeries> allTimesSeries, Consumer<List<TimeSeries>> timeSeriesGenerator) {
223248
List<List<TimeSeries>> batches = Lists.partition(allTimesSeries, MAX_BATCH_SIZE);
224249
for (List<TimeSeries> timeSeries : batches) {
225-
metricServiceClient.createTimeSeries(projectName, new ArrayList<>(timeSeries));
250+
timeSeriesGenerator.accept(new ArrayList<>(timeSeries));
226251
}
227252
}
228253

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.opentelemetry.api.common.AttributeKey;
2929
import io.opentelemetry.semconv.ResourceAttributes;
3030
import java.time.Duration;
31+
import java.util.List;
3132
import java.util.function.Predicate;
3233
import java.util.function.Supplier;
3334
import javax.annotation.Nullable;
@@ -141,6 +142,15 @@ public final String getProjectId() {
141142
*/
142143
public abstract Predicate<AttributeKey<?>> getResourceAttributesFilter();
143144

145+
/**
146+
* Returns a boolean indicating if the {@link MetricConfiguration} is configured to write to a
147+
* metric generated from a Google Cloud Service.
148+
*
149+
* @return true if the {@link MetricConfiguration} is configured to write to a metric generated
150+
* from a Google Cloud Service, false otherwise.
151+
*/
152+
public abstract boolean getUseServiceTimeSeries();
153+
144154
@VisibleForTesting
145155
abstract boolean getInsecureEndpoint();
146156

@@ -164,6 +174,7 @@ public static Builder builder() {
164174
.setDeadline(DEFAULT_DEADLINE)
165175
.setDescriptorStrategy(MetricDescriptorStrategy.SEND_ONCE)
166176
.setInsecureEndpoint(false)
177+
.setUseServiceTimeSeries(false)
167178
.setResourceAttributesFilter(DEFAULT_RESOURCE_ATTRIBUTES_FILTER)
168179
.setMetricServiceEndpoint(MetricServiceStubSettings.getDefaultEndpoint());
169180
}
@@ -215,6 +226,18 @@ public final Builder setProjectId(String projectId) {
215226
/** Sets the endpoint where to write Metrics. Defaults to monitoring.googleapis.com:443. */
216227
public abstract Builder setMetricServiceEndpoint(String endpoint);
217228

229+
/**
230+
* Sets the {@link MetricConfiguration} to configure the exporter to write metrics via {@link
231+
* com.google.cloud.monitoring.v3.MetricServiceClient#createServiceTimeSeries(String, List)}
232+
* method. By default, this is false.
233+
*
234+
* @param useServiceTimeSeries a boolean indicating whether to use {@link
235+
* com.google.cloud.monitoring.v3.MetricServiceClient#createServiceTimeSeries(String, List)}
236+
* method for writing metrics to Google Cloud Monitoring.
237+
* @return this
238+
*/
239+
public abstract Builder setUseServiceTimeSeries(boolean useServiceTimeSeries);
240+
218241
/**
219242
* Set a filter to determine which resource attributes to add to metrics as metric labels. By
220243
* default, it adds service.name, service.namespace, and service.instance.id. This is

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ public class FakeData {
119119
ImmutableSumData.create(
120120
true, AggregationTemporality.CUMULATIVE, ImmutableList.of(aLongPoint)));
121121

122+
static final MetricData googleComputeServiceMetricData =
123+
ImmutableMetricData.createLongSum(
124+
aGceResource,
125+
anInstrumentationLibraryInfo,
126+
"guest/disk/io_time",
127+
"description",
128+
"ns",
129+
ImmutableSumData.create(
130+
true, AggregationTemporality.CUMULATIVE, ImmutableList.of(aLongPoint)));
131+
122132
static final String aTraceId = "00000000000000000000000000000001";
123133
static final String aSpanId = "0000000000000002";
124134
static final SpanContext aSpanContext =

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import static com.google.cloud.opentelemetry.metric.FakeData.aSpanId;
3131
import static com.google.cloud.opentelemetry.metric.FakeData.aTraceId;
3232
import static com.google.cloud.opentelemetry.metric.FakeData.anInstrumentationLibraryInfo;
33+
import static com.google.cloud.opentelemetry.metric.FakeData.googleComputeServiceMetricData;
3334
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_PREFIX;
3435
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_RESOURCE_ATTRIBUTES_FILTER;
3536
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.NO_RESOURCE_ATTRIBUTES;
@@ -132,7 +133,8 @@ public void testExportSendsAllDescriptorsOnce() {
132133
DEFAULT_PREFIX,
133134
mockClient,
134135
MetricDescriptorStrategy.SEND_ONCE,
135-
DEFAULT_RESOURCE_ATTRIBUTES_FILTER);
136+
DEFAULT_RESOURCE_ATTRIBUTES_FILTER,
137+
false);
136138
CompletableResultCode result = exporter.export(ImmutableList.of(aMetricData, aHistogram));
137139
assertTrue(result.isSuccess());
138140
CompletableResultCode result2 = exporter.export(ImmutableList.of(aMetricData, aHistogram));
@@ -241,7 +243,8 @@ public void testExportSucceeds() {
241243
DEFAULT_PREFIX,
242244
mockClient,
243245
MetricDescriptorStrategy.ALWAYS_SEND,
244-
DEFAULT_RESOURCE_ATTRIBUTES_FILTER);
246+
DEFAULT_RESOURCE_ATTRIBUTES_FILTER,
247+
false);
245248

246249
CompletableResultCode result = exporter.export(ImmutableList.of(aMetricData));
247250
verify(mockClient, times(1)).createMetricDescriptor(metricDescriptorCaptor.capture());
@@ -362,7 +365,8 @@ public void testExportWithHistogram_Succeeds() {
362365
DEFAULT_PREFIX,
363366
mockClient,
364367
MetricDescriptorStrategy.ALWAYS_SEND,
365-
DEFAULT_RESOURCE_ATTRIBUTES_FILTER);
368+
DEFAULT_RESOURCE_ATTRIBUTES_FILTER,
369+
false);
366370
CompletableResultCode result = exporter.export(ImmutableList.of(aHistogram));
367371
verify(mockClient, times(1)).createMetricDescriptor(metricDescriptorCaptor.capture());
368372
verify(mockClient, times(1))
@@ -382,7 +386,8 @@ public void testExportWithNonSupportedMetricTypeReturnsFailure() {
382386
DEFAULT_PREFIX,
383387
mockClient,
384388
MetricDescriptorStrategy.ALWAYS_SEND,
385-
NO_RESOURCE_ATTRIBUTES);
389+
NO_RESOURCE_ATTRIBUTES,
390+
false);
386391

387392
MetricData metricData =
388393
ImmutableMetricData.createDoubleSummary(
@@ -452,6 +457,26 @@ public void verifyExporterCreationErrorDoesNotBreakMetricExporter() {
452457
}
453458
}
454459

460+
@Test
461+
public void verifyExporterExportGoogleServiceMetrics() {
462+
MetricExporter exporter =
463+
InternalMetricExporter.createWithClient(
464+
aProjectId,
465+
"compute.googleapis.com",
466+
mockClient,
467+
MetricDescriptorStrategy.ALWAYS_SEND,
468+
NO_RESOURCE_ATTRIBUTES,
469+
true);
470+
471+
CompletableResultCode result =
472+
exporter.export(ImmutableList.of(googleComputeServiceMetricData));
473+
verify(mockClient, times(0)).createMetricDescriptor(any());
474+
verify(mockClient, times(0)).createTimeSeries(any(ProjectName.class), any());
475+
verify(mockClient, times(1)).createServiceTimeSeries(any(ProjectName.class), any());
476+
477+
assertTrue(result.isSuccess());
478+
}
479+
455480
private void generateOpenTelemetryUsingGoogleCloudMetricExporter(MetricExporter metricExporter) {
456481
SdkMeterProvider meterProvider =
457482
SdkMeterProvider.builder()

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
package com.google.cloud.opentelemetry.metric;
1717

1818
import static org.junit.Assert.assertEquals;
19+
import static org.junit.Assert.assertFalse;
1920
import static org.junit.Assert.assertNull;
2021
import static org.junit.Assert.assertThrows;
22+
import static org.junit.Assert.assertTrue;
2123

2224
import com.google.auth.Credentials;
2325
import com.google.auth.oauth2.AccessToken;
@@ -48,6 +50,7 @@ public void testDefaultConfigurationSucceeds() {
4850

4951
assertNull(configuration.getCredentials());
5052
assertEquals(PROJECT_ID, configuration.getProjectId());
53+
assertFalse(configuration.getUseServiceTimeSeries());
5154
}
5255

5356
@Test
@@ -58,11 +61,13 @@ public void testSetAllConfigurationFieldsSucceeds() {
5861
.setProjectId(PROJECT_ID)
5962
.setCredentials(FAKE_CREDENTIALS)
6063
.setResourceAttributesFilter(allowAllPredicate)
64+
.setUseServiceTimeSeries(true)
6165
.build();
6266

6367
assertEquals(FAKE_CREDENTIALS, configuration.getCredentials());
6468
assertEquals(PROJECT_ID, configuration.getProjectId());
6569
assertEquals(allowAllPredicate, configuration.getResourceAttributesFilter());
70+
assertTrue(configuration.getUseServiceTimeSeries());
6671
}
6772

6873
@Test

0 commit comments

Comments
 (0)