diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index 1d6a72593..b4d574b8b 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -867,7 +867,7 @@ public final class RegistryConfig { * Maximum QPS for the Google Cloud Monitoring V3 (aka Stackdriver) API. The QPS limit can be * adjusted by contacting Cloud Support. * - * @see google.registry.monitoring.metrics.StackdriverWriter + * @see google.registry.monitoring.metrics.stackdriver.StackdriverWriter */ @Provides @Config("stackdriverMaxQps") @@ -879,7 +879,7 @@ public final class RegistryConfig { * Maximum number of points that can be sent to Stackdriver in a single TimeSeries.Create API * call. * - * @see google.registry.monitoring.metrics.StackdriverWriter + * @see google.registry.monitoring.metrics.stackdriver.StackdriverWriter */ @Provides @Config("stackdriverMaxPointsPerRequest") diff --git a/java/google/registry/monitoring/metrics/BUILD b/java/google/registry/monitoring/metrics/BUILD index bb383bc48..6891e0d8b 100644 --- a/java/google/registry/monitoring/metrics/BUILD +++ b/java/google/registry/monitoring/metrics/BUILD @@ -8,15 +8,11 @@ java_library( name = "metrics", srcs = glob(["*.java"]), deps = [ - "@com_google_api_client", - "@com_google_apis_google_api_services_monitoring", - "@com_google_appengine_api_1_0_sdk", "@com_google_auto_value", "@com_google_code_findbugs_jsr305", "@com_google_dagger", "@com_google_errorprone_error_prone_annotations", "@com_google_guava", - "@com_google_http_client", "@com_google_re2j", "@joda_time", ], diff --git a/java/google/registry/monitoring/metrics/MetricPoint.java b/java/google/registry/monitoring/metrics/MetricPoint.java index 7e50a70a8..e4a6043dc 100644 --- a/java/google/registry/monitoring/metrics/MetricPoint.java +++ b/java/google/registry/monitoring/metrics/MetricPoint.java @@ -15,6 +15,7 @@ package google.registry.monitoring.metrics; import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import org.joda.time.Instant; import org.joda.time.Interval; @@ -32,7 +33,8 @@ public abstract class MetricPoint { *

Callers should insure that the count of {@code labelValues} matches the count of labels for * the given metric. */ - static MetricPoint create( + @VisibleForTesting + public static MetricPoint create( Metric metric, ImmutableList labelValues, Instant timestamp, V value) { MetricsUtils.checkLabelValuesLength(metric, labelValues); @@ -47,7 +49,8 @@ public abstract class MetricPoint { *

Callers should insure that the count of {@code labelValues} matches the count of labels for * the given metric. */ - static MetricPoint create( + @VisibleForTesting + public static MetricPoint create( Metric metric, ImmutableList labelValues, Instant startTime, diff --git a/java/google/registry/monitoring/metrics/MetricSchema.java b/java/google/registry/monitoring/metrics/MetricSchema.java index a8e4ee165..8dc9a040a 100644 --- a/java/google/registry/monitoring/metrics/MetricSchema.java +++ b/java/google/registry/monitoring/metrics/MetricSchema.java @@ -17,6 +17,7 @@ package google.registry.monitoring.metrics; import static com.google.common.base.Preconditions.checkArgument; import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; /** The description of a metric's schema. */ @@ -36,7 +37,8 @@ public abstract class MetricSchema { * code as a label. If labels are set, corresponding label values must be provided when values * are set. The set of labels may be empty. */ - static MetricSchema create( + @VisibleForTesting + public static MetricSchema create( String name, String description, String valueDisplayName, diff --git a/java/google/registry/monitoring/metrics/StackdriverWriter.java b/java/google/registry/monitoring/metrics/StackdriverWriter.java deleted file mode 100644 index 6c45ca3ca..000000000 --- a/java/google/registry/monitoring/metrics/StackdriverWriter.java +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright 2016 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.monitoring.metrics; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -import com.google.api.client.googleapis.json.GoogleJsonResponseException; -import com.google.api.services.monitoring.v3.Monitoring; -import com.google.api.services.monitoring.v3.model.BucketOptions; -import com.google.api.services.monitoring.v3.model.CreateTimeSeriesRequest; -import com.google.api.services.monitoring.v3.model.Distribution; -import com.google.api.services.monitoring.v3.model.Explicit; -import com.google.api.services.monitoring.v3.model.Exponential; -import com.google.api.services.monitoring.v3.model.LabelDescriptor; -import com.google.api.services.monitoring.v3.model.Linear; -import com.google.api.services.monitoring.v3.model.Metric; -import com.google.api.services.monitoring.v3.model.MetricDescriptor; -import com.google.api.services.monitoring.v3.model.MonitoredResource; -import com.google.api.services.monitoring.v3.model.Point; -import com.google.api.services.monitoring.v3.model.TimeInterval; -import com.google.api.services.monitoring.v3.model.TimeSeries; -import com.google.api.services.monitoring.v3.model.TypedValue; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.RateLimiter; -import google.registry.monitoring.metrics.MetricSchema.Kind; -import java.io.IOException; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Queue; -import java.util.logging.Logger; -import javax.annotation.concurrent.NotThreadSafe; -import javax.inject.Inject; -import javax.inject.Named; -import org.joda.time.DateTime; -import org.joda.time.Interval; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.ISODateTimeFormat; - -/** - * Metrics writer for Google Cloud Monitoring V3 - * - *

This class communicates with the API via HTTP. In order to increase throughput and minimize - * CPU, it buffers points to be written until it has {@code maxPointsPerRequest} points buffered or - * until {@link #flush()} is called. - * - * @see Introduction to the Stackdriver - * Monitoring API - */ -// TODO(shikhman): add retry logic -@NotThreadSafe -public class StackdriverWriter implements MetricWriter { - - /** - * A counter representing the total number of points pushed. Has {@link MetricSchema.Kind} and - * metric value types as labels. - */ - private static final IncrementableMetric pushedPoints = - MetricRegistryImpl.getDefault() - .newIncrementableMetric( - "/metrics/stackdriver/points_pushed", - "Count of points pushed to Stackdriver Monitoring API.", - "Points Pushed", - ImmutableSet.of( - google.registry.monitoring.metrics.LabelDescriptor.create("kind", "Metric Kind"), - google.registry.monitoring.metrics.LabelDescriptor.create( - "valueType", "Metric Value Type"))); - private static final String METRIC_DOMAIN = "custom.googleapis.com"; - private static final String LABEL_VALUE_TYPE = "STRING"; - private static final DateTimeFormatter DATETIME_FORMATTER = ISODateTimeFormat.dateTime(); - private static final Logger logger = Logger.getLogger(StackdriverWriter.class.getName()); - // A map of native type to the equivalent Stackdriver metric type. - private static final ImmutableMap, String> ENCODED_METRIC_TYPES = - new ImmutableMap.Builder, String>() - .put(Long.class, "INT64") - .put(Double.class, "DOUBLE") - .put(Boolean.class, "BOOL") - .put(String.class, "STRING") - .put(google.registry.monitoring.metrics.Distribution.class, "DISTRIBUTION") - .build(); - // A map of native kind to the equivalent Stackdriver metric kind. - private static final ImmutableMap ENCODED_METRIC_KINDS = - new ImmutableMap.Builder() - .put(Kind.GAUGE.name(), "GAUGE") - .put(Kind.CUMULATIVE.name(), "CUMULATIVE") - .build(); - private static final String FLUSH_OVERFLOW_ERROR = "Cannot flush more than 200 points at a time"; - private static final String METRIC_KIND_ERROR = - "Unrecognized metric kind, must be one of " - + Joiner.on(",").join(ENCODED_METRIC_KINDS.keySet()); - private static final String METRIC_TYPE_ERROR = - "Unrecognized metric type, must be one of " - + Joiner.on(" ").join(ENCODED_METRIC_TYPES.keySet()); - private static final String METRIC_LABEL_COUNT_ERROR = - "Metric label value count does not match its MetricDescriptor's label count"; - - private final MonitoredResource monitoredResource; - private final Queue timeSeriesBuffer; - /** - * A local cache of MetricDescriptors. A metric's metadata (name, kind, type, label definitions) - * must be registered before points for the metric can be pushed. - */ - private final HashMap, MetricDescriptor> - registeredDescriptors = new HashMap<>(); - private final String projectResource; - private final Monitoring monitoringClient; - private final int maxPointsPerRequest; - private final RateLimiter rateLimiter; - - /** - * Constructs a {@link StackdriverWriter}. - * - *

The monitoringClient must have read and write permissions to the Cloud Monitoring API v3 on - * the provided project. - */ - @Inject - public StackdriverWriter( - Monitoring monitoringClient, - @Named("stackdriverGcpProject") String project, - MonitoredResource monitoredResource, - @Named("stackdriverMaxQps") int maxQps, - @Named("stackdriverMaxPointsPerRequest") int maxPointsPerRequest) { - this.monitoringClient = checkNotNull(monitoringClient); - this.projectResource = "projects/" + checkNotNull(project); - this.monitoredResource = monitoredResource; - this.maxPointsPerRequest = maxPointsPerRequest; - this.timeSeriesBuffer = new ArrayDeque<>(maxPointsPerRequest); - this.rateLimiter = RateLimiter.create(maxQps); - } - - @VisibleForTesting - static ImmutableList encodeLabelDescriptors( - ImmutableSet labelDescriptors) { - List stackDriverLabelDescriptors = new ArrayList<>(labelDescriptors.size()); - - for (google.registry.monitoring.metrics.LabelDescriptor labelDescriptor : labelDescriptors) { - stackDriverLabelDescriptors.add( - new LabelDescriptor() - .setKey(labelDescriptor.name()) - .setDescription(labelDescriptor.description()) - .setValueType(LABEL_VALUE_TYPE)); - } - - return ImmutableList.copyOf(stackDriverLabelDescriptors); - } - - @VisibleForTesting - static MetricDescriptor encodeMetricDescriptor( - google.registry.monitoring.metrics.Metric metric) { - return new MetricDescriptor() - .setType(METRIC_DOMAIN + metric.getMetricSchema().name()) - .setDescription(metric.getMetricSchema().description()) - .setDisplayName(metric.getMetricSchema().valueDisplayName()) - .setValueType(ENCODED_METRIC_TYPES.get(metric.getValueClass())) - .setLabels(encodeLabelDescriptors(metric.getMetricSchema().labels())) - .setMetricKind(ENCODED_METRIC_KINDS.get(metric.getMetricSchema().kind().name())); - } - - /** Encodes and writes a metric point to Stackdriver. The point may be buffered. */ - @Override - public void write(google.registry.monitoring.metrics.MetricPoint point) - throws IOException { - checkNotNull(point); - - TimeSeries timeSeries = getEncodedTimeSeries(point); - timeSeriesBuffer.add(timeSeries); - - logger.fine(String.format("Enqueued metric %s for writing", timeSeries.getMetric().getType())); - if (timeSeriesBuffer.size() == maxPointsPerRequest) { - flush(); - } - } - - /** Flushes all buffered metric points to Stackdriver. This call is blocking. */ - @Override - public void flush() throws IOException { - checkState(timeSeriesBuffer.size() <= 200, FLUSH_OVERFLOW_ERROR); - - // Return early; Stackdriver throws errors if we attempt to send empty requests. - if (timeSeriesBuffer.isEmpty()) { - logger.fine("Attempted to flush with no pending points, doing nothing"); - return; - } - - ImmutableList timeSeriesList = ImmutableList.copyOf(timeSeriesBuffer); - timeSeriesBuffer.clear(); - - CreateTimeSeriesRequest request = new CreateTimeSeriesRequest().setTimeSeries(timeSeriesList); - - rateLimiter.acquire(); - monitoringClient.projects().timeSeries().create(projectResource, request).execute(); - - for (TimeSeries timeSeries : timeSeriesList) { - pushedPoints.increment(timeSeries.getMetricKind(), timeSeries.getValueType()); - } - logger.info(String.format("Flushed %d metrics to Stackdriver", timeSeriesList.size())); - } - - /** - * Registers a metric's {@link MetricDescriptor} with the Monitoring API. - * - * @param metric the metric to be registered. - * @see Stackdriver MetricDescriptor API - */ - @VisibleForTesting - MetricDescriptor registerMetric(final google.registry.monitoring.metrics.Metric metric) - throws IOException { - if (registeredDescriptors.containsKey(metric)) { - logger.fine( - String.format("Using existing metric descriptor %s", metric.getMetricSchema().name())); - return registeredDescriptors.get(metric); - } - - MetricDescriptor descriptor = encodeMetricDescriptor(metric); - - rateLimiter.acquire(); - // We try to create a descriptor, but it may have been created already, so we re-fetch it on - // failure - try { - descriptor = - monitoringClient - .projects() - .metricDescriptors() - .create(projectResource, descriptor) - .execute(); - logger.info(String.format("Registered new metric descriptor %s", descriptor.getType())); - } catch (GoogleJsonResponseException jsonException) { - // Not the error we were expecting, just give up - if (!"ALREADY_EXISTS".equals(jsonException.getStatusMessage())) { - throw jsonException; - } - - descriptor = - monitoringClient - .projects() - .metricDescriptors() - .get(projectResource + "/metricDescriptors/" + descriptor.getType()) - .execute(); - - logger.info( - String.format("Fetched existing metric descriptor %s", metric.getMetricSchema().name())); - } - - registeredDescriptors.put(metric, descriptor); - - return descriptor; - } - - private static TimeInterval encodeTimeInterval(Interval nativeInterval, Kind metricKind) { - - TimeInterval encodedInterval = - new TimeInterval().setStartTime(DATETIME_FORMATTER.print(nativeInterval.getStart())); - - DateTime endTimestamp = - nativeInterval.toDurationMillis() == 0 && metricKind != Kind.GAUGE - ? nativeInterval.getEnd().plusMillis(1) - : nativeInterval.getEnd(); - - return encodedInterval.setEndTime(DATETIME_FORMATTER.print(endTimestamp)); - } - - private static BucketOptions encodeBucketOptions(DistributionFitter fitter) { - BucketOptions bucketOptions = new BucketOptions(); - - if (fitter instanceof LinearFitter) { - LinearFitter linearFitter = (LinearFitter) fitter; - - bucketOptions.setLinearBuckets( - new Linear() - .setNumFiniteBuckets(linearFitter.boundaries().size() - 1) - .setWidth(linearFitter.width()) - .setOffset(linearFitter.offset())); - } else if (fitter instanceof ExponentialFitter) { - ExponentialFitter exponentialFitter = (ExponentialFitter) fitter; - - bucketOptions.setExponentialBuckets( - new Exponential() - .setNumFiniteBuckets(exponentialFitter.boundaries().size() - 1) - .setGrowthFactor(exponentialFitter.base()) - .setScale(exponentialFitter.scale())); - } else if (fitter instanceof CustomFitter) { - bucketOptions.setExplicitBuckets(new Explicit().setBounds(fitter.boundaries().asList())); - } else { - throw new IllegalArgumentException("Illegal DistributionFitter type"); - } - - return bucketOptions; - } - - private static List encodeDistributionPoints( - google.registry.monitoring.metrics.Distribution distribution) { - return distribution.intervalCounts().asMapOfRanges().values().asList(); - } - - private static Distribution encodeDistribution( - google.registry.monitoring.metrics.Distribution nativeDistribution) { - return new Distribution() - .setMean(nativeDistribution.mean()) - .setCount(nativeDistribution.count()) - .setSumOfSquaredDeviation(nativeDistribution.sumOfSquaredDeviation()) - .setBucketOptions(encodeBucketOptions(nativeDistribution.distributionFitter())) - .setBucketCounts(encodeDistributionPoints(nativeDistribution)); - } - - /** - * Encodes a {@link MetricPoint} into a Stackdriver {@link TimeSeries}. - * - *

This method will register the underlying {@link google.registry.monitoring.metrics.Metric} - * as a Stackdriver {@link MetricDescriptor}. - * - * @see - * Stackdriver TimeSeries API - */ - @VisibleForTesting - TimeSeries getEncodedTimeSeries(google.registry.monitoring.metrics.MetricPoint point) - throws IOException { - google.registry.monitoring.metrics.Metric metric = point.metric(); - try { - checkArgument( - ENCODED_METRIC_KINDS.containsKey(metric.getMetricSchema().kind().name()), - METRIC_KIND_ERROR); - checkArgument(ENCODED_METRIC_TYPES.containsKey(metric.getValueClass()), METRIC_TYPE_ERROR); - } catch (IllegalArgumentException e) { - throw new IOException(e.getMessage()); - } - - MetricDescriptor descriptor = registerMetric(metric); - - if (point.labelValues().size() != point.metric().getMetricSchema().labels().size()) { - throw new IOException(METRIC_LABEL_COUNT_ERROR); - } - - V value = point.value(); - TypedValue encodedValue = new TypedValue(); - Class valueClass = metric.getValueClass(); - - if (valueClass == Long.class) { - encodedValue.setInt64Value((Long) value); - } else if (valueClass == Double.class) { - encodedValue.setDoubleValue((Double) value); - } else if (valueClass == Boolean.class) { - encodedValue.setBoolValue((Boolean) value); - } else if (valueClass == String.class) { - encodedValue.setStringValue((String) value); - } else if (valueClass == google.registry.monitoring.metrics.Distribution.class) { - encodedValue.setDistributionValue( - encodeDistribution((google.registry.monitoring.metrics.Distribution) value)); - } else { - // This is unreachable because the precondition checks will catch all NotSerializable - // exceptions. - throw new IllegalArgumentException("Invalid metric valueClass: " + metric.getValueClass()); - } - - Point encodedPoint = - new Point() - .setInterval(encodeTimeInterval(point.interval(), metric.getMetricSchema().kind())) - .setValue(encodedValue); - - List encodedLabels = descriptor.getLabels(); - // The MetricDescriptors returned by the GCM API have null fields rather than empty lists - encodedLabels = encodedLabels == null ? ImmutableList.of() : encodedLabels; - - ImmutableMap.Builder labelValues = new ImmutableMap.Builder<>(); - int i = 0; - for (LabelDescriptor labelDescriptor : encodedLabels) { - labelValues.put(labelDescriptor.getKey(), point.labelValues().get(i++)); - } - - Metric encodedMetric = - new Metric().setType(descriptor.getType()).setLabels(labelValues.build()); - - return new TimeSeries() - .setMetric(encodedMetric) - .setPoints(ImmutableList.of(encodedPoint)) - .setResource(monitoredResource) - // these two attributes are ignored by the API, we set them here to use elsewhere - // for internal metrics. - .setMetricKind(descriptor.getMetricKind()) - .setValueType(descriptor.getValueType()); - } -} diff --git a/java/google/registry/monitoring/whitebox/BUILD b/java/google/registry/monitoring/whitebox/BUILD index 4f62ef4f1..92274deb7 100644 --- a/java/google/registry/monitoring/whitebox/BUILD +++ b/java/google/registry/monitoring/whitebox/BUILD @@ -12,6 +12,7 @@ java_library( "//java/google/registry/config", "//java/google/registry/model", "//java/google/registry/monitoring/metrics", + "//java/google/registry/monitoring/metrics/stackdriver", "//java/google/registry/request", "//java/google/registry/util", "//third_party/java/objectify:objectify-v4_1", diff --git a/java/google/registry/monitoring/whitebox/StackdriverModule.java b/java/google/registry/monitoring/whitebox/StackdriverModule.java index 8a5ad0bda..58239ef5d 100644 --- a/java/google/registry/monitoring/whitebox/StackdriverModule.java +++ b/java/google/registry/monitoring/whitebox/StackdriverModule.java @@ -28,7 +28,7 @@ import dagger.Module; import dagger.Provides; import google.registry.config.RegistryConfig.Config; import google.registry.monitoring.metrics.MetricWriter; -import google.registry.monitoring.metrics.StackdriverWriter; +import google.registry.monitoring.metrics.stackdriver.StackdriverWriter; import java.util.Set; import java.util.concurrent.ThreadFactory; import javax.inject.Named; diff --git a/javatests/google/registry/monitoring/metrics/BUILD b/javatests/google/registry/monitoring/metrics/BUILD index b3f8cefff..3886c4aea 100644 --- a/javatests/google/registry/monitoring/metrics/BUILD +++ b/javatests/google/registry/monitoring/metrics/BUILD @@ -12,11 +12,7 @@ java_library( srcs = glob(["*.java"]), deps = [ "//java/google/registry/monitoring/metrics", - "@com_google_api_client", - "@com_google_apis_google_api_services_monitoring", "@com_google_guava", - "@com_google_http_client", - "@com_google_http_client_jackson2", "@com_google_truth", "@joda_time", "@junit", diff --git a/javatests/google/registry/monitoring/metrics/GoogleJsonResponseExceptionHelper.java b/javatests/google/registry/monitoring/metrics/GoogleJsonResponseExceptionHelper.java deleted file mode 100644 index 8da92370a..000000000 --- a/javatests/google/registry/monitoring/metrics/GoogleJsonResponseExceptionHelper.java +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2016 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.monitoring.metrics; - -import com.google.api.client.googleapis.json.GoogleJsonResponseException; -import com.google.api.client.http.GenericUrl; -import com.google.api.client.http.HttpContent; -import com.google.api.client.http.HttpRequest; -import com.google.api.client.http.HttpRequestFactory; -import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpTransport; -import com.google.api.client.http.LowLevelHttpRequest; -import com.google.api.client.http.LowLevelHttpResponse; -import com.google.api.client.json.jackson2.JacksonFactory; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** A helper to create instances of {@link GoogleJsonResponseException}. */ -public class GoogleJsonResponseExceptionHelper { - /** - * @param statusCode the status code that should be in the returned {@link - * GoogleJsonResponseException} - * @return a {@link GoogleJsonResponseException} with the status code {@code statusCode} - * @throws IOException shouldn't occur - */ - public static GoogleJsonResponseException create(int statusCode) throws IOException { - HttpResponse response = createHttpResponse(statusCode, null); - return GoogleJsonResponseException.from(new JacksonFactory(), response); - } - - public static HttpResponse createHttpResponse(int statusCode, InputStream content) - throws IOException { - FakeHttpTransport transport = new FakeHttpTransport(statusCode, content); - HttpRequestFactory factory = transport.createRequestFactory(); - HttpRequest request = - factory.buildRequest( - "foo", new GenericUrl("http://example.com/bar"), new EmptyHttpContent()); - request.setThrowExceptionOnExecuteError(false); - return request.execute(); - } - - private static class FakeHttpTransport extends HttpTransport { - private final int statusCode; - private final InputStream content; - - FakeHttpTransport(int statusCode, InputStream content) { - this.statusCode = statusCode; - this.content = content; - } - - @Override - protected LowLevelHttpRequest buildRequest(String method, String url) throws IOException { - return new FakeLowLevelHttpRequest(statusCode, content); - } - } - - private static class FakeLowLevelHttpRequest extends LowLevelHttpRequest { - private final int statusCode; - private final InputStream content; - - FakeLowLevelHttpRequest(int statusCode, InputStream content) { - this.statusCode = statusCode; - this.content = content; - } - - @Override - public void addHeader(String name, String value) throws IOException { - // Nothing! - } - - @Override - public LowLevelHttpResponse execute() throws IOException { - return new FakeLowLevelHttpResponse(statusCode, content); - } - } - - private static class FakeLowLevelHttpResponse extends LowLevelHttpResponse { - private final int statusCode; - private final InputStream content; - - FakeLowLevelHttpResponse(int statusCode, InputStream content) { - this.statusCode = statusCode; - this.content = content; - } - - @Override - public InputStream getContent() throws IOException { - return content; - } - - @Override - public String getContentEncoding() throws IOException { - return null; - } - - @Override - public long getContentLength() throws IOException { - return 0; - } - - @Override - public String getContentType() throws IOException { - return "text/json"; - } - - @Override - public String getStatusLine() throws IOException { - return null; - } - - @Override - public int getStatusCode() throws IOException { - return statusCode; - } - - @Override - public String getReasonPhrase() throws IOException { - return null; - } - - @Override - public int getHeaderCount() throws IOException { - return 0; - } - - @Override - public String getHeaderName(int index) throws IOException { - return null; - } - - @Override - public String getHeaderValue(int index) throws IOException { - return null; - } - } - - private static class EmptyHttpContent implements HttpContent { - @Override - public long getLength() throws IOException { - return 0; - } - - @Override - public String getType() { - return "text/json"; - } - - @Override - public boolean retrySupported() { - return false; - } - - @Override - public void writeTo(OutputStream out) throws IOException { - // Nothing! - } - } -} diff --git a/javatests/google/registry/monitoring/metrics/StackdriverWriterTest.java b/javatests/google/registry/monitoring/metrics/StackdriverWriterTest.java deleted file mode 100644 index 6c88c4447..000000000 --- a/javatests/google/registry/monitoring/metrics/StackdriverWriterTest.java +++ /dev/null @@ -1,542 +0,0 @@ -// Copyright 2016 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.monitoring.metrics; - -import static com.google.common.truth.Truth.assertThat; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.google.api.client.googleapis.json.GoogleJsonResponseException; -import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpResponseException; -import com.google.api.services.monitoring.v3.Monitoring; -import com.google.api.services.monitoring.v3.model.BucketOptions; -import com.google.api.services.monitoring.v3.model.CreateTimeSeriesRequest; -import com.google.api.services.monitoring.v3.model.Explicit; -import com.google.api.services.monitoring.v3.model.Exponential; -import com.google.api.services.monitoring.v3.model.Linear; -import com.google.api.services.monitoring.v3.model.MetricDescriptor; -import com.google.api.services.monitoring.v3.model.MonitoredResource; -import com.google.api.services.monitoring.v3.model.Point; -import com.google.api.services.monitoring.v3.model.TimeSeries; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import google.registry.monitoring.metrics.MetricSchema.Kind; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.List; -import org.joda.time.Instant; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.runners.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; - -/** Unit tests for {@link StackdriverWriter}. */ -@RunWith(MockitoJUnitRunner.class) -public class StackdriverWriterTest { - - @Mock private Monitoring client; - @Mock private Monitoring.Projects projects; - @Mock private Monitoring.Projects.MetricDescriptors metricDescriptors; - @Mock private Monitoring.Projects.MetricDescriptors.Get metricDescriptorGet; - @Mock private Monitoring.Projects.TimeSeries timeSeries; - @Mock private Monitoring.Projects.MetricDescriptors.Create metricDescriptorCreate; - @Mock private Monitoring.Projects.TimeSeries.Create timeSeriesCreate; - @Mock private Metric mockMetric; - @Mock private MetricSchema schema; - @Mock MetricPoint metricPoint; - private Counter metric; - private MetricDescriptor descriptor; - private static final String PROJECT = "PROJECT"; - private static final int MAX_QPS = 10; - private static final int MAX_POINTS_PER_REQUEST = 10; - private static final MonitoredResource MONITORED_RESOURCE = new MonitoredResource(); - - @Before - public void setUp() throws Exception { - metric = - new Counter( - "/name", - "desc", - "vdn", - ImmutableSet.of(LabelDescriptor.create("label", "description"))); - descriptor = StackdriverWriter.encodeMetricDescriptor(metric); - when(client.projects()).thenReturn(projects); - when(projects.metricDescriptors()).thenReturn(metricDescriptors); - when(projects.timeSeries()).thenReturn(timeSeries); - when(metricDescriptors.create(anyString(), any(MetricDescriptor.class))) - .thenReturn(metricDescriptorCreate); - when(metricDescriptorCreate.execute()).thenReturn(descriptor); - when(metricDescriptors.get(anyString())).thenReturn(metricDescriptorGet); - when(metricDescriptorGet.execute()).thenReturn(descriptor); - when(timeSeries.create(anyString(), any(CreateTimeSeriesRequest.class))) - .thenReturn(timeSeriesCreate); - } - - @Test - public void testWrite_maxPoints_flushes() throws Exception { - // The counter must be set once in order for there to be values to send. - metric.set(0L, new Instant(1337), ImmutableList.of("some_value")); - StackdriverWriter writer = - spy( - new StackdriverWriter( - client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST)); - - for (int i = 0; i < MAX_POINTS_PER_REQUEST; i++) { - for (MetricPoint point : metric.getTimestampedValues(new Instant(1337))) { - writer.write(point); - } - } - - verify(writer).flush(); - } - - @Test - public void testWrite_lessThanMaxPoints_doesNotFlush() throws Exception { - // The counter must be set once in order for there to be values to send. - metric.set(0L, new Instant(1337), ImmutableList.of("some_value")); - StackdriverWriter writer = - spy( - new StackdriverWriter( - client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST)); - - for (int i = 0; i < MAX_POINTS_PER_REQUEST - 1; i++) { - for (MetricPoint point : metric.getTimestampedValues(new Instant(1337))) { - writer.write(point); - } - } - - verify(writer, never()).flush(); - } - - @Test - public void testWrite_invalidMetricType_throwsException() throws Exception { - when(mockMetric.getValueClass()) - .thenAnswer( - new Answer>() { - @Override - public Class answer(InvocationOnMock invocation) throws Throwable { - return Object.class; - } - }); - when(mockMetric.getMetricSchema()).thenReturn(schema); - when(mockMetric.getTimestampedValues()).thenReturn(ImmutableList.of(metricPoint)); - when(schema.kind()).thenReturn(Kind.CUMULATIVE); - when(metricPoint.metric()).thenReturn(mockMetric); - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - - for (MetricPoint point : mockMetric.getTimestampedValues()) { - try { - writer.write(point); - fail("expected IllegalArgumentException"); - } catch (IOException expected) {} - } - } - - @Test - public void testWrite_ManyPoints_flushesTwice() throws Exception { - // The counter must be set once in order for there to be values to send. - metric.set(0L, new Instant(1337), ImmutableList.of("some_value")); - StackdriverWriter writer = - spy( - new StackdriverWriter( - client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST)); - - for (int i = 0; i < MAX_POINTS_PER_REQUEST * 2; i++) { - for (MetricPoint point : metric.getTimestampedValues(new Instant(1337))) { - writer.write(point); - } - } - - verify(writer, times(2)).flush(); - } - - @Test - public void testRegisterMetric_registersWithStackdriver() throws Exception { - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - - writer.registerMetric(metric); - - verify( - client - .projects() - .metricDescriptors() - .create(PROJECT, StackdriverWriter.encodeMetricDescriptor(metric))) - .execute(); - } - - @Test - public void registerMetric_doesNotReregisterDupe() throws Exception { - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - - writer.registerMetric(metric); - writer.registerMetric(metric); - - verify( - client - .projects() - .metricDescriptors() - .create(PROJECT, StackdriverWriter.encodeMetricDescriptor(metric))) - .execute(); - } - - @Test - public void registerMetric_fetchesStackdriverDefinition() throws Exception { - // Stackdriver throws an Exception with the status message "ALREADY_EXISTS" when you try to - // register a metric that's already been registered, so we fake one here. - ByteArrayInputStream inputStream = new ByteArrayInputStream("".getBytes(UTF_8)); - HttpResponse response = GoogleJsonResponseExceptionHelper.createHttpResponse(400, inputStream); - HttpResponseException.Builder httpResponseExceptionBuilder = - new HttpResponseException.Builder(response); - httpResponseExceptionBuilder.setStatusCode(400); - httpResponseExceptionBuilder.setStatusMessage("ALREADY_EXISTS"); - GoogleJsonResponseException exception = - new GoogleJsonResponseException(httpResponseExceptionBuilder, null); - when(metricDescriptorCreate.execute()).thenThrow(exception); - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - - writer.registerMetric(metric); - - verify(client.projects().metricDescriptors().get("metric")).execute(); - } - - @Test - public void registerMetric_rethrowsException() throws Exception { - ByteArrayInputStream inputStream = new ByteArrayInputStream("".getBytes(UTF_8)); - HttpResponse response = GoogleJsonResponseExceptionHelper.createHttpResponse(400, inputStream); - HttpResponseException.Builder httpResponseExceptionBuilder = - new HttpResponseException.Builder(response); - httpResponseExceptionBuilder.setStatusCode(404); - GoogleJsonResponseException exception = - new GoogleJsonResponseException(httpResponseExceptionBuilder, null); - when(metricDescriptorCreate.execute()).thenThrow(exception); - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - - try { - writer.registerMetric(metric); - fail("Expected GoogleJsonResponseException"); - } catch (GoogleJsonResponseException expected) { - assertThat(exception.getStatusCode()).isEqualTo(404); - } - } - - @Test - public void getEncodedTimeSeries_nullLabels_encodes() throws Exception { - ByteArrayInputStream inputStream = new ByteArrayInputStream("".getBytes(UTF_8)); - HttpResponse response = GoogleJsonResponseExceptionHelper.createHttpResponse(400, inputStream); - HttpResponseException.Builder httpResponseExceptionBuilder = - new HttpResponseException.Builder(response); - httpResponseExceptionBuilder.setStatusCode(400); - httpResponseExceptionBuilder.setStatusMessage("ALREADY_EXISTS"); - GoogleJsonResponseException exception = - new GoogleJsonResponseException(httpResponseExceptionBuilder, null); - when(metricDescriptorCreate.execute()).thenThrow(exception); - when(metricDescriptorGet.execute()) - .thenReturn(new MetricDescriptor().setName("foo").setLabels(null)); - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - writer.registerMetric(metric); - - TimeSeries timeSeries = - writer.getEncodedTimeSeries( - MetricPoint.create(metric, ImmutableList.of("foo"), new Instant(1337), 10L)); - - assertThat(timeSeries.getMetric().getLabels()).isEmpty(); - } - - @Test - public void encodeMetricDescriptor_simpleMetric_encodes() { - MetricDescriptor descriptor = StackdriverWriter.encodeMetricDescriptor(metric); - - assertThat(descriptor.getType()).isEqualTo("custom.googleapis.com/name"); - assertThat(descriptor.getValueType()).isEqualTo("INT64"); - assertThat(descriptor.getDescription()).isEqualTo("desc"); - assertThat(descriptor.getDisplayName()).isEqualTo("vdn"); - assertThat(descriptor.getLabels()) - .containsExactly( - new com.google.api.services.monitoring.v3.model.LabelDescriptor() - .setValueType("STRING") - .setKey("label") - .setDescription("description")); - } - - @Test - public void encodeLabelDescriptors_simpleLabels_encodes() { - ImmutableSet descriptors = - ImmutableSet.of( - LabelDescriptor.create("label1", "description1"), - LabelDescriptor.create("label2", "description2")); - - ImmutableList encodedDescritors = - StackdriverWriter.encodeLabelDescriptors(descriptors); - - assertThat(encodedDescritors) - .containsExactly( - new com.google.api.services.monitoring.v3.model.LabelDescriptor() - .setValueType("STRING") - .setKey("label1") - .setDescription("description1"), - new com.google.api.services.monitoring.v3.model.LabelDescriptor() - .setValueType("STRING") - .setKey("label2") - .setDescription("description2")); - } - - @Test - public void getEncodedTimeSeries_cumulativeMetricPoint_ZeroInterval_encodesGreaterEndTime() - throws Exception { - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - MetricPoint nativePoint = - MetricPoint.create( - metric, ImmutableList.of("foo"), new Instant(1337), new Instant(1337), 10L); - - TimeSeries timeSeries = writer.getEncodedTimeSeries(nativePoint); - - assertThat(timeSeries.getValueType()).isEqualTo("INT64"); - assertThat(timeSeries.getMetricKind()).isEqualTo("CUMULATIVE"); - List points = timeSeries.getPoints(); - assertThat(points).hasSize(1); - Point point = points.get(0); - assertThat(point.getValue().getInt64Value()).isEqualTo(10L); - assertThat(point.getInterval().getStartTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - assertThat(point.getInterval().getEndTime()).isEqualTo("1970-01-01T00:00:01.338Z"); - } - - @Test - public void getEncodedTimeSeries_cumulativeMetricPoint_nonZeroInterval_encodesSameInterval() - throws Exception { - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - MetricPoint nativePoint = - MetricPoint.create( - metric, ImmutableList.of("foo"), new Instant(1337), new Instant(1339), 10L); - - TimeSeries timeSeries = writer.getEncodedTimeSeries(nativePoint); - - assertThat(timeSeries.getValueType()).isEqualTo("INT64"); - assertThat(timeSeries.getMetricKind()).isEqualTo("CUMULATIVE"); - List points = timeSeries.getPoints(); - assertThat(points).hasSize(1); - Point point = points.get(0); - assertThat(point.getValue().getInt64Value()).isEqualTo(10L); - assertThat(point.getInterval().getStartTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - assertThat(point.getInterval().getEndTime()).isEqualTo("1970-01-01T00:00:01.339Z"); - } - - @Test - public void getEncodedTimeSeries_gaugeMetricPoint_zeroInterval_encodesSameInterval() - throws Exception { - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - Metric metric = - new StoredMetric<>( - "/name", - "desc", - "vdn", - ImmutableSet.of(LabelDescriptor.create("label", "description")), - Long.class); - when(metricDescriptorCreate.execute()) - .thenReturn(StackdriverWriter.encodeMetricDescriptor(metric)); - MetricPoint nativePoint = - MetricPoint.create( - metric, ImmutableList.of("foo"), new Instant(1337), new Instant(1337), 10L); - - TimeSeries timeSeries = writer.getEncodedTimeSeries(nativePoint); - - assertThat(timeSeries.getValueType()).isEqualTo("INT64"); - assertThat(timeSeries.getMetricKind()).isEqualTo("GAUGE"); - List points = timeSeries.getPoints(); - assertThat(points).hasSize(1); - Point point = points.get(0); - assertThat(point.getValue().getInt64Value()).isEqualTo(10L); - assertThat(point.getInterval().getStartTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - assertThat(point.getInterval().getEndTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - } - - @Test - public void getEncodedTimeSeries_booleanMetric_encodes() throws Exception { - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - Metric boolMetric = - new StoredMetric<>( - "/name", - "desc", - "vdn", - ImmutableSet.of(LabelDescriptor.create("label", "description")), - Boolean.class); - MetricDescriptor boolDescriptor = StackdriverWriter.encodeMetricDescriptor(boolMetric); - when(metricDescriptorCreate.execute()).thenReturn(boolDescriptor); - MetricPoint nativePoint = - MetricPoint.create(boolMetric, ImmutableList.of("foo"), new Instant(1337), true); - - TimeSeries timeSeries = writer.getEncodedTimeSeries(nativePoint); - - assertThat(timeSeries.getValueType()).isEqualTo("BOOL"); - assertThat(timeSeries.getMetricKind()).isEqualTo("GAUGE"); - List points = timeSeries.getPoints(); - assertThat(points).hasSize(1); - Point point = points.get(0); - assertThat(point.getValue().getBoolValue()).isEqualTo(true); - assertThat(point.getInterval().getEndTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - assertThat(point.getInterval().getStartTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - } - - @Test - public void getEncodedTimeSeries_distributionMetricCustomFitter_encodes() throws Exception { - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - Metric metric = - new StoredMetric<>( - "/name", - "desc", - "vdn", - ImmutableSet.of(LabelDescriptor.create("label", "description")), - Distribution.class); - MetricDescriptor descriptor = StackdriverWriter.encodeMetricDescriptor(metric); - when(metricDescriptorCreate.execute()).thenReturn(descriptor); - MutableDistribution distribution = - new MutableDistribution(CustomFitter.create(ImmutableSet.of(5.0))); - distribution.add(10.0, 5L); - distribution.add(0.0, 5L); - MetricPoint nativePoint = - MetricPoint.create(metric, ImmutableList.of("foo"), new Instant(1337), distribution); - - TimeSeries timeSeries = writer.getEncodedTimeSeries(nativePoint); - - assertThat(timeSeries.getValueType()).isEqualTo("DISTRIBUTION"); - assertThat(timeSeries.getMetricKind()).isEqualTo("GAUGE"); - List points = timeSeries.getPoints(); - assertThat(points).hasSize(1); - Point point = points.get(0); - assertThat(point.getValue().getDistributionValue()) - .isEqualTo( - new com.google.api.services.monitoring.v3.model.Distribution() - .setMean(5.0) - .setSumOfSquaredDeviation(250.0) - .setCount(10L) - .setBucketCounts(ImmutableList.of(5L, 5L)) - .setBucketOptions( - new BucketOptions() - .setExplicitBuckets(new Explicit().setBounds(ImmutableList.of(5.0))))); - assertThat(point.getInterval().getEndTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - assertThat(point.getInterval().getStartTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - } - - @Test - public void getEncodedTimeSeries_distributionMetricLinearFitter_encodes() throws Exception { - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - Metric metric = - new StoredMetric<>( - "/name", - "desc", - "vdn", - ImmutableSet.of(LabelDescriptor.create("label", "description")), - Distribution.class); - MetricDescriptor descriptor = StackdriverWriter.encodeMetricDescriptor(metric); - when(metricDescriptorCreate.execute()).thenReturn(descriptor); - MutableDistribution distribution = new MutableDistribution(LinearFitter.create(2, 5.0, 3.0)); - distribution.add(0.0, 1L); - distribution.add(3.0, 2L); - distribution.add(10.0, 5L); - distribution.add(20.0, 5L); - MetricPoint nativePoint = - MetricPoint.create(metric, ImmutableList.of("foo"), new Instant(1337), distribution); - - TimeSeries timeSeries = writer.getEncodedTimeSeries(nativePoint); - - assertThat(timeSeries.getValueType()).isEqualTo("DISTRIBUTION"); - assertThat(timeSeries.getMetricKind()).isEqualTo("GAUGE"); - List points = timeSeries.getPoints(); - assertThat(points).hasSize(1); - Point point = points.get(0); - assertThat(point.getValue().getDistributionValue()) - .isEqualTo( - new com.google.api.services.monitoring.v3.model.Distribution() - .setMean(12.0) - .setSumOfSquaredDeviation(646.0) - .setCount(13L) - .setBucketCounts(ImmutableList.of(1L, 2L, 5L, 5L)) - .setBucketOptions( - new BucketOptions() - .setLinearBuckets( - new Linear().setNumFiniteBuckets(2).setWidth(5.0).setOffset(3.0)))); - assertThat(point.getInterval().getEndTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - assertThat(point.getInterval().getStartTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - } - - @Test - public void getEncodedTimeSeries_distributionMetricExponentialFitter_encodes() throws Exception { - StackdriverWriter writer = - new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST); - Metric metric = - new StoredMetric<>( - "/name", - "desc", - "vdn", - ImmutableSet.of(LabelDescriptor.create("label", "description")), - Distribution.class); - MetricDescriptor descriptor = StackdriverWriter.encodeMetricDescriptor(metric); - when(metricDescriptorCreate.execute()).thenReturn(descriptor); - MutableDistribution distribution = - new MutableDistribution(ExponentialFitter.create(2, 3.0, 0.5)); - distribution.add(0.0, 1L); - distribution.add(3.0, 2L); - distribution.add(10.0, 5L); - distribution.add(20.0, 5L); - MetricPoint nativePoint = - MetricPoint.create(metric, ImmutableList.of("foo"), new Instant(1337), distribution); - - TimeSeries timeSeries = writer.getEncodedTimeSeries(nativePoint); - - assertThat(timeSeries.getValueType()).isEqualTo("DISTRIBUTION"); - assertThat(timeSeries.getMetricKind()).isEqualTo("GAUGE"); - List points = timeSeries.getPoints(); - assertThat(points).hasSize(1); - Point point = points.get(0); - assertThat(point.getValue().getDistributionValue()) - .isEqualTo( - new com.google.api.services.monitoring.v3.model.Distribution() - .setMean(12.0) - .setSumOfSquaredDeviation(646.0) - .setCount(13L) - .setBucketCounts(ImmutableList.of(1L, 0L, 2L, 10L)) - .setBucketOptions( - new BucketOptions() - .setExponentialBuckets( - new Exponential() - .setNumFiniteBuckets(2) - .setGrowthFactor(3.0) - .setScale(0.5)))); - assertThat(point.getInterval().getEndTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - assertThat(point.getInterval().getStartTime()).isEqualTo("1970-01-01T00:00:01.337Z"); - } -}