Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for createServiceTimeSeries #318

Merged
merged 5 commits into from
Apr 3, 2024
Merged
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
Expand Up @@ -31,14 +31,24 @@ public interface CloudMetricClient {
MetricDescriptor createMetricDescriptor(CreateMetricDescriptorRequest request);

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

/**
* Send a service time series to Cloud Monitoring. A service time series is a time series for a
* metric from a Google Cloud service. This method should not be used for sending custom metrics.
*
* @param name The name of the project where we write the time series.
* @param timeSeries The list of time series to write.
* <p>Note: This can only take one point at per time series.
*/
void createServiceTimeSeries(ProjectName name, List<TimeSeries> timeSeries);

/** Shutdown this client, cleaning up any resources. */
void shutdown();
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public void createTimeSeries(ProjectName name, List<TimeSeries> timeSeries) {
this.metricServiceClient.createTimeSeries(name, timeSeries);
}

@Override
public void createServiceTimeSeries(ProjectName name, List<TimeSeries> timeSeries) {
this.metricServiceClient.createServiceTimeSeries(name, timeSeries);
}

@Override
public void shutdown() {
this.metricServiceClient.shutdown();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import org.slf4j.Logger;
Expand All @@ -66,18 +67,21 @@ class InternalMetricExporter implements MetricExporter {
private final String prefix;
private final MetricDescriptorStrategy metricDescriptorStrategy;
private final Predicate<AttributeKey<?>> resourceAttributesFilter;
private final boolean useCreateServiceTimeSeries;

InternalMetricExporter(
String projectId,
String prefix,
CloudMetricClient client,
MetricDescriptorStrategy descriptorStrategy,
Predicate<AttributeKey<?>> resourceAttributesFilter) {
Predicate<AttributeKey<?>> resourceAttributesFilter,
boolean useCreateServiceTimeSeries) {
this.projectId = projectId;
this.prefix = prefix;
this.metricServiceClient = client;
this.metricDescriptorStrategy = descriptorStrategy;
this.resourceAttributesFilter = resourceAttributesFilter;
this.useCreateServiceTimeSeries = useCreateServiceTimeSeries;
}

static InternalMetricExporter createWithConfiguration(MetricConfiguration configuration)
Expand Down Expand Up @@ -115,7 +119,8 @@ static InternalMetricExporter createWithConfiguration(MetricConfiguration config
prefix,
new CloudMetricClientImpl(MetricServiceClient.create(builder.build())),
configuration.getDescriptorStrategy(),
configuration.getResourceAttributesFilter());
configuration.getResourceAttributesFilter(),
configuration.getUseServiceTimeSeries());
}

@VisibleForTesting
Expand All @@ -124,9 +129,15 @@ static InternalMetricExporter createWithClient(
String prefix,
CloudMetricClient metricServiceClient,
MetricDescriptorStrategy descriptorStrategy,
Predicate<AttributeKey<?>> resourceAttributesFilter) {
Predicate<AttributeKey<?>> resourceAttributesFilter,
boolean useCreateServiceTimeSeries) {
return new InternalMetricExporter(
projectId, prefix, metricServiceClient, descriptorStrategy, resourceAttributesFilter);
projectId,
prefix,
metricServiceClient,
descriptorStrategy,
resourceAttributesFilter,
useCreateServiceTimeSeries);
}

private void exportDescriptor(MetricDescriptor descriptor) {
Expand Down Expand Up @@ -197,32 +208,46 @@ public CompletableResultCode export(Collection<MetricData> metrics) {
// }
}
// Update metric descriptors based on configured strategy.
try {
Collection<MetricDescriptor> descriptors = builder.getDescriptors();
if (!descriptors.isEmpty()) {
metricDescriptorStrategy.exportDescriptors(descriptors, this::exportDescriptor);
}
} catch (Exception e) {
logger.warn("Failed to create metric descriptors", e);
}
exportDescriptors(builder);

List<TimeSeries> series = builder.getTimeSeries();
createTimeSeriesBatch(metricServiceClient, ProjectName.of(projectId), series);
Consumer<List<TimeSeries>> timeSeriesGenerator =
timeSeries -> {
if (useCreateServiceTimeSeries) {
metricServiceClient.createServiceTimeSeries(ProjectName.of(projectId), timeSeries);
} else {
metricServiceClient.createTimeSeries(ProjectName.of(projectId), timeSeries);
}
};
createTimeSeriesBatch(series, timeSeriesGenerator);
// TODO: better error reporting.
if (series.size() < metrics.size()) {
return CompletableResultCode.ofFailure();
}
return CompletableResultCode.ofSuccess();
}

