Re-add Stackdriver metrics code

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=146000275
This commit is contained in:
mcilwain 2017-01-30 09:17:24 -08:00 committed by Ben McIlwain
parent 37a4f389ef
commit 813f837956
7 changed files with 1487 additions and 0 deletions

View file

@ -0,0 +1,24 @@
package(
default_visibility = ["//visibility:public"],
)
licenses(["notice"]) # Apache 2.0
java_library(
name = "stackdriver",
srcs = glob(["*.java"]),
deps = [
"//java/google/registry/monitoring/metrics",
"@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",
],
)

View file

@ -0,0 +1,408 @@
// 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.stackdriver;
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.CustomFitter;
import google.registry.monitoring.metrics.DistributionFitter;
import google.registry.monitoring.metrics.ExponentialFitter;
import google.registry.monitoring.metrics.IncrementableMetric;
import google.registry.monitoring.metrics.LinearFitter;
import google.registry.monitoring.metrics.MetricPoint;
import google.registry.monitoring.metrics.MetricRegistryImpl;
import google.registry.monitoring.metrics.MetricSchema.Kind;
import google.registry.monitoring.metrics.MetricWriter;
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
*
* <p>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 <a href="https://cloud.google.com/monitoring/api/v3/">Introduction to the Stackdriver
* Monitoring API</a>
*/
// TODO(shikhman): add retry logic
@NotThreadSafe
public class StackdriverWriter implements MetricWriter {
/**
* A counter representing the total number of points pushed. Has {@link 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<Class<?>, String> ENCODED_METRIC_TYPES =
new ImmutableMap.Builder<Class<?>, 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<String, String> ENCODED_METRIC_KINDS =
new ImmutableMap.Builder<String, String>()
.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<TimeSeries> 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<google.registry.monitoring.metrics.Metric<?>, MetricDescriptor>
registeredDescriptors = new HashMap<>();
private final String projectResource;
private final Monitoring monitoringClient;
private final int maxPointsPerRequest;
private final RateLimiter rateLimiter;
/**
* Constructs a {@link StackdriverWriter}.
*
* <p>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<LabelDescriptor> encodeLabelDescriptors(
ImmutableSet<google.registry.monitoring.metrics.LabelDescriptor> labelDescriptors) {
List<LabelDescriptor> 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 <V> void write(google.registry.monitoring.metrics.MetricPoint<V> 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<TimeSeries> 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 <a href="https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.metricDescriptors">Stackdriver MetricDescriptor API</a>
*/
@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<Long> 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}.
*
* <p>This method will register the underlying {@link google.registry.monitoring.metrics.Metric}
* as a Stackdriver {@link MetricDescriptor}.
*
* @see <a href="https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries">
* Stackdriver TimeSeries API</a>
*/
@VisibleForTesting
<V> TimeSeries getEncodedTimeSeries(google.registry.monitoring.metrics.MetricPoint<V> point)
throws IOException {
google.registry.monitoring.metrics.Metric<V> 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<LabelDescriptor> encodedLabels = descriptor.getLabels();
// The MetricDescriptors returned by the GCM API have null fields rather than empty lists
encodedLabels = encodedLabels == null ? ImmutableList.<LabelDescriptor>of() : encodedLabels;
ImmutableMap.Builder<String, String> 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());
}
}

View file

@ -0,0 +1,19 @@
package(
default_visibility = ["//java/google/registry:registry_project"],
)
licenses(["notice"]) # Apache 2.0
java_binary(
name = "SheepCounterExample",
srcs = ["SheepCounterExample.java"],
deps = [
"//java/google/registry/monitoring/metrics",
"//java/google/registry/monitoring/metrics/stackdriver",
"@com_google_api_client",
"@com_google_apis_google_api_services_monitoring",
"@com_google_guava",
"@com_google_http_client",
"@com_google_http_client_jackson2",
],
)

View file

@ -0,0 +1,268 @@
// 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.example;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.monitoring.v3.Monitoring;
import com.google.api.services.monitoring.v3.MonitoringScopes;
import com.google.api.services.monitoring.v3.model.MonitoredResource;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.monitoring.metrics.EventMetric;
import google.registry.monitoring.metrics.IncrementableMetric;
import google.registry.monitoring.metrics.LabelDescriptor;
import google.registry.monitoring.metrics.LinearFitter;
import google.registry.monitoring.metrics.Metric;
import google.registry.monitoring.metrics.MetricRegistryImpl;
import google.registry.monitoring.metrics.MetricReporter;
import google.registry.monitoring.metrics.MetricWriter;
import google.registry.monitoring.metrics.SettableMetric;
import google.registry.monitoring.metrics.stackdriver.StackdriverWriter;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
/** A sample application which uses the Metrics API to count sheep while sleeping. */
public final class SheepCounterExample {
/**
* The code below for using a custom {@link LogManager} is only necessary to enable logging at JVM
* shutdown to show the shutdown logs of {@link MetricReporter} in this small standalone
* application.
*
* <p>It is NOT necessary for normal use of the Metrics library.
*/
static {
// must be called before any Logger method is used.
System.setProperty("java.util.logging.manager", DelayedShutdownLogManager.class.getName());
}
private static final Logger logger = Logger.getLogger(SheepCounterExample.class.getName());
/**
* The time interval, in seconds, between when the {@link MetricReporter} will read {@link Metric}
* instances and enqueue them to the {@link MetricWriter}.
*
* @see MetricReporter
*/
private static final long METRICS_REPORTING_INTERVAL = 30L;
/**
* The maximum queries per second to the Stackdriver API. Contact Cloud Support to raise this from
* the default value if necessary.
*/
private static final int STACKDRIVER_MAX_QPS = 30;
/**
* The maximum number of {@link com.google.api.services.monitoring.v3.model.TimeSeries} that can
* be bundled into a single {@link
* com.google.api.services.monitoring.v3.model.CreateTimeSeriesRequest}. This must be at most 200.
* Setting this lower will cause the {@link StackdriverWriter} to {@link
* StackdriverWriter#flush()} more frequently.
*/
private static final int STACKDRIVER_MAX_POINTS_PER_REQUEST = 200;
// Create some metrics to track your ZZZs.
private static final ImmutableList<String> SHEEP_COLORS =
ImmutableList.of("Green", "Yellow", "Red", "Blue");
private static final ImmutableList<String> SHEEP_SPECIES =
ImmutableList.of("Domestic", "Bighorn");
private static final ImmutableSet<LabelDescriptor> SHEEP_ATTRIBUTES =
ImmutableSet.of(
LabelDescriptor.create("color", "Sheep Color"),
LabelDescriptor.create("species", "Sheep Species"));
/**
* Counters are good for tracking monotonically increasing values, like request counts or error
* counts. Or, in this case, sheep.
*/
private static final IncrementableMetric sheepCounter =
MetricRegistryImpl.getDefault()
.newIncrementableMetric(
"/sheep", "Counts sheep over time.", "Number of Sheep", SHEEP_ATTRIBUTES);
/**
* Settable metrics are good for state indicators. For example, you could use one to track the
* lifecycle of a {@link com.google.common.util.concurrent.Service}. In this case, we are just
* using it to track the sleep state of this application.
*/
private static final SettableMetric<Boolean> isSleeping =
MetricRegistryImpl.getDefault()
.newSettableMetric(
"/is_sleeping",
"Tracks sleep state.",
"Sleeping?",
ImmutableSet.<LabelDescriptor>of(),
Boolean.class);
/**
* Gauge metrics never need to be accessed, so the assignment here is unnecessary. You only need
* it if you plan on calling {@link Metric#getTimestampedValues()} to read the values of the
* metric in the code yourself.
*/
@SuppressWarnings("unused")
private static final Metric<Double> sleepQuality =
MetricRegistryImpl.getDefault()
.newGauge(
"/sleep_quality",
"Quality of the sleep.",
"Quality",
ImmutableSet.<LabelDescriptor>of(),
new Supplier<ImmutableMap<ImmutableList<String>, Double>>() {
@Override
public ImmutableMap<ImmutableList<String>, Double> get() {
return ImmutableMap.of(ImmutableList.<String>of(), new Random().nextDouble());
}
},
Double.class);
/**
* Event metrics track aspects of an "event." Here, we track the fluffiness of the sheep we've
* seen.
*/
private static final EventMetric sheepFluffiness =
MetricRegistryImpl.getDefault()
.newEventMetric(
"/sheep_fluffiness",
"Measures the fluffiness of seen sheep.",
"Fill Power",
SHEEP_ATTRIBUTES,
LinearFitter.create(5, 20.0, 20.0));
private static Monitoring createAuthorizedMonitoringClient() throws IOException {
// Grab the Application Default Credentials from the environment.
// Generate these with 'gcloud beta auth application-default login'
GoogleCredential credential =
GoogleCredential.getApplicationDefault().createScoped(MonitoringScopes.all());
// Create and return the CloudMonitoring service object
HttpTransport httpTransport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
return new Monitoring.Builder(httpTransport, jsonFactory, credential)
.setApplicationName("Monitoring Sample")
.build();
}
public static void main(String[] args) throws Exception {
if (args.length < 1) {
System.err.println("Missing required project argument");
System.err.printf(
"Usage: java %s gcp-project-id [verbose]\n", SheepCounterExample.class.getName());
return;
}
String project = args[0];
// Turn up the logging verbosity
if (args.length > 1) {
Logger log = LogManager.getLogManager().getLogger("");
log.setLevel(Level.ALL);
for (Handler h : log.getHandlers()) {
h.setLevel(Level.ALL);
}
}
// Create a sample resource. In this case, a GCE Instance.
// See https://cloud.google.com/monitoring/api/resources for a list of resource types.
MonitoredResource monitoredResource =
new MonitoredResource()
.setType("gce_instance")
.setLabels(
ImmutableMap.of(
"instance_id", "test-instance",
"zone", "us-central1-f"));
// Set up the Metrics infrastructure.
MetricWriter stackdriverWriter =
new StackdriverWriter(
createAuthorizedMonitoringClient(),
project,
monitoredResource,
STACKDRIVER_MAX_QPS,
STACKDRIVER_MAX_POINTS_PER_REQUEST);
final MetricReporter reporter =
new MetricReporter(
stackdriverWriter, METRICS_REPORTING_INTERVAL, Executors.defaultThreadFactory());
reporter.startAsync().awaitRunning();
// Set up a handler to stop sleeping on SIGINT.
Runtime.getRuntime()
.addShutdownHook(
new Thread(
new Runnable() {
@Override
public void run() {
reporter.stopAsync().awaitTerminated();
// Allow the LogManager to cleanup the loggers.
DelayedShutdownLogManager.resetFinally();
}
}));
System.err.println("Send SIGINT (Ctrl+C) to stop sleeping.");
while (true) {
// Count some Googley sheep.
int colorIndex = new Random().nextInt(SHEEP_COLORS.size());
int speciesIndex = new Random().nextInt(SHEEP_SPECIES.size());
sheepCounter.incrementBy(1, SHEEP_COLORS.get(colorIndex), SHEEP_SPECIES.get(speciesIndex));
sheepFluffiness.record(
new Random().nextDouble() * 200,
SHEEP_COLORS.get(colorIndex),
SHEEP_SPECIES.get(speciesIndex));
isSleeping.set(true);
logger.info("zzz...");
Thread.sleep(5000);
}
}
/**
* Special {@link LogManager} with a no-op {@link LogManager#reset()} so that logging can proceed
* as usual until stopped in in another runtime shutdown hook.
*
* <p>The class is marked public because it is loaded by the JVM classloader at runtime.
*/
@SuppressWarnings("WeakerAccess")
public static class DelayedShutdownLogManager extends LogManager {
private static DelayedShutdownLogManager instance;
public DelayedShutdownLogManager() {
instance = this;
}
/** A no-op implementation. */
@Override
public void reset() {
/* don't reset yet. */
}
static void resetFinally() {
instance.delayedReset();
}
private void delayedReset() {
super.reset();
}
}
}