private void exportDescriptors(MetricTimeSeriesBuilder timeSeriesBuilder) {
if (useCreateServiceTimeSeries) {
// do not export metric descriptors when using createServiceTimeSeries
return;
}
try {
Collection<MetricDescriptor> descriptors = timeSeriesBuilder.getDescriptors();
if (!descriptors.isEmpty()) {
metricDescriptorStrategy.exportDescriptors(descriptors, this::exportDescriptor);
}
} catch (Exception e) {
logger.warn("Failed to create metric descriptors", e);
}
}

// Fragment metrics into batches and send to GCM.
private static void createTimeSeriesBatch(
CloudMetricClient metricServiceClient,
ProjectName projectName,
List<TimeSeries> allTimesSeries) {
private void createTimeSeriesBatch(
List<TimeSeries> allTimesSeries, Consumer<List<TimeSeries>> timeSeriesGenerator) {
List<List<TimeSeries>> batches = Lists.partition(allTimesSeries, MAX_BATCH_SIZE);
for (List<TimeSeries> timeSeries : batches) {
metricServiceClient.createTimeSeries(projectName, new ArrayList<>(timeSeries));
timeSeriesGenerator.accept(new ArrayList<>(timeSeries));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.semconv.ResourceAttributes;
import java.time.Duration;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -141,6 +142,15 @@ public final String getProjectId() {
*/
public abstract Predicate<AttributeKey<?>> getResourceAttributesFilter();

/**
* Returns a boolean indicating if the {@link MetricConfiguration} is configured to write to a
* metric generated from a Google Cloud Service.
*
* @return true if the {@link MetricConfiguration} is configured to write to a metric generated
* from a Google Cloud Service, false otherwise.
*/
public abstract boolean getUseServiceTimeSeries();

@VisibleForTesting
abstract boolean getInsecureEndpoint();

Expand All @@ -164,6 +174,7 @@ public static Builder builder() {
.setDeadline(DEFAULT_DEADLINE)
.setDescriptorStrategy(MetricDescriptorStrategy.SEND_ONCE)
.setInsecureEndpoint(false)
.setUseServiceTimeSeries(false)
.setResourceAttributesFilter(DEFAULT_RESOURCE_ATTRIBUTES_FILTER)
.setMetricServiceEndpoint(MetricServiceStubSettings.getDefaultEndpoint());
}
Expand Down Expand Up @@ -215,6 +226,18 @@ public final Builder setProjectId(String projectId) {
/** Sets the endpoint where to write Metrics. Defaults to monitoring.googleapis.com:443. */
public abstract Builder setMetricServiceEndpoint(String endpoint);

/**
* Sets the {@link MetricConfiguration} to configure the exporter to write metrics via {@link
* com.google.cloud.monitoring.v3.MetricServiceClient#createServiceTimeSeries(String, List)}
* method. By default, this is false.
*
* @param useServiceTimeSeries a boolean indicating whether to use {@link
* com.google.cloud.monitoring.v3.MetricServiceClient#createServiceTimeSeries(String, List)}
* method for writing metrics to Google Cloud Monitoring.
* @return this
*/
public abstract Builder setUseServiceTimeSeries(boolean useServiceTimeSeries);
psx95 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Set a filter to determine which resource attributes to add to metrics as metric labels. By
* default, it adds service.name, service.namespace, and service.instance.id. This is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ public class FakeData {
ImmutableSumData.create(
true, AggregationTemporality.CUMULATIVE, ImmutableList.of(aLongPoint)));

static final MetricData googleComputeServiceMetricData =
ImmutableMetricData.createLongSum(
aGceResource,
anInstrumentationLibraryInfo,
"guest/disk/io_time",
"description",
"ns",
ImmutableSumData.create(
true, AggregationTemporality.CUMULATIVE, ImmutableList.of(aLongPoint)));

static final String aTraceId = "00000000000000000000000000000001";
static final String aSpanId = "0000000000000002";
static final SpanContext aSpanContext =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static com.google.cloud.opentelemetry.metric.FakeData.aSpanId;
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_PREFIX;
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_RESOURCE_ATTRIBUTES_FILTER;
import static com.google.cloud.opentelemetry.metric.MetricConfiguration.NO_RESOURCE_ATTRIBUTES;
Expand Down Expand Up @@ -132,7 +133,8 @@ public void testExportSendsAllDescriptorsOnce() {
DEFAULT_PREFIX,
mockClient,
MetricDescriptorStrategy.SEND_ONCE,
DEFAULT_RESOURCE_ATTRIBUTES_FILTER);
DEFAULT_RESOURCE_ATTRIBUTES_FILTER,
false);
CompletableResultCode result = exporter.export(ImmutableList.of(aMetricData, aHistogram));
assertTrue(result.isSuccess());
CompletableResultCode result2 = exporter.export(ImmutableList.of(aMetricData, aHistogram));
Expand Down Expand Up @@ -241,7 +243,8 @@ public void testExportSucceeds() {
DEFAULT_PREFIX,
mockClient,
MetricDescriptorStrategy.ALWAYS_SEND,
DEFAULT_RESOURCE_ATTRIBUTES_FILTER);
DEFAULT_RESOURCE_ATTRIBUTES_FILTER,
false);

CompletableResultCode result = exporter.export(ImmutableList.of(aMetricData));
verify(mockClient, times(1)).createMetricDescriptor(metricDescriptorCaptor.capture());
Expand Down Expand Up @@ -362,7 +365,8 @@ public void testExportWithHistogram_Succeeds() {
DEFAULT_PREFIX,
mockClient,
MetricDescriptorStrategy.ALWAYS_SEND,
DEFAULT_RESOURCE_ATTRIBUTES_FILTER);
DEFAULT_RESOURCE_ATTRIBUTES_FILTER,
false);
CompletableResultCode result = exporter.export(ImmutableList.of(aHistogram));
verify(mockClient, times(1)).createMetricDescriptor(metricDescriptorCaptor.capture());
verify(mockClient, times(1))
Expand All @@ -382,7 +386,8 @@ public void testExportWithNonSupportedMetricTypeReturnsFailure() {
DEFAULT_PREFIX,
mockClient,
MetricDescriptorStrategy.ALWAYS_SEND,
NO_RESOURCE_ATTRIBUTES);
NO_RESOURCE_ATTRIBUTES,
false);

MetricData metricData =
ImmutableMetricData.createDoubleSummary(
Expand Down Expand Up @@ -452,6 +457,26 @@ public void verifyExporterCreationErrorDoesNotBreakMetricExporter() {
}
}

@Test
public void verifyExporterExportGoogleServiceMetrics() {
MetricExporter exporter =
InternalMetricExporter.createWithClient(
aProjectId,
"compute.googleapis.com",
mockClient,
MetricDescriptorStrategy.ALWAYS_SEND,
NO_RESOURCE_ATTRIBUTES,
true);

CompletableResultCode result =
exporter.export(ImmutableList.of(googleComputeServiceMetricData));
verify(mockClient, times(0)).createMetricDescriptor(any());
verify(mockClient, times(0)).createTimeSeries(any(ProjectName.class), any());
verify(mockClient, times(1)).createServiceTimeSeries(any(ProjectName.class), any());

assertTrue(result.isSuccess());
}

private void generateOpenTelemetryUsingGoogleCloudMetricExporter(MetricExporter metricExporter) {
SdkMeterProvider meterProvider =
SdkMeterProvider.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
package com.google.cloud.opentelemetry.metric;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import com.google.auth.Credentials;
import com.google.auth.oauth2.AccessToken;
Expand Down Expand Up @@ -48,6 +50,7 @@ public void testDefaultConfigurationSucceeds() {

assertNull(configuration.getCredentials());
assertEquals(PROJECT_ID, configuration.getProjectId());
assertFalse(configuration.getUseServiceTimeSeries());
}

@Test
Expand All @@ -58,11 +61,13 @@ public void testSetAllConfigurationFieldsSucceeds() {
.setProjectId(PROJECT_ID)
.setCredentials(FAKE_CREDENTIALS)
.setResourceAttributesFilter(allowAllPredicate)
.setUseServiceTimeSeries(true)
.build();

assertEquals(FAKE_CREDENTIALS, configuration.getCredentials());
assertEquals(PROJECT_ID, configuration.getProjectId());
assertEquals(allowAllPredicate, configuration.getResourceAttributesFilter());
assertTrue(configuration.getUseServiceTimeSeries());
}

@Test
Expand Down
Loading