View file

@ -0,0 +1,34 @@
package(
default_testonly = 1,
default_visibility = ["//java/google/registry:registry_project"],
)
licenses(["notice"]) # Apache 2.0
load("//java/com/google/testing/builddefs:GenTestRules.bzl", "GenTestRules")
java_library(
name = "stackdriver",
srcs = glob(["*.java"]),
deps = [
"//java/google/registry/monitoring/metrics",
"//java/google/registry/monitoring/metrics/stackdriver",
"@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",
"@org_mockito_all",
],
)
GenTestRules(
name = "GeneratedTestRules",
test_files = glob(["*Test.java"]),
deps = [
":stackdriver",
],
)

View file

@ -0,0 +1,171 @@
// 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.stackdriver;
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!
}
}
}

View file

@ -0,0 +1,563 @@
// 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.stackdriver;
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.CustomFitter;
import google.registry.monitoring.metrics.Distribution;
import google.registry.monitoring.metrics.ExponentialFitter;
import google.registry.monitoring.metrics.LabelDescriptor;
import google.registry.monitoring.metrics.LinearFitter;
import google.registry.monitoring.metrics.Metric;
import google.registry.monitoring.metrics.MetricPoint;
import google.registry.monitoring.metrics.MetricSchema;
import google.registry.monitoring.metrics.MetricSchema.Kind;
import google.registry.monitoring.metrics.MutableDistribution;
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<Long> metric;
@Mock private Metric<Boolean> boolMetric;
@Mock private Metric<Distribution> distributionMetric;
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 {
when(metric.getValueClass()).thenReturn(Long.class);
when(metric.getCardinality()).thenReturn(1);
when(metric.getMetricSchema())
.thenReturn(
MetricSchema.create(
"/name",
"desc",
"vdn",
Kind.CUMULATIVE,
ImmutableSet.of(LabelDescriptor.create("label1", "desc1"))));
// Store in an intermediate value, because Mockito hates when mocks are evaluated inside of
// thenReturn() methods.
MetricPoint<Long> longPoint =
MetricPoint.create(
metric, ImmutableList.of("value1"), new Instant(1337), new Instant(1338), 5L);
when(metric.getTimestampedValues()).thenReturn(ImmutableList.of(longPoint));
when(boolMetric.getValueClass()).thenReturn(Boolean.class);
when(boolMetric.getMetricSchema())
.thenReturn(
MetricSchema.create(
"/name",
"desc",
"vdn",
Kind.GAUGE,
ImmutableSet.of(LabelDescriptor.create("label1", "desc1"))));
// Store in an intermediate value, because Mockito hates when mocks are evaluated inside of
// thenReturn() methods.
MetricPoint<Boolean> boolPoint =
MetricPoint.create(boolMetric, ImmutableList.of("foo"), new Instant(1337), true);
when(boolMetric.getTimestampedValues()).thenReturn(ImmutableList.of(boolPoint));
when(distributionMetric.getMetricSchema())
.thenReturn(
MetricSchema.create(
"/name",
"desc",
"vdn",
Kind.GAUGE,
ImmutableSet.of(LabelDescriptor.create("label1", "desc1"))));
when(distributionMetric.getValueClass()).thenReturn(Distribution.class);
MetricDescriptor 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 {
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()) {
writer.write(point);
}
}
verify(writer).flush();
}
@Test
public void testWrite_lessThanMaxPoints_doesNotFlush() throws Exception {
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()) {
writer.write(point);
}
}
verify(writer, never()).flush();
}
@Test
public void testWrite_invalidMetricType_throwsException() throws Exception {
when(metric.getValueClass())
.thenAnswer(
new Answer<Class<?>>() {
@Override
public Class<?> answer(InvocationOnMock invocation) throws Throwable {
return Object.class;
}
});
StackdriverWriter writer =
new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST);
for (MetricPoint<?> point : metric.getTimestampedValues()) {
try {
writer.write(point);
fail("expected IllegalArgumentException");
} catch (IOException expected) {}
}
}
@Test
public void testWrite_ManyPoints_flushesTwice() throws Exception {
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()) {
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("label1")
.setDescription("desc1"));
}
@Test
public void encodeLabelDescriptors_simpleLabels_encodes() {
ImmutableSet<LabelDescriptor> descriptors =
ImmutableSet.of(
LabelDescriptor.create("label1", "description1"),
LabelDescriptor.create("label2", "description2"));
ImmutableList<com.google.api.services.monitoring.v3.model.LabelDescriptor> 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<Long> 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<Point> 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<Long> 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<Point> 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 {
when(metric.getMetricSchema())
.thenReturn(
MetricSchema.create(
"/name",
"desc",
"vdn",
Kind.GAUGE,
ImmutableSet.of(LabelDescriptor.create("label1", "desc1"))));
// Store in an intermediate value, because Mockito hates when mocks are evaluated inside of
// thenReturn() methods.
MetricPoint<Long> testPoint =
MetricPoint.create(metric, ImmutableList.of("foo"), new Instant(1337), 10L);
when(metric.getTimestampedValues()).thenReturn(ImmutableList.of(testPoint));
// Store in an intermediate value, because Mockito hates when mocks are evaluated inside of
// thenReturn() methods.
MetricDescriptor descriptor = StackdriverWriter.encodeMetricDescriptor(metric);
when(metricDescriptorCreate.execute()).thenReturn(descriptor);
StackdriverWriter writer =
new StackdriverWriter(client, PROJECT, MONITORED_RESOURCE, MAX_QPS, MAX_POINTS_PER_REQUEST);
MetricPoint<Long> 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<Point> 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);
MetricDescriptor boolDescriptor = StackdriverWriter.encodeMetricDescriptor(boolMetric);
when(metricDescriptorCreate.execute()).thenReturn(boolDescriptor);
MetricPoint<Boolean> 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<Point> 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);
MetricDescriptor descriptor = StackdriverWriter.encodeMetricDescriptor(distributionMetric);
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<Distribution> nativePoint =
MetricPoint.create(
distributionMetric, ImmutableList.of("foo"), new Instant(1337), distribution);
TimeSeries timeSeries = writer.getEncodedTimeSeries(nativePoint);
assertThat(timeSeries.getValueType()).isEqualTo("DISTRIBUTION");
assertThat(timeSeries.getMetricKind()).isEqualTo("GAUGE");
List<Point> 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);
MetricDescriptor descriptor = StackdriverWriter.encodeMetricDescriptor(distributionMetric);
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<Distribution> nativePoint =
MetricPoint.create(
distributionMetric, ImmutableList.of("foo"), new Instant(1337), distribution);
TimeSeries timeSeries = writer.getEncodedTimeSeries(nativePoint);
assertThat(timeSeries.getValueType()).isEqualTo("DISTRIBUTION");
assertThat(timeSeries.getMetricKind()).isEqualTo("GAUGE");
List<Point> 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);
MetricDescriptor descriptor = StackdriverWriter.encodeMetricDescriptor(distributionMetric);
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<Distribution> nativePoint =
MetricPoint.create(
distributionMetric, ImmutableList.of("foo"), new Instant(1337), distribution);
TimeSeries timeSeries = writer.getEncodedTimeSeries(nativePoint);
assertThat(timeSeries.getValueType()).isEqualTo("DISTRIBUTION");
assertThat(timeSeries.getMetricKind()).isEqualTo("GAUGE");
List<Point> 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");
}
}