├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── contrib ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── google │ │ └── monitoring │ │ └── metrics │ │ └── contrib │ │ ├── AbstractMetricSubject.java │ │ ├── DistributionMetricSubject.java │ │ └── LongMetricSubject.java │ └── test │ └── java │ └── com │ └── google │ └── monitoring │ └── metrics │ └── contrib │ ├── DistributionMetricSubjectTest.java │ └── LongMetricSubjectTest.java ├── metrics ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── google │ │ └── monitoring │ │ └── metrics │ │ ├── AbstractMetric.java │ │ ├── Counter.java │ │ ├── CustomFitter.java │ │ ├── Distribution.java │ │ ├── DistributionFitter.java │ │ ├── EventMetric.java │ │ ├── ExponentialFitter.java │ │ ├── FibonacciFitter.java │ │ ├── ImmutableDistribution.java │ │ ├── IncrementableMetric.java │ │ ├── LabelDescriptor.java │ │ ├── LinearFitter.java │ │ ├── Metric.java │ │ ├── MetricExporter.java │ │ ├── MetricMetrics.java │ │ ├── MetricPoint.java │ │ ├── MetricRegistry.java │ │ ├── MetricRegistryImpl.java │ │ ├── MetricReporter.java │ │ ├── MetricSchema.java │ │ ├── MetricWriter.java │ │ ├── MetricsUtils.java │ │ ├── MutableDistribution.java │ │ ├── SettableMetric.java │ │ ├── StoredMetric.java │ │ ├── VirtualMetric.java │ │ └── package-info.java │ └── test │ └── java │ └── com │ └── google │ └── monitoring │ └── metrics │ ├── CounterTest.java │ ├── CustomFitterTest.java │ ├── EventMetricTest.java │ ├── ExponentialFitterTest.java │ ├── FibonacciFitterTest.java │ ├── LabelDescriptorTest.java │ ├── LinearFitterTest.java │ ├── MetricExporterTest.java │ ├── MetricRegistryImplTest.java │ ├── MetricReporterTest.java │ ├── MetricSchemaTest.java │ ├── MutableDistributionTest.java │ ├── StoredMetricTest.java │ └── VirtualMetricTest.java ├── pom.xml └── stackdriver ├── pom.xml └── src ├── main └── java │ └── com │ └── google │ └── monitoring │ └── metrics │ └── stackdriver │ └── StackdriverWriter.java └── test └── java └── com └── google └── monitoring └── metrics ├── example └── SheepCounterExample.java └── stackdriver ├── GoogleJsonResponseExceptionHelper.java └── StackdriverWriterTest.java /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | - openjdk11 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Monitoring Client Library for Java 2 | 3 | [![Build 4 | Status](https://travis-ci.org/google/java-monitoring-client-library.svg?branch=master)](https://travis-ci.org/google/java-monitoring-client-library) 5 | 6 | This is not an official Google product. 7 | 8 | This library provides an API that is powerful and java idiomatic for configuring 9 | and publishing application metrics. A reference implementation using 10 | [Stackdriver Monitoring API v3](https://cloud.google.com/monitoring/api/v3/) is 11 | included, but other monitoring backend implementations can also be used. 12 | 13 | Most of other monitoring libraries available are low-level and are tied directly 14 | to the backend. This library provides type safety, retry logic and an 15 | backend-agnostic approach to Java metrics instrumentation. 16 | 17 | ## Basic concepts 18 | 19 | * Metric Types 20 | 21 | - `Counters`: monotonically increasing integers. e. g. number of total 22 | requests. 23 | - `EventMetrics`: data points in certain distribution, used in combination 24 | with a `DistributionFitter`. e. g. latency distribution, request size 25 | distribution. 26 | - `Gauges`: state indicators, used with a callback function to query the 27 | state. e. g. number of active connections, current memory usage. 28 | 29 | In general, cumulative values are counters and cumulative probability 30 | distributions, and non-cumulative values are gauges. 31 | 32 | A metric class consists of typed values (for type safety) and string labels. 33 | The labels are used to identify a specific metric time series, for example, 34 | a counter for the number of requests can have label "client_ip_address" and 35 | "protocol". In this example, all requests coming from the same client IP and 36 | using the same protocol would be counted together. 37 | 38 | Metrics are modeled after [Stackdriver 39 | Metrics](https://cloud.google.com/monitoring/api/v3/metrics). 40 | 41 | ## Importing the library 42 | 43 | The most recent release is [v1.0.5](https://github.com/google/java-monitoring-client-library/releases/). 44 | 45 | The Maven group ID is `com.google.monitoring-client`. The artifact ID is 46 | `metrics` for the main library, and `stackdriver` for the stackdriver backend 47 | writer. We also provide a `contrib` library that is useful if you want to make test 48 | assertions on certain metric types with Google's [truth](https://github.com/google/truth) 49 | library. 50 | 51 | To add a dependency on the metrics library using Maven: 52 | 53 | ```xml 54 | 55 | com.google.monitoring-client 56 | metrics 57 | 1.0.5 58 | 59 | ``` 60 | 61 | ## Using the library 62 | 63 | * Registering Metrics 64 | 65 | To register a metric, specify the name is should be registered (in style of 66 | an URL path), and the set of `LabelDescriptor` of the metric. For example to 67 | register a counter: 68 | 69 | ```java 70 | IncrementableMetric myCounter = MetricRegistryImpl.getDefault() 71 | .newIncrementableMetric( 72 | "/path/to/record/metrics", 73 | "description", 74 | "value_unit", 75 | labels); 76 | ``` 77 | 78 | * Recording Metrics 79 | 80 | To record a data point to the metric we just registered, call its recording 81 | method, specify the labels (required), and value (required for `EventMetric` 82 | ). In case of a `Counter`, just supplying the labels is sufficient as the 83 | value is implicitly increased by one. 84 | 85 | ```java 86 | myCounter.increment("label1", "label2", "label3"); 87 | ``` 88 | 89 | * Exporting Metrics 90 | 91 | To export metrics to a monitoring backend, you need configure your backend 92 | accordingly and implement a `MetricWriter` that talks to your backend. A 93 | `StackdriverWriter` is provided. A `MetricReporter` needs to be constructed 94 | from the `MetricWriter`: 95 | 96 | ```java 97 | MetricReporter metricReporter = new MetricReporter( 98 | metricWriter, 99 | writeIntervalInSeconds, 100 | threadFactory); 101 | 102 | ``` 103 | 104 | A thread factory is needed so that the metric reporter can run in the 105 | background periodically to export recorded metrics (in batch) to the 106 | backend. It is recommended to set the thread to daemon mode so that it does 107 | not interfere with JVM shutdown. You can use `ThreadFactoryBuilder` from 108 | [Guava](https://google.github.io/guava/releases/23.0/api/docs/com/google/common/util/concurrent/ThreadFactoryBuilder.html): 109 | 110 | ```java 111 | ThreadFactory threadFactory = ThreadFactoryBuilder().setDaemon(true).build(); 112 | ``` 113 | 114 | Then in you `main` method, start the metric reporter asynchronously: 115 | 116 | ```java 117 | metricReporter.get().startAsync().awaitRunning(10, TimeUnit.SECONDS); 118 | ``` 119 | 120 | The reporter will now run in the background and automatically exports 121 | metrics at the given `writeIntervalInSeconds`. 122 | -------------------------------------------------------------------------------- /contrib/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.google.monitoring-client 8 | monitoring 9 | 1.0.8-SNAPSHOT 10 | .. 11 | 12 | 13 | contrib 14 | Metrics Test Subjects 15 | Test subjects for various metric objects. 16 | 17 | jar 18 | 19 | 20 | 21 | 22 | 23 | com.google.truth 24 | truth 25 | 0.44 26 | 27 | 28 | 29 | com.google.monitoring-client 30 | metrics 31 | 1.0.8-SNAPSHOT 32 | 33 | 34 | 35 | 36 | 37 | com.google.truth.extensions 38 | truth-java8-extension 39 | 0.44 40 | test 41 | 42 | 43 | 44 | junit 45 | junit 46 | 4.13.1 47 | test 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /contrib/src/main/java/com/google/monitoring/metrics/contrib/AbstractMetricSubject.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics.contrib; 16 | 17 | import static com.google.common.base.Preconditions.checkNotNull; 18 | 19 | import com.google.common.base.Function; 20 | import com.google.common.base.Joiner; 21 | import com.google.common.collect.ImmutableList; 22 | import com.google.common.collect.Lists; 23 | import com.google.common.collect.Ordering; 24 | import com.google.common.truth.FailureMetadata; 25 | import com.google.common.truth.Subject; 26 | import com.google.monitoring.metrics.Metric; 27 | import com.google.monitoring.metrics.MetricPoint; 28 | import java.util.HashSet; 29 | import java.util.Set; 30 | import javax.annotation.Nullable; 31 | 32 | /** 33 | * Base truth subject for asserting things about {@link Metric} instances. 34 | * 35 | *

For use with the Google Truth framework. 36 | */ 37 | abstract class AbstractMetricSubject> 38 | extends Subject> { 39 | 40 | /** And chainer to allow fluent assertions. */ 41 | public static class And> { 42 | 43 | private final S subject; 44 | 45 | And(S subject) { 46 | this.subject = subject; 47 | } 48 | 49 | public S and() { 50 | return subject; 51 | } 52 | } 53 | 54 | @SuppressWarnings("unchecked") 55 | And andChainer() { 56 | return new And<>((S) this); 57 | } 58 | 59 | /** 60 | * List of label value tuples about which an assertion has been made so far. 61 | * 62 | *

Used to track what tuples have been seen, in order to support hasNoOtherValues() assertions. 63 | */ 64 | protected final Set> expectedNondefaultLabelTuples = new HashSet<>(); 65 | 66 | /** 67 | * Function to convert a metric point to a nice string representation for use in error messages. 68 | */ 69 | protected final Function, String> metricPointConverter = 70 | metricPoint -> 71 | String.format( 72 | "%s => %s", 73 | Joiner.on(':').join(metricPoint.labelValues()), 74 | getMessageRepresentation(metricPoint.value())); 75 | 76 | private final Metric actual; 77 | 78 | protected AbstractMetricSubject(FailureMetadata metadata, Metric actual) { 79 | super(metadata, checkNotNull(actual)); 80 | this.actual = actual; 81 | } 82 | 83 | /** 84 | * Returns the string representation of the subject. 85 | * 86 | *

For metrics, it makes sense to use the metric name, as given in the schema. 87 | */ 88 | @Override 89 | public String actualCustomStringRepresentation() { 90 | return actual.getMetricSchema().name(); 91 | } 92 | 93 | /** 94 | * Asserts that the metric has a given value for the specified label values. 95 | * 96 | * @param value the value which the metric should have 97 | * @param labels the labels for which the value is being asserted; the number and order of labels 98 | * should match the definition of the metric 99 | */ 100 | public And hasValueForLabels(T value, String... labels) { 101 | MetricPoint metricPoint = findMetricPointForLabels(ImmutableList.copyOf(labels)); 102 | if (metricPoint == null) { 103 | failWithBadResults( 104 | "has a value for labels", 105 | Joiner.on(':').join(labels), 106 | "has labeled values", 107 | Lists.transform( 108 | Ordering.>natural().sortedCopy(actual.getTimestampedValues()), 109 | metricPointConverter)); 110 | } 111 | if (!metricPoint.value().equals(value)) { 112 | failWithBadResults( 113 | String.format("has a value of %s for labels", getMessageRepresentation(value)), 114 | Joiner.on(':').join(labels), 115 | "has a value of", 116 | getMessageRepresentation(metricPoint.value())); 117 | } 118 | expectedNondefaultLabelTuples.add(ImmutableList.copyOf(labels)); 119 | return andChainer(); 120 | } 121 | 122 | /** 123 | * Asserts that the metric has any (non-default) value for the specified label values. 124 | * 125 | * @param labels the labels for which the value is being asserted; the number and order of labels 126 | * should match the definition of the metric 127 | */ 128 | public And hasAnyValueForLabels(String... labels) { 129 | MetricPoint metricPoint = findMetricPointForLabels(ImmutableList.copyOf(labels)); 130 | if (metricPoint == null) { 131 | failWithBadResults( 132 | "has a value for labels", 133 | Joiner.on(':').join(labels), 134 | "has labeled values", 135 | Lists.transform( 136 | Ordering.>natural().sortedCopy(actual.getTimestampedValues()), 137 | metricPointConverter)); 138 | } 139 | if (hasDefaultValue(metricPoint)) { 140 | failWithBadResults( 141 | "has a non-default value for labels", 142 | Joiner.on(':').join(labels), 143 | "has a value of", 144 | getMessageRepresentation(metricPoint.value())); 145 | } 146 | expectedNondefaultLabelTuples.add(ImmutableList.copyOf(labels)); 147 | return andChainer(); 148 | } 149 | 150 | /** Asserts that the metric does not have a (non-default) value for the specified label values. */ 151 | protected And doesNotHaveAnyValueForLabels(String... labels) { 152 | MetricPoint metricPoint = findMetricPointForLabels(ImmutableList.copyOf(labels)); 153 | if (metricPoint != null) { 154 | failWithBadResults( 155 | "has no value for labels", 156 | Joiner.on(':').join(labels), 157 | "has a value of", 158 | getMessageRepresentation(metricPoint.value())); 159 | } 160 | return andChainer(); 161 | } 162 | 163 | /** 164 | * Asserts that the metric has no (non-default) values other than those about which an assertion 165 | * has already been made. 166 | */ 167 | public And hasNoOtherValues() { 168 | for (MetricPoint metricPoint : actual.getTimestampedValues()) { 169 | if (!expectedNondefaultLabelTuples.contains(metricPoint.labelValues())) { 170 | if (!hasDefaultValue(metricPoint)) { 171 | failWithBadResults( 172 | "has", 173 | "no other nondefault values", 174 | "has labeled values", 175 | Lists.transform( 176 | Ordering.>natural().sortedCopy(actual.getTimestampedValues()), 177 | metricPointConverter)); 178 | } 179 | return andChainer(); 180 | } 181 | } 182 | return andChainer(); 183 | } 184 | 185 | private @Nullable MetricPoint findMetricPointForLabels(ImmutableList labels) { 186 | if (actual.getMetricSchema().labels().size() != labels.size()) { 187 | return null; 188 | } 189 | for (MetricPoint metricPoint : actual.getTimestampedValues()) { 190 | if (metricPoint.labelValues().equals(labels)) { 191 | return metricPoint; 192 | } 193 | } 194 | return null; 195 | } 196 | 197 | /** 198 | * Returns a string representation of a metric point value, for use in error messages. 199 | * 200 | *

Subclass can override this method if the string needs extra processing. 201 | */ 202 | protected String getMessageRepresentation(T value) { 203 | return String.valueOf(value); 204 | } 205 | 206 | /** 207 | * Returns true if the metric point has a non-default value. 208 | * 209 | *

This should be overridden by subclasses. E.g. for incrementable metrics, the method should 210 | * return true if the value is not zero, and so on. 211 | */ 212 | protected abstract boolean hasDefaultValue(MetricPoint metricPoint); 213 | } 214 | -------------------------------------------------------------------------------- /contrib/src/main/java/com/google/monitoring/metrics/contrib/DistributionMetricSubject.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics.contrib; 16 | 17 | import static com.google.common.truth.Truth.assertAbout; 18 | 19 | import com.google.common.base.Joiner; 20 | import com.google.common.collect.BoundType; 21 | import com.google.common.collect.ImmutableList; 22 | import com.google.common.collect.ImmutableSet; 23 | import com.google.common.collect.Range; 24 | import com.google.common.truth.FailureMetadata; 25 | import com.google.monitoring.metrics.Distribution; 26 | import com.google.monitoring.metrics.ImmutableDistribution; 27 | import com.google.monitoring.metrics.Metric; 28 | import com.google.monitoring.metrics.MetricPoint; 29 | import com.google.monitoring.metrics.MutableDistribution; 30 | import java.util.Map; 31 | import javax.annotation.Nullable; 32 | 33 | /** 34 | * Truth subject for {@link Distribution} {@link Metric} instances. 35 | * 36 | *

For use with the Google Truth framework. Usage: 37 | * 38 | *

  assertThat(myDistributionMetric)
 39 |  *       .hasAnyValueForLabels("label1", "label2", "label3")
 40 |  *       .and()
 41 |  *       .hasNoOtherValues();
 42 |  *   assertThat(myDistributionMetric)
 43 |  *       .doesNotHaveAnyValueForLabels("label1", "label2");
 44 |  *   assertThat(myDistributionMetric)
 45 |  *       .hasDataSetForLabels(ImmutableSet.of(data1, data2, data3), "label1", "label2");
 46 |  * 
47 | * 48 | *

The assertions treat an empty distribution as no value at all. This is not how the data is 49 | * actually stored; event metrics do in fact have an empty distribution after they are reset. But 50 | * it's difficult to write assertions about expected metric data when any number of empty 51 | * distributions can also be present, so they are screened out for convenience. 52 | */ 53 | public final class DistributionMetricSubject 54 | extends AbstractMetricSubject { 55 | 56 | /** Static shortcut method for {@link Distribution} {@link Metric} objects. */ 57 | public static DistributionMetricSubject assertThat(@Nullable Metric metric) { 58 | return assertAbout(DistributionMetricSubject::new).that(metric); 59 | } 60 | 61 | private final Metric actual; 62 | 63 | private DistributionMetricSubject(FailureMetadata metadata, Metric actual) { 64 | super(metadata, actual); 65 | this.actual = actual; 66 | } 67 | 68 | /** 69 | * Returns an indication to {@link AbstractMetricSubject#hasNoOtherValues} on whether a {@link 70 | * MetricPoint} has a non-empty distribution. 71 | */ 72 | @Override 73 | protected boolean hasDefaultValue(MetricPoint metricPoint) { 74 | return metricPoint.value().count() == 0; 75 | } 76 | 77 | /** Returns an appropriate string representation of a metric value for use in error messages. */ 78 | @Override 79 | protected String getMessageRepresentation(Distribution distribution) { 80 | StringBuilder sb = new StringBuilder("{"); 81 | boolean first = true; 82 | for (Map.Entry, Long> entry : 83 | distribution.intervalCounts().asMapOfRanges().entrySet()) { 84 | if (entry.getValue() != 0L) { 85 | if (first) { 86 | first = false; 87 | } else { 88 | sb.append(','); 89 | } 90 | if (entry.getKey().hasLowerBound()) { 91 | sb.append((entry.getKey().lowerBoundType() == BoundType.CLOSED) ? '[' : '('); 92 | sb.append(entry.getKey().lowerEndpoint()); 93 | } 94 | sb.append(".."); 95 | if (entry.getKey().hasUpperBound()) { 96 | sb.append(entry.getKey().upperEndpoint()); 97 | sb.append((entry.getKey().upperBoundType() == BoundType.CLOSED) ? ']' : ')'); 98 | } 99 | sb.append('='); 100 | sb.append(entry.getValue()); 101 | } 102 | } 103 | sb.append('}'); 104 | return sb.toString(); 105 | } 106 | 107 | /** 108 | * Asserts that the distribution for the given label can be constructed from the given data set. 109 | * 110 | *

Note that this only tests that the distribution has the same binned histogram, along with 111 | * the same mean, and sum of squared deviation as it would if it had recorded the specified data 112 | * points. It could have in fact collected different data points that resulted in the same 113 | * distribution, but that information is lost to us and cannot be tested. 114 | */ 115 | public And hasDataSetForLabels( 116 | ImmutableSet dataSet, String... labels) { 117 | ImmutableList> metricPoints = actual.getTimestampedValues(); 118 | if (metricPoints.isEmpty()) { 119 | failWithBadResults( 120 | "has a distribution for labels", Joiner.on(':').join(labels), "has", "no values"); 121 | } 122 | MutableDistribution targetDistribution = 123 | new MutableDistribution(metricPoints.get(0).value().distributionFitter()); 124 | dataSet.forEach(data -> targetDistribution.add(data.doubleValue())); 125 | return hasValueForLabels(ImmutableDistribution.copyOf(targetDistribution), labels); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /contrib/src/main/java/com/google/monitoring/metrics/contrib/LongMetricSubject.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics.contrib; 16 | 17 | import static com.google.common.truth.Truth.assertAbout; 18 | 19 | import com.google.common.truth.FailureMetadata; 20 | import com.google.common.truth.Subject; 21 | import com.google.monitoring.metrics.Metric; 22 | import com.google.monitoring.metrics.MetricPoint; 23 | import javax.annotation.Nullable; 24 | 25 | /** 26 | * Truth subject for {@link Long} {@link Metric} instances. 27 | * 28 | *

For use with the Google Truth framework. Usage: 29 | * 30 | *

  assertThat(myLongMetric)
31 |  *       .hasValueForLabels(5, "label1", "label2", "label3")
32 |  *       .and()
33 |  *       .hasAnyValueForLabels("label1", "label2", "label4")
34 |  *       .and()
35 |  *       .hasNoOtherValues();
36 |  *   assertThat(myLongMetric)
37 |  *       .doesNotHaveAnyValueForLabels("label1", "label2");
38 |  * 
39 | * 40 | *

The assertions treat a value of 0 as no value at all. This is not how the data is actually 41 | * stored; zero is a valid value for incrementable metrics, and they do in fact have a value of zero 42 | * after they are reset. But it's difficult to write assertions about expected metric data when any 43 | * number of zero values can also be present, so they are screened out for convenience. 44 | */ 45 | public final class LongMetricSubject extends AbstractMetricSubject { 46 | 47 | /** Static shortcut method for {@link Long} metrics. */ 48 | public static LongMetricSubject assertThat(@Nullable Metric metric) { 49 | return assertAbout(LongMetricSubject::new).that(metric); 50 | } 51 | 52 | private LongMetricSubject(FailureMetadata metadata, Metric actual) { 53 | super(metadata, actual); 54 | } 55 | 56 | /** 57 | * Asserts that the metric has a given value for the specified label values. This is a convenience 58 | * method that takes a long instead of a Long, for ease of use. 59 | */ 60 | public And hasValueForLabels(long value, String... labels) { 61 | return hasValueForLabels(Long.valueOf(value), labels); 62 | } 63 | 64 | /** 65 | * Returns an indication to {@link AbstractMetricSubject#hasNoOtherValues} on whether a {@link 66 | * MetricPoint} has a non-zero value. 67 | */ 68 | @Override 69 | protected boolean hasDefaultValue(MetricPoint metricPoint) { 70 | return metricPoint.value() == 0L; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /contrib/src/test/java/com/google/monitoring/metrics/contrib/DistributionMetricSubjectTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics.contrib; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static com.google.monitoring.metrics.contrib.DistributionMetricSubject.assertThat; 19 | import static org.junit.Assert.assertThrows; 20 | 21 | import com.google.common.collect.ImmutableSet; 22 | import com.google.monitoring.metrics.EventMetric; 23 | import com.google.monitoring.metrics.LabelDescriptor; 24 | import com.google.monitoring.metrics.MetricRegistryImpl; 25 | import org.junit.Before; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.junit.runners.JUnit4; 29 | 30 | @RunWith(JUnit4.class) 31 | public class DistributionMetricSubjectTest { 32 | 33 | private static final ImmutableSet LABEL_DESCRIPTORS = 34 | ImmutableSet.of( 35 | LabelDescriptor.create("species", "Sheep Species"), 36 | LabelDescriptor.create("color", "Sheep Color")); 37 | 38 | private static final EventMetric metric = 39 | MetricRegistryImpl.getDefault() 40 | .newEventMetric( 41 | "/test/event/sheep", 42 | "Sheep Latency", 43 | "sheeplatency", 44 | LABEL_DESCRIPTORS, 45 | EventMetric.DEFAULT_FITTER); 46 | 47 | @Before 48 | public void before() { 49 | metric.reset(); 50 | metric.record(2.5, "Domestic", "Green"); 51 | metric.record(10, "Bighorn", "Blue"); 52 | } 53 | 54 | @Test 55 | public void testWrongNumberOfLabels_fails() { 56 | AssertionError e = 57 | assertThrows( 58 | AssertionError.class, () -> assertThat(metric).hasAnyValueForLabels("Domestic")); 59 | assertThat(e) 60 | .hasMessageThat() 61 | .isEqualTo( 62 | "Not true that has a value for labels ." 63 | + " It has labeled values <[Bighorn:Blue =>" 64 | + " {[4.0..16.0)=1}, Domestic:Green => {[1.0..4.0)=1}]>"); 65 | } 66 | 67 | @Test 68 | public void testDoesNotHaveWrongNumberOfLabels_succeeds() { 69 | assertThat(metric).doesNotHaveAnyValueForLabels("Domestic"); 70 | } 71 | 72 | @Test 73 | public void testHasAnyValueForLabels_success() { 74 | assertThat(metric) 75 | .hasAnyValueForLabels("Domestic", "Green") 76 | .and() 77 | .hasAnyValueForLabels("Bighorn", "Blue") 78 | .and() 79 | .hasNoOtherValues(); 80 | } 81 | 82 | @Test 83 | public void testDoesNotHaveValueForLabels_success() { 84 | assertThat(metric).doesNotHaveAnyValueForLabels("Domestic", "Blue"); 85 | } 86 | 87 | @Test 88 | public void testDoesNotHaveValueForLabels_failure() { 89 | AssertionError e = 90 | assertThrows( 91 | AssertionError.class, 92 | () -> assertThat(metric).doesNotHaveAnyValueForLabels("Domestic", "Green")); 93 | assertThat(e) 94 | .hasMessageThat() 95 | .isEqualTo( 96 | "Not true that has no value for labels ." 97 | + " It has a value of <{[1.0..4.0)=1}>"); 98 | } 99 | 100 | @Test 101 | public void testUnexpectedValue_failure() { 102 | AssertionError e = 103 | assertThrows( 104 | AssertionError.class, 105 | () -> 106 | assertThat(metric) 107 | .hasAnyValueForLabels("Domestic", "Green") 108 | .and() 109 | .hasNoOtherValues()); 110 | assertThat(e) 111 | .hasMessageThat() 112 | .isEqualTo( 113 | "Not true that has ." 114 | + " It has labeled values <[Bighorn:Blue =>" 115 | + " {[4.0..16.0)=1}, Domestic:Green => {[1.0..4.0)=1}]>"); 116 | } 117 | 118 | @Test 119 | public void testExpectedDataSet_success() { 120 | metric.record(7.5, "Domestic", "Green"); 121 | assertThat(metric).hasDataSetForLabels(ImmutableSet.of(2.5, 7.5), "Domestic", "Green"); 122 | } 123 | 124 | @Test 125 | public void testExpectedDataSetsChained_success() { 126 | metric.record(7.5, "Domestic", "Green"); 127 | assertThat(metric) 128 | .hasDataSetForLabels(ImmutableSet.of(2.5, 7.5), "Domestic", "Green") 129 | .and() 130 | .hasDataSetForLabels(ImmutableSet.of(10), "Bighorn", "Blue") 131 | .and() 132 | .hasNoOtherValues(); 133 | } 134 | 135 | @Test 136 | public void testUnexpectedDataSet_failure() { 137 | AssertionError e = 138 | assertThrows( 139 | AssertionError.class, 140 | () -> 141 | assertThat(metric) 142 | .hasDataSetForLabels(ImmutableSet.of(2.5, 7.5), "Domestic", "Green")); 143 | assertThat(e) 144 | .hasMessageThat() 145 | .isEqualTo( 146 | "Not true that has a value of" 147 | + " {[1.0..4.0)=1,[4.0..16.0)=1} for labels ." 148 | + " It has a value of <{[1.0..4.0)=1}>"); 149 | } 150 | 151 | @Test 152 | public void testNonExistentLabels_failure() { 153 | AssertionError e = 154 | assertThrows( 155 | AssertionError.class, 156 | () -> 157 | assertThat(metric) 158 | .hasDataSetForLabels(ImmutableSet.of(2.5, 7.5), "Domestic", "Blue")); 159 | assertThat(e) 160 | .hasMessageThat() 161 | .isEqualTo( 162 | "Not true that has a value for labels ." 163 | + " It has labeled values <[Bighorn:Blue => {[4.0..16.0)=1}," 164 | + " Domestic:Green => {[1.0..4.0)=1}]>"); 165 | } 166 | 167 | @Test 168 | public void testEmptyMetric_failure() { 169 | EventMetric emptyMetric = 170 | MetricRegistryImpl.getDefault() 171 | .newEventMetric( 172 | "/test/event/goat", 173 | "Sheep Latency", 174 | "sheeplatency", 175 | LABEL_DESCRIPTORS, 176 | EventMetric.DEFAULT_FITTER); 177 | AssertionError e = 178 | assertThrows( 179 | AssertionError.class, 180 | () -> 181 | assertThat(emptyMetric) 182 | .hasDataSetForLabels(ImmutableSet.of(2.5, 7.5), "Domestic", "Blue")); 183 | assertThat(e) 184 | .hasMessageThat() 185 | .isEqualTo( 186 | "Not true that has a distribution for labels ." 187 | + " It has "); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /contrib/src/test/java/com/google/monitoring/metrics/contrib/LongMetricSubjectTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics.contrib; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat; 19 | import static org.junit.Assert.assertThrows; 20 | 21 | import com.google.common.collect.ImmutableSet; 22 | import com.google.monitoring.metrics.IncrementableMetric; 23 | import com.google.monitoring.metrics.LabelDescriptor; 24 | import com.google.monitoring.metrics.MetricRegistryImpl; 25 | import org.junit.Before; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.junit.runners.JUnit4; 29 | 30 | @RunWith(JUnit4.class) 31 | public class LongMetricSubjectTest { 32 | 33 | private static final ImmutableSet LABEL_DESCRIPTORS = 34 | ImmutableSet.of( 35 | LabelDescriptor.create("species", "Sheep Species"), 36 | LabelDescriptor.create("color", "Sheep Color")); 37 | 38 | private static final IncrementableMetric metric = 39 | MetricRegistryImpl.getDefault() 40 | .newIncrementableMetric( 41 | "/test/incrementable/sheep", 42 | "Count of Sheep", 43 | "sheepcount", 44 | LABEL_DESCRIPTORS); 45 | 46 | @Before 47 | public void before() { 48 | metric.reset(); 49 | metric.increment("Domestic", "Green"); 50 | metric.incrementBy(2, "Bighorn", "Blue"); 51 | } 52 | 53 | @Test 54 | public void testWrongNumberOfLabels_fails() { 55 | AssertionError e = 56 | assertThrows( 57 | AssertionError.class, () -> assertThat(metric).hasValueForLabels(1, "Domestic")); 58 | assertThat(e) 59 | .hasMessageThat() 60 | .isEqualTo( 61 | "Not true that has a value for labels ." 62 | + " It has labeled values <[Bighorn:Blue => 2, Domestic:Green => 1]>"); 63 | } 64 | 65 | @Test 66 | public void testDoesNotHaveWrongNumberOfLabels_succeeds() { 67 | assertThat(metric).doesNotHaveAnyValueForLabels("Domestic"); 68 | } 69 | 70 | @Test 71 | public void testHasValueForLabels_success() { 72 | assertThat(metric) 73 | .hasValueForLabels(1, "Domestic", "Green") 74 | .and() 75 | .hasValueForLabels(2, "Bighorn", "Blue") 76 | .and() 77 | .hasNoOtherValues(); 78 | } 79 | 80 | @Test 81 | public void testHasAnyValueForLabels_success() { 82 | assertThat(metric) 83 | .hasAnyValueForLabels("Domestic", "Green") 84 | .and() 85 | .hasAnyValueForLabels("Bighorn", "Blue") 86 | .and() 87 | .hasNoOtherValues(); 88 | } 89 | 90 | @Test 91 | public void testDoesNotHaveValueForLabels_success() { 92 | assertThat(metric).doesNotHaveAnyValueForLabels("Domestic", "Blue"); 93 | } 94 | 95 | @Test 96 | public void testDoesNotHaveValueForLabels_failure() { 97 | AssertionError e = 98 | assertThrows( 99 | AssertionError.class, 100 | () -> assertThat(metric).doesNotHaveAnyValueForLabels("Domestic", "Green")); 101 | assertThat(e) 102 | .hasMessageThat() 103 | .isEqualTo( 104 | "Not true that has no value for labels ." 105 | + " It has a value of <1>"); 106 | } 107 | 108 | @Test 109 | public void testWrongValue_failure() { 110 | AssertionError e = 111 | assertThrows( 112 | AssertionError.class, 113 | () -> assertThat(metric).hasValueForLabels(2, "Domestic", "Green")); 114 | assertThat(e) 115 | .hasMessageThat() 116 | .isEqualTo( 117 | "Not true that has a value of 2" 118 | + " for labels . It has a value of <1>"); 119 | } 120 | 121 | @Test 122 | public void testUnexpectedValue_failure() { 123 | AssertionError e = 124 | assertThrows( 125 | AssertionError.class, 126 | () -> 127 | assertThat(metric) 128 | .hasValueForLabels(1, "Domestic", "Green") 129 | .and() 130 | .hasNoOtherValues()); 131 | assertThat(e) 132 | .hasMessageThat() 133 | .isEqualTo( 134 | "Not true that has ." 135 | + " It has labeled values <[Bighorn:Blue => 2, Domestic:Green => 1]>"); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /metrics/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.google.monitoring-client 8 | monitoring 9 | 1.0.8-SNAPSHOT 10 | .. 11 | 12 | 13 | metrics 14 | Main Metrics Library 15 | The main library that provides a java idiomatic interface to record metrics. 16 | 17 | 18 | jar 19 | 20 | 21 | 22 | 23 | 24 | com.google.guava 25 | guava 26 | 30.0-jre 27 | 28 | 29 | 30 | com.google.auto.value 31 | auto-value 32 | 1.5.3 33 | provided 34 | 35 | 36 | 37 | com.google.errorprone 38 | error_prone_annotations 39 | 2.1.3 40 | 41 | 42 | 43 | com.google.re2j 44 | re2j 45 | 1.1 46 | 47 | 48 | 49 | 50 | 51 | junit 52 | junit 53 | 4.13.1 54 | test 55 | 56 | 57 | 58 | org.mockito 59 | mockito-all 60 | 2.0.2-beta 61 | test 62 | 63 | 64 | 65 | com.google.truth 66 | truth 67 | 0.44 68 | test 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/AbstractMetric.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import com.google.common.base.MoreObjects; 18 | import com.google.common.collect.ImmutableSet; 19 | import com.google.monitoring.metrics.MetricSchema.Kind; 20 | 21 | abstract class AbstractMetric implements Metric { 22 | 23 | private final Class valueClass; 24 | private final MetricSchema metricSchema; 25 | 26 | AbstractMetric( 27 | String name, 28 | String description, 29 | String valueDisplayName, 30 | Kind kind, 31 | ImmutableSet labels, 32 | Class valueClass) { 33 | this.metricSchema = MetricSchema.create(name, description, valueDisplayName, kind, labels); 34 | this.valueClass = valueClass; 35 | } 36 | 37 | /** Returns the schema of this metric. */ 38 | @Override 39 | public final MetricSchema getMetricSchema() { 40 | return metricSchema; 41 | } 42 | 43 | /** 44 | * Returns the type for the value of this metric, which would otherwise be erased at compile-time. 45 | * This is useful for implementors of {@link MetricWriter}. 46 | */ 47 | @Override 48 | public final Class getValueClass() { 49 | return valueClass; 50 | } 51 | 52 | @Override 53 | public final String toString() { 54 | return MoreObjects.toStringHelper(this) 55 | .add("valueClass", valueClass) 56 | .add("schema", metricSchema) 57 | .toString(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/Counter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | import static com.google.monitoring.metrics.MetricsUtils.DEFAULT_CONCURRENCY_LEVEL; 19 | import static com.google.monitoring.metrics.MetricsUtils.newConcurrentHashMap; 20 | 21 | import com.google.common.annotations.VisibleForTesting; 22 | import com.google.common.collect.ImmutableList; 23 | import com.google.common.collect.ImmutableSet; 24 | import com.google.common.collect.Ordering; 25 | import com.google.common.util.concurrent.AtomicLongMap; 26 | import com.google.common.util.concurrent.Striped; 27 | import com.google.monitoring.metrics.MetricSchema.Kind; 28 | import java.time.Instant; 29 | import java.util.Map.Entry; 30 | import java.util.concurrent.ConcurrentHashMap; 31 | import java.util.concurrent.locks.Lock; 32 | import javax.annotation.concurrent.ThreadSafe; 33 | 34 | /** 35 | * A metric which stores Long values. It is stateful and can be changed in increments. 36 | * 37 | *

This metric is generally suitable for counters, such as requests served or errors generated. 38 | * 39 | *

The start of the {@link MetricPoint#interval()} of values of instances of this metric will be 40 | * set to the time that the metric was first set or last {@link #reset()}. 41 | */ 42 | @ThreadSafe 43 | public final class Counter extends AbstractMetric 44 | implements SettableMetric, IncrementableMetric { 45 | 46 | /** 47 | * A map of the {@link Counter} values, with a list of label values as the keys. 48 | * 49 | *

This should be modified in a critical section with {@code valueStartTimestamps} so that the 50 | * values are in sync. 51 | */ 52 | private final AtomicLongMap> values = AtomicLongMap.create(); 53 | 54 | /** 55 | * A map of the {@link Instant} that each value was created, with a list of label values as the 56 | * keys. The start timestamp (as part of the {@link MetricPoint#interval()} can be used by 57 | * implementations of {@link MetricWriter} to encode resets of monotonic counters. 58 | */ 59 | private final ConcurrentHashMap, Instant> valueStartTimestamps = 60 | newConcurrentHashMap(DEFAULT_CONCURRENCY_LEVEL); 61 | 62 | /** 63 | * A fine-grained lock to ensure that {@code values} and {@code valueStartTimestamps} are modified 64 | * and read in a critical section. The initialization parameter is the concurrency level, set to 65 | * match the default concurrency level of {@link ConcurrentHashMap}. 66 | * 67 | * @see Striped 68 | */ 69 | private final Striped valueLocks = Striped.lock(DEFAULT_CONCURRENCY_LEVEL); 70 | 71 | /** 72 | * Constructs a new Counter. 73 | * 74 | *

Note that the order of the labels is significant. 75 | */ 76 | Counter( 77 | String name, 78 | String description, 79 | String valueDisplayName, 80 | ImmutableSet labels) { 81 | super(name, description, valueDisplayName, Kind.CUMULATIVE, labels, Long.class); 82 | } 83 | 84 | @VisibleForTesting 85 | void incrementBy(long offset, Instant startTimestamp, ImmutableList labelValues) { 86 | Lock lock = valueLocks.get(labelValues); 87 | lock.lock(); 88 | 89 | try { 90 | values.addAndGet(labelValues, offset); 91 | valueStartTimestamps.putIfAbsent(labelValues, startTimestamp); 92 | } finally { 93 | lock.unlock(); 94 | } 95 | } 96 | 97 | @Override 98 | public final void incrementBy(long offset, String... labelValues) { 99 | MetricsUtils.checkLabelValuesLength(this, labelValues); 100 | checkArgument(offset >= 0, "The offset provided must be non-negative"); 101 | 102 | incrementBy(offset, Instant.now(), ImmutableList.copyOf(labelValues)); 103 | } 104 | 105 | @Override 106 | public final void increment(String... labelValues) { 107 | MetricsUtils.checkLabelValuesLength(this, labelValues); 108 | 109 | incrementBy(1L, Instant.now(), ImmutableList.copyOf(labelValues)); 110 | } 111 | 112 | /** 113 | * Returns a snapshot of the metric's values. The timestamp of each {@link MetricPoint} will be 114 | * the last modification time for that tuple of label values. 115 | */ 116 | @Override 117 | public final ImmutableList> getTimestampedValues() { 118 | return getTimestampedValues(Instant.now()); 119 | } 120 | 121 | @Override 122 | public final int getCardinality() { 123 | return values.size(); 124 | } 125 | 126 | @VisibleForTesting 127 | final ImmutableList> getTimestampedValues(Instant endTimestamp) { 128 | ImmutableList.Builder> timestampedValues = new ImmutableList.Builder<>(); 129 | for (Entry, Long> entry : values.asMap().entrySet()) { 130 | ImmutableList labelValues = entry.getKey(); 131 | valueLocks.get(labelValues).lock(); 132 | 133 | Instant startTimestamp; 134 | try { 135 | startTimestamp = valueStartTimestamps.get(labelValues); 136 | } finally { 137 | valueLocks.get(labelValues).unlock(); 138 | } 139 | 140 | // There is an opportunity for endTimestamp to be less than startTimestamp if 141 | // one of the modification methods is called on a value before the lock for that value is 142 | // acquired but after getTimestampedValues has been invoked. Just set endTimestamp equal to 143 | // startTimestamp if that happens. 144 | endTimestamp = Ordering.natural().max(startTimestamp, endTimestamp); 145 | 146 | timestampedValues.add( 147 | MetricPoint.create(this, labelValues, startTimestamp, endTimestamp, entry.getValue())); 148 | } 149 | return timestampedValues.build(); 150 | } 151 | 152 | @VisibleForTesting 153 | final void set(Long value, Instant startTimestamp, ImmutableList labelValues) { 154 | Lock lock = valueLocks.get(labelValues); 155 | lock.lock(); 156 | 157 | try { 158 | this.values.put(labelValues, value); 159 | valueStartTimestamps.putIfAbsent(labelValues, startTimestamp); 160 | } finally { 161 | lock.unlock(); 162 | } 163 | } 164 | 165 | @Override 166 | public final void set(Long value, String... labelValues) { 167 | MetricsUtils.checkLabelValuesLength(this, labelValues); 168 | 169 | set(value, Instant.now(), ImmutableList.copyOf(labelValues)); 170 | } 171 | 172 | @VisibleForTesting 173 | final void reset(Instant startTimestamp) { 174 | // Lock the entire set of values so that all existing values will have a consistent timestamp 175 | // after this call, without the possibility of interleaving with another reset() call. 176 | for (int i = 0; i < valueLocks.size(); i++) { 177 | valueLocks.getAt(i).lock(); 178 | } 179 | 180 | try { 181 | for (ImmutableList labelValues : values.asMap().keySet()) { 182 | this.values.put(labelValues, 0); 183 | this.valueStartTimestamps.put(labelValues, startTimestamp); 184 | } 185 | } finally { 186 | for (int i = 0; i < valueLocks.size(); i++) { 187 | valueLocks.getAt(i).unlock(); 188 | } 189 | } 190 | } 191 | 192 | @Override 193 | public final void reset() { 194 | reset(Instant.now()); 195 | } 196 | 197 | @VisibleForTesting 198 | final void reset(Instant startTimestamp, ImmutableList labelValues) { 199 | Lock lock = valueLocks.get(labelValues); 200 | lock.lock(); 201 | 202 | try { 203 | this.values.put(labelValues, 0); 204 | this.valueStartTimestamps.put(labelValues, startTimestamp); 205 | } finally { 206 | lock.unlock(); 207 | } 208 | } 209 | 210 | @Override 211 | public final void reset(String... labelValues) { 212 | MetricsUtils.checkLabelValuesLength(this, labelValues); 213 | 214 | reset(Instant.now(), ImmutableList.copyOf(labelValues)); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/CustomFitter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | import static com.google.monitoring.metrics.MetricsUtils.checkDouble; 19 | 20 | import com.google.auto.value.AutoValue; 21 | import com.google.common.collect.ImmutableSet; 22 | import com.google.common.collect.ImmutableSortedSet; 23 | import com.google.common.collect.Ordering; 24 | 25 | /** 26 | * Models a {@link DistributionFitter} with arbitrary sized intervals. 27 | * 28 | *

If only only one boundary is provided, then the fitter will consist of an overflow and 29 | * underflow interval separated by that boundary. 30 | */ 31 | @AutoValue 32 | public abstract class CustomFitter implements DistributionFitter { 33 | 34 | /** 35 | * Create a new {@link CustomFitter} with the given interval boundaries. 36 | * 37 | * @param boundaries is a sorted list of interval boundaries 38 | * @throws IllegalArgumentException if {@code boundaries} is empty or not sorted in ascending 39 | * order, or if a value in the set is infinite, {@code NaN}, or {@code -0.0}. 40 | */ 41 | public static CustomFitter create(ImmutableSet boundaries) { 42 | checkArgument(boundaries.size() > 0, "boundaries must not be empty"); 43 | checkArgument(Ordering.natural().isOrdered(boundaries), "boundaries must be sorted"); 44 | for (Double d : boundaries) { 45 | checkDouble(d); 46 | } 47 | 48 | return new AutoValue_CustomFitter(ImmutableSortedSet.copyOf(boundaries)); 49 | } 50 | 51 | @Override 52 | public abstract ImmutableSortedSet boundaries(); 53 | } 54 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/Distribution.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import com.google.common.collect.ImmutableRangeMap; 18 | 19 | /** 20 | * Models a distribution of double-precision floating point sample data, and provides summary 21 | * statistics of the distribution. This class also models the probability density function (PDF) of 22 | * the distribution with a histogram. 23 | * 24 | *

The summary statistics provided are the mean and sumOfSquaredDeviation of the distribution. 25 | * 26 | *

The histogram fitting function is provided via a {@link DistributionFitter} implementation. 27 | * 28 | * @see DistributionFitter 29 | */ 30 | public interface Distribution { 31 | 32 | /** Returns the mean of this distribution. */ 33 | double mean(); 34 | 35 | /** Returns the sum of squared deviations from the mean of this distribution. */ 36 | double sumOfSquaredDeviation(); 37 | 38 | /** Returns the count of samples in this distribution. */ 39 | long count(); 40 | 41 | /** Returns a histogram of the distribution's values. */ 42 | ImmutableRangeMap intervalCounts(); 43 | 44 | /** Returns the {@link DistributionFitter} of this distribution. */ 45 | DistributionFitter distributionFitter(); 46 | } 47 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/DistributionFitter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import com.google.common.collect.ImmutableSortedSet; 18 | 19 | /** 20 | * A companion interface to {@link Distribution} which fits samples to a histogram in order to 21 | * estimate the probability density function (PDF) of the {@link Distribution}. 22 | * 23 | *

The fitter models the histogram with a set of finite boundaries. The closed-open interval 24 | * [a,b) between two consecutive boundaries represents the domain of permissible values in that 25 | * interval. The values less than the first boundary are in the underflow interval of (-inf, a) and 26 | * values greater or equal to the last boundary in the array are in the overflow interval of [a, 27 | * inf). 28 | */ 29 | public interface DistributionFitter { 30 | 31 | /** Returns a sorted set of the boundaries modeled by this {@link DistributionFitter}. */ 32 | ImmutableSortedSet boundaries(); 33 | } 34 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/EventMetric.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.monitoring.metrics.MetricsUtils.DEFAULT_CONCURRENCY_LEVEL; 18 | import static com.google.monitoring.metrics.MetricsUtils.newConcurrentHashMap; 19 | 20 | import com.google.common.annotations.VisibleForTesting; 21 | import com.google.common.collect.ImmutableList; 22 | import com.google.common.collect.ImmutableSet; 23 | import com.google.common.collect.Ordering; 24 | import com.google.common.util.concurrent.Striped; 25 | import com.google.monitoring.metrics.MetricSchema.Kind; 26 | import java.time.Instant; 27 | import java.util.Map.Entry; 28 | import java.util.concurrent.ConcurrentHashMap; 29 | import java.util.concurrent.locks.Lock; 30 | 31 | /** 32 | * A metric which stores {@link Distribution} values. The values are stateful and meant to be 33 | * updated incrementally via the {@link EventMetric#record(double, String...)} method. 34 | * 35 | *

This metric class is generally suitable for recording aggregations of data about a 36 | * quantitative aspect of an event. For example, this metric would be suitable for recording the 37 | * latency distribution for a request over the network. 38 | * 39 | *

The {@link MutableDistribution} values tracked by this metric can be reset with {@link 40 | * EventMetric#reset()}. 41 | */ 42 | public class EventMetric extends AbstractMetric { 43 | 44 | /** 45 | * Default {@link DistributionFitter} suitable for latency measurements. 46 | * 47 | *

The finite range of this fitter is from 1 to 4^16 (4294967296). 48 | */ 49 | public static final DistributionFitter DEFAULT_FITTER = ExponentialFitter.create(16, 4.0, 1.0); 50 | 51 | private final ConcurrentHashMap, Instant> valueStartTimestamps = 52 | newConcurrentHashMap(DEFAULT_CONCURRENCY_LEVEL); 53 | private final ConcurrentHashMap, MutableDistribution> values = 54 | newConcurrentHashMap(DEFAULT_CONCURRENCY_LEVEL); 55 | 56 | private final DistributionFitter distributionFitter; 57 | 58 | /** 59 | * A fine-grained lock to ensure that {@code values} and {@code valueStartTimestamps} are modified 60 | * and read in a critical section. The initialization parameter is the concurrency level, set to 61 | * match the default concurrency level of {@link ConcurrentHashMap}. 62 | * 63 | * @see Striped 64 | */ 65 | private final Striped valueLocks = Striped.lock(DEFAULT_CONCURRENCY_LEVEL); 66 | 67 | EventMetric( 68 | String name, 69 | String description, 70 | String valueDisplayName, 71 | DistributionFitter distributionFitter, 72 | ImmutableSet labels) { 73 | super(name, description, valueDisplayName, Kind.CUMULATIVE, labels, Distribution.class); 74 | 75 | this.distributionFitter = distributionFitter; 76 | } 77 | 78 | @Override 79 | public final int getCardinality() { 80 | return values.size(); 81 | } 82 | 83 | @Override 84 | public final ImmutableList> getTimestampedValues() { 85 | return getTimestampedValues(Instant.now()); 86 | } 87 | 88 | @VisibleForTesting 89 | ImmutableList> getTimestampedValues(Instant endTimestamp) { 90 | ImmutableList.Builder> timestampedValues = 91 | new ImmutableList.Builder<>(); 92 | 93 | for (Entry, MutableDistribution> entry : values.entrySet()) { 94 | ImmutableList labelValues = entry.getKey(); 95 | Lock lock = valueLocks.get(labelValues); 96 | lock.lock(); 97 | 98 | Instant startTimestamp; 99 | ImmutableDistribution distribution; 100 | try { 101 | startTimestamp = valueStartTimestamps.get(labelValues); 102 | distribution = ImmutableDistribution.copyOf(entry.getValue()); 103 | } finally { 104 | lock.unlock(); 105 | } 106 | 107 | // There is an opportunity for endTimestamp to be less than startTimestamp if 108 | // one of the modification methods is called on a value before the lock for that value is 109 | // acquired but after getTimestampedValues has been invoked. Just set endTimestamp equal to 110 | // startTimestamp if that happens. 111 | endTimestamp = Ordering.natural().max(startTimestamp, endTimestamp); 112 | 113 | timestampedValues.add( 114 | MetricPoint.create(this, labelValues, startTimestamp, endTimestamp, distribution)); 115 | } 116 | 117 | return timestampedValues.build(); 118 | } 119 | 120 | /** 121 | * Adds the given {@code sample} to the {@link Distribution} for the given {@code labelValues}. 122 | * 123 | *

If the metric is undefined for given label values, this method will autovivify the {@link 124 | * Distribution}. 125 | * 126 | *

The count of {@code labelValues} must be equal to the underlying metric's count of labels. 127 | */ 128 | public void record(double sample, String... labelValues) { 129 | MetricsUtils.checkLabelValuesLength(this, labelValues); 130 | 131 | recordMultiple(sample, 1, Instant.now(), ImmutableList.copyOf(labelValues)); 132 | } 133 | 134 | /** 135 | * Adds {@code count} of the given {@code sample} to the {@link Distribution} for the given {@code 136 | * labelValues}. 137 | * 138 | *

If the metric is undefined for given label values, this method will autovivify the {@link 139 | * Distribution}. 140 | * 141 | *

The count of {@code labelValues} must be equal to the underlying metric's count of labels. 142 | */ 143 | public void record(double sample, int count, String... labelValues) { 144 | MetricsUtils.checkLabelValuesLength(this, labelValues); 145 | 146 | recordMultiple(sample, count, Instant.now(), ImmutableList.copyOf(labelValues)); 147 | } 148 | 149 | @VisibleForTesting 150 | void recordMultiple( 151 | double sample, int count, Instant startTimestamp, ImmutableList labelValues) { 152 | Lock lock = valueLocks.get(labelValues); 153 | lock.lock(); 154 | 155 | try { 156 | values.computeIfAbsent(labelValues, k -> new MutableDistribution(distributionFitter)); 157 | 158 | values.get(labelValues).add(sample, count); 159 | valueStartTimestamps.putIfAbsent(labelValues, startTimestamp); 160 | } finally { 161 | lock.unlock(); 162 | } 163 | } 164 | 165 | /** 166 | * Atomically resets the value and start timestamp of the metric for all label values. 167 | * 168 | *

This is useful if the metric is tracking values that are reset as part of a retrying 169 | * transaction, for example. 170 | */ 171 | public void reset() { 172 | reset(Instant.now()); 173 | } 174 | 175 | @VisibleForTesting 176 | final void reset(Instant startTime) { 177 | // Lock the entire set of values so that all existing values will have a consistent timestamp 178 | // after this call, without the possibility of interleaving with another reset() call. 179 | for (int i = 0; i < valueLocks.size(); i++) { 180 | valueLocks.getAt(i).lock(); 181 | } 182 | 183 | try { 184 | for (ImmutableList labelValues : values.keySet()) { 185 | this.values.put(labelValues, new MutableDistribution(distributionFitter)); 186 | this.valueStartTimestamps.put(labelValues, startTime); 187 | } 188 | } finally { 189 | for (int i = 0; i < valueLocks.size(); i++) { 190 | valueLocks.getAt(i).unlock(); 191 | } 192 | } 193 | } 194 | 195 | /** 196 | * Resets the value and start timestamp of the metric for the given label values. 197 | * 198 | *

This is useful if the metric is tracking a value that is reset as part of a retrying 199 | * transaction, for example. 200 | */ 201 | public void reset(String... labelValues) { 202 | MetricsUtils.checkLabelValuesLength(this, labelValues); 203 | 204 | reset(Instant.now(), ImmutableList.copyOf(labelValues)); 205 | } 206 | 207 | @VisibleForTesting 208 | final void reset(Instant startTimestamp, ImmutableList labelValues) { 209 | Lock lock = valueLocks.get(labelValues); 210 | lock.lock(); 211 | 212 | try { 213 | this.values.put(labelValues, new MutableDistribution(distributionFitter)); 214 | this.valueStartTimestamps.put(labelValues, startTimestamp); 215 | } finally { 216 | lock.unlock(); 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/ExponentialFitter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | import static com.google.monitoring.metrics.MetricsUtils.checkDouble; 19 | 20 | import com.google.auto.value.AutoValue; 21 | import com.google.common.collect.ImmutableSortedSet; 22 | 23 | /** 24 | * Models a {@link DistributionFitter} with intervals of exponentially increasing size. 25 | * 26 | *

The interval boundaries are defined by {@code scale * Math.pow(base, i)} for {@code i} in 27 | * {@code [0, numFiniteIntervals]}. 28 | * 29 | *

For example, an {@link ExponentialFitter} with {@code numFiniteIntervals=3, base=4.0, 30 | * scale=1.5} represents a histogram with intervals {@code (-inf, 1.5), [1.5, 6), [6, 24), [24, 96), 31 | * [96, +inf)}. 32 | */ 33 | @AutoValue 34 | public abstract class ExponentialFitter implements DistributionFitter { 35 | 36 | /** 37 | * Create a new {@link ExponentialFitter}. 38 | * 39 | * @param numFiniteIntervals the number of intervals, excluding the underflow and overflow 40 | * intervals 41 | * @param base the base of the exponent 42 | * @param scale a multiplicative factor for the exponential function 43 | * @throws IllegalArgumentException if {@code numFiniteIntervals <= 0}, {@code width <= 0} or 44 | * {@code base <= 1} 45 | */ 46 | public static ExponentialFitter create(int numFiniteIntervals, double base, double scale) { 47 | checkArgument(numFiniteIntervals > 0, "numFiniteIntervals must be greater than 0"); 48 | checkArgument(scale != 0, "scale must not be 0"); 49 | checkArgument(base > 1, "base must be greater than 1"); 50 | checkDouble(base); 51 | checkDouble(scale); 52 | 53 | ImmutableSortedSet.Builder boundaries = ImmutableSortedSet.naturalOrder(); 54 | 55 | for (int i = 0; i < numFiniteIntervals + 1; i++) { 56 | boundaries.add(scale * Math.pow(base, i)); 57 | } 58 | 59 | return new AutoValue_ExponentialFitter(base, scale, boundaries.build()); 60 | } 61 | 62 | public abstract double base(); 63 | 64 | public abstract double scale(); 65 | 66 | @Override 67 | public abstract ImmutableSortedSet boundaries(); 68 | } 69 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/FibonacciFitter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | 19 | import com.google.common.collect.ImmutableSortedSet; 20 | 21 | /** 22 | * Utility method to create a {@link CustomFitter} with intervals using the Fibonacci sequence. 23 | * 24 | *

A Fibonacci fitter is useful in situations where you want more precision on the low end than 25 | * an {@link ExponentialFitter} with exponent base 2 would provide without the hassle of dealing 26 | * with non-integer boundaries, such as would be created by an exponential fitter with a base of 27 | * less than 2. Fibonacci fitters are ideal for integer metrics that are bounded across a certain 28 | * range, e.g. integers between 1 and 1,000. 29 | * 30 | *

The interval boundaries are chosen as {@code (-inf, 0), [0, 1), [1, 2), [2, 3), [3, 5), [5, 31 | * 8), [8, 13)}, etc., up to {@code [fibonacciFloor(maxBucketSize), inf)}. 32 | */ 33 | public final class FibonacciFitter { 34 | 35 | /** 36 | * Returns a new {@link CustomFitter} with bounds corresponding to the Fibonacci sequence. 37 | * 38 | * @param maxBucketSize the maximum bucket size to create (rounded down to the nearest Fibonacci 39 | * number) 40 | * @throws IllegalArgumentException if {@code maxBucketSize <= 0} 41 | */ 42 | public static CustomFitter create(long maxBucketSize) { 43 | checkArgument(maxBucketSize > 0, "maxBucketSize must be greater than 0"); 44 | 45 | ImmutableSortedSet.Builder boundaries = ImmutableSortedSet.naturalOrder(); 46 | boundaries.add(Double.valueOf(0)); 47 | long i = 1; 48 | long j = 2; 49 | long k = 3; 50 | while (i <= maxBucketSize) { 51 | boundaries.add(Double.valueOf(i)); 52 | i = j; 53 | j = k; 54 | k = i + j; 55 | } 56 | 57 | return CustomFitter.create(boundaries.build()); 58 | } 59 | 60 | private FibonacciFitter() {} 61 | } 62 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/ImmutableDistribution.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | import static com.google.monitoring.metrics.MetricsUtils.checkDouble; 19 | 20 | import com.google.auto.value.AutoValue; 21 | import com.google.common.annotations.VisibleForTesting; 22 | import com.google.common.collect.ImmutableRangeMap; 23 | import javax.annotation.concurrent.ThreadSafe; 24 | 25 | /** 26 | * An immutable {@link Distribution}. Instances of this class can used to create {@link MetricPoint} 27 | * instances, and should be used when exporting values to a {@link MetricWriter}. 28 | * 29 | * @see MutableDistribution 30 | */ 31 | @ThreadSafe 32 | @AutoValue 33 | public abstract class ImmutableDistribution implements Distribution { 34 | 35 | public static ImmutableDistribution copyOf(Distribution distribution) { 36 | return new AutoValue_ImmutableDistribution( 37 | distribution.mean(), 38 | distribution.sumOfSquaredDeviation(), 39 | distribution.count(), 40 | distribution.intervalCounts(), 41 | distribution.distributionFitter()); 42 | } 43 | 44 | @VisibleForTesting 45 | static ImmutableDistribution create( 46 | double mean, 47 | double sumOfSquaredDeviation, 48 | long count, 49 | ImmutableRangeMap intervalCounts, 50 | DistributionFitter distributionFitter) { 51 | checkDouble(mean); 52 | checkDouble(sumOfSquaredDeviation); 53 | checkArgument(count >= 0); 54 | 55 | return new AutoValue_ImmutableDistribution( 56 | mean, sumOfSquaredDeviation, count, intervalCounts, distributionFitter); 57 | } 58 | 59 | @Override 60 | public abstract double mean(); 61 | 62 | @Override 63 | public abstract double sumOfSquaredDeviation(); 64 | 65 | @Override 66 | public abstract long count(); 67 | 68 | @Override 69 | public abstract ImmutableRangeMap intervalCounts(); 70 | 71 | @Override 72 | public abstract DistributionFitter distributionFitter(); 73 | } 74 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/IncrementableMetric.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | /** 18 | * A {@link Metric} which can be incremented. 19 | * 20 | *

This is a view into a {@link Counter} to provide compile-time checking to disallow arbitrarily 21 | * setting the metric, which is useful for metrics which should be monotonically increasing. 22 | */ 23 | public interface IncrementableMetric extends Metric { 24 | 25 | /** 26 | * Increments a metric by 1 for the given label values. 27 | * 28 | *

Use this method rather than {@link IncrementableMetric#incrementBy(long, String...)} if the 29 | * increment value is 1, as it will be slightly more performant. 30 | * 31 | *

If the metric is undefined for given label values, it will be incremented from zero. 32 | * 33 | *

The metric's timestamp will be updated to the current time for the given label values. 34 | * 35 | *

The count of {@code labelValues} must be equal to the underlying metric's count of labels. 36 | */ 37 | void increment(String... labelValues); 38 | 39 | /** 40 | * Increments a metric by the given non-negative offset for the given label values. 41 | * 42 | *

If the metric is undefined for given label values, it will be incremented from zero. 43 | * 44 | *

The metric's timestamp will be updated to the current time for the given label values. 45 | * 46 | *

The count of {@code labelValues} must be equal to the underlying metric's count of labels. 47 | * 48 | * @throws IllegalArgumentException if the offset is negative. 49 | */ 50 | void incrementBy(long offset, String... labelValues); 51 | 52 | /** 53 | * Resets the value and start timestamp of the metric for the given label values. 54 | * 55 | *

This is useful if the counter is tracking a value that is reset as part of a retrying 56 | * transaction, for example. 57 | */ 58 | void reset(String... labelValues); 59 | 60 | /** 61 | * Atomically resets the value and start timestamp of the metric for all label values. 62 | * 63 | *

This is useful if the counter is tracking values that are reset as part of a retrying 64 | * transaction, for example. 65 | */ 66 | void reset(); 67 | } 68 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/LabelDescriptor.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | 19 | import com.google.auto.value.AutoValue; 20 | import com.google.re2j.Pattern; 21 | 22 | /** 23 | * Definition of a metric label. 24 | * 25 | *

If a metric is created with labels, corresponding label values must be provided when setting 26 | * values on the metric. 27 | */ 28 | @AutoValue 29 | public abstract class LabelDescriptor { 30 | 31 | private static final Pattern ALLOWED_LABEL_PATTERN = Pattern.compile("\\w+"); 32 | 33 | LabelDescriptor() {} 34 | 35 | /** 36 | * Returns a new {@link LabelDescriptor}. 37 | * 38 | * @param name identifier for label 39 | * @param description human-readable description of label 40 | * @throws IllegalArgumentException if {@code name} isn't {@code \w+} or {@code description} is 41 | * blank 42 | */ 43 | public static LabelDescriptor create(String name, String description) { 44 | checkArgument(!name.isEmpty(), "Name must not be empty"); 45 | checkArgument(!description.isEmpty(), "Description must not be empty"); 46 | checkArgument( 47 | ALLOWED_LABEL_PATTERN.matches(name), 48 | "Label name must match the regex %s", 49 | ALLOWED_LABEL_PATTERN); 50 | 51 | return new AutoValue_LabelDescriptor(name, description); 52 | } 53 | 54 | public abstract String name(); 55 | 56 | public abstract String description(); 57 | } 58 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/LinearFitter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | import static com.google.monitoring.metrics.MetricsUtils.checkDouble; 19 | 20 | import com.google.auto.value.AutoValue; 21 | import com.google.common.collect.ImmutableSortedSet; 22 | 23 | /** 24 | * Models a {@link DistributionFitter} with equally sized intervals. 25 | * 26 | *

The interval boundaries are defined by {@code width * i + offset} for {@code i} in {@code [0, 27 | * numFiniteIntervals}. 28 | * 29 | *

For example, a {@link LinearFitter} with {@code numFiniteIntervals=2, width=10, offset=5} 30 | * represents a histogram with intervals {@code (-inf, 5), [5, 15), [15, 25), [25, +inf)}. 31 | */ 32 | @AutoValue 33 | public abstract class LinearFitter implements DistributionFitter { 34 | 35 | /** 36 | * Create a new {@link LinearFitter}. 37 | * 38 | * @param numFiniteIntervals the number of intervals, excluding the underflow and overflow 39 | * intervals 40 | * @param width the width of each interval 41 | * @param offset the start value of the first interval 42 | * @throws IllegalArgumentException if {@code numFiniteIntervals <= 0} or {@code width <= 0} 43 | */ 44 | public static LinearFitter create(int numFiniteIntervals, double width, double offset) { 45 | checkArgument(numFiniteIntervals > 0, "numFiniteIntervals must be greater than 0"); 46 | checkArgument(width > 0, "width must be greater than 0"); 47 | checkDouble(offset); 48 | 49 | ImmutableSortedSet.Builder boundaries = ImmutableSortedSet.naturalOrder(); 50 | 51 | for (int i = 0; i < numFiniteIntervals + 1; i++) { 52 | boundaries.add(width * i + offset); 53 | } 54 | 55 | return new AutoValue_LinearFitter(width, offset, boundaries.build()); 56 | } 57 | 58 | public abstract double width(); 59 | 60 | public abstract double offset(); 61 | 62 | @Override 63 | public abstract ImmutableSortedSet boundaries(); 64 | } 65 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/Metric.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import com.google.common.collect.ImmutableList; 18 | 19 | /** 20 | * A Metric which stores timestamped values. 21 | * 22 | *

This is a read-only view. 23 | */ 24 | public interface Metric { 25 | 26 | /** 27 | * Returns the latest {@link MetricPoint} instances for every label-value combination tracked for 28 | * this metric. 29 | */ 30 | ImmutableList> getTimestampedValues(); 31 | 32 | /** Returns the count of values being stored with this metric. */ 33 | int getCardinality(); 34 | 35 | /** Returns the schema of this metric. */ 36 | MetricSchema getMetricSchema(); 37 | 38 | /** 39 | * Returns the type for the value of this metric, which would otherwise be erased at compile-time. 40 | * This is useful for implementors of {@link MetricWriter}. 41 | */ 42 | Class getValueClass(); 43 | } 44 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/MetricExporter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.util.concurrent.MoreExecutors.directExecutor; 18 | 19 | import com.google.common.collect.ImmutableList; 20 | import com.google.common.util.concurrent.AbstractExecutionThreadService; 21 | import java.io.IOException; 22 | import java.util.Optional; 23 | import java.util.concurrent.BlockingQueue; 24 | import java.util.concurrent.Executor; 25 | import java.util.concurrent.ExecutorService; 26 | import java.util.concurrent.Executors; 27 | import java.util.concurrent.ThreadFactory; 28 | import java.util.logging.Level; 29 | import java.util.logging.Logger; 30 | 31 | /** 32 | * Background service to asynchronously push bundles of {@link MetricPoint} instances to a {@link 33 | * MetricWriter}. 34 | */ 35 | class MetricExporter extends AbstractExecutionThreadService { 36 | 37 | private static final Logger logger = Logger.getLogger(MetricExporter.class.getName()); 38 | 39 | private final BlockingQueue>>> writeQueue; 40 | private final MetricWriter writer; 41 | private final ThreadFactory threadFactory; 42 | 43 | MetricExporter( 44 | BlockingQueue>>> writeQueue, 45 | MetricWriter writer, 46 | ThreadFactory threadFactory) { 47 | this.writeQueue = writeQueue; 48 | this.writer = writer; 49 | this.threadFactory = threadFactory; 50 | } 51 | 52 | @Override 53 | protected void run() throws Exception { 54 | logger.info("Started up MetricExporter"); 55 | while (isRunning()) { 56 | Optional>> batch = writeQueue.take(); 57 | logger.fine("Got a batch of points from the writeQueue"); 58 | if (batch.isPresent()) { 59 | logger.fine("Batch contains data, writing to MetricWriter"); 60 | try { 61 | for (MetricPoint point : batch.get()) { 62 | writer.write(point); 63 | } 64 | writer.flush(); 65 | } catch (IOException exception) { 66 | logger.log( 67 | Level.WARNING, "Threw an exception while writing or flushing metrics", exception); 68 | } 69 | } else { 70 | logger.info("Received a poison pill, stopping now"); 71 | // An absent optional indicates that the Reporter wants this service to shut down. 72 | return; 73 | } 74 | } 75 | } 76 | 77 | @Override 78 | protected Executor executor() { 79 | final ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory); 80 | // Make sure the ExecutorService terminates when this service does. 81 | addListener( 82 | new Listener() { 83 | @Override 84 | public void terminated(State from) { 85 | executor.shutdown(); 86 | } 87 | 88 | @Override 89 | public void failed(State from, Throwable failure) { 90 | executor.shutdown(); 91 | } 92 | }, 93 | directExecutor()); 94 | return executor; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/MetricMetrics.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import com.google.common.collect.ImmutableList; 18 | import com.google.common.collect.ImmutableMap; 19 | import com.google.common.collect.ImmutableSet; 20 | import java.util.HashMap; 21 | 22 | /** Static store of metrics internal to this client library. */ 23 | final class MetricMetrics { 24 | 25 | /** A counter representing the total number of push intervals since the start of the process. */ 26 | static final IncrementableMetric pushIntervals = 27 | MetricRegistryImpl.getDefault() 28 | .newIncrementableMetric( 29 | "/metrics/push_intervals", 30 | "Count of push intervals.", 31 | "Push Intervals", 32 | ImmutableSet.of()); 33 | private static final ImmutableSet LABELS = 34 | ImmutableSet.of( 35 | LabelDescriptor.create("kind", "Metric Kind"), 36 | LabelDescriptor.create("valueType", "Metric Value Type")); 37 | 38 | /** 39 | * A counter representing the total number of points pushed. Has {@link MetricSchema.Kind} and 40 | * Metric value classes as LABELS. 41 | */ 42 | static final IncrementableMetric pushedPoints = 43 | MetricRegistryImpl.getDefault() 44 | .newIncrementableMetric( 45 | "/metrics/points_pushed", 46 | "Count of points pushed to Monitoring API.", 47 | "Points Pushed", 48 | LABELS); 49 | 50 | /** A gauge representing a snapshot of the number of active timeseries being reported. */ 51 | @SuppressWarnings("unused") 52 | private static final Metric timeseriesCount = 53 | MetricRegistryImpl.getDefault() 54 | .newGauge( 55 | "/metrics/timeseries_count", 56 | "Count of Timeseries being pushed to Monitoring API", 57 | "Timeseries Count", 58 | LABELS, 59 | () -> { 60 | HashMap, Long> timeseriesCount = new HashMap<>(); 61 | 62 | for (Metric metric : MetricRegistryImpl.getDefault().getRegisteredMetrics()) { 63 | ImmutableList key = 64 | ImmutableList.of( 65 | metric.getMetricSchema().kind().toString(), 66 | metric.getValueClass().toString()); 67 | 68 | int cardinality = metric.getCardinality(); 69 | if (!timeseriesCount.containsKey(key)) { 70 | timeseriesCount.put(key, (long) cardinality); 71 | } else { 72 | timeseriesCount.put(key, timeseriesCount.get(key) + cardinality); 73 | } 74 | } 75 | 76 | return ImmutableMap.copyOf(timeseriesCount); 77 | }, 78 | Long.class); 79 | 80 | private MetricMetrics() {} 81 | } 82 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/MetricPoint.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import com.google.auto.value.AutoValue; 18 | import com.google.common.annotations.VisibleForTesting; 19 | import com.google.common.collect.ImmutableList; 20 | import com.google.common.collect.Range; 21 | import java.time.Instant; 22 | 23 | /** 24 | * Value type class to store a snapshot of a {@link Metric} value for a given label value tuple and 25 | * time {@link Range}. 26 | */ 27 | @AutoValue 28 | public abstract class MetricPoint implements Comparable> { 29 | 30 | /** 31 | * Returns a new {@link MetricPoint} representing a value at a specific {@link Instant}. 32 | * 33 | *

Callers should insure that the count of {@code labelValues} matches the count of labels for 34 | * the given metric. 35 | */ 36 | @VisibleForTesting 37 | public static MetricPoint create( 38 | Metric metric, ImmutableList labelValues, Instant timestamp, V value) { 39 | MetricsUtils.checkLabelValuesLength(metric, labelValues); 40 | 41 | return new AutoValue_MetricPoint<>( 42 | metric, labelValues, Range.openClosed(timestamp, timestamp), value); 43 | } 44 | 45 | /** 46 | * Returns a new {@link MetricPoint} representing a value over an {@link Range} from {@code 47 | * startTime} to {@code endTime}. 48 | * 49 | *

Callers should insure that the count of {@code labelValues} matches the count of labels for 50 | * the given metric. 51 | */ 52 | @VisibleForTesting 53 | public static MetricPoint create( 54 | Metric metric, 55 | ImmutableList labelValues, 56 | Instant startTime, 57 | Instant endTime, 58 | V value) { 59 | MetricsUtils.checkLabelValuesLength(metric, labelValues); 60 | 61 | return new AutoValue_MetricPoint<>( 62 | metric, labelValues, Range.openClosed(startTime, endTime), value); 63 | } 64 | 65 | public abstract Metric metric(); 66 | 67 | public abstract ImmutableList labelValues(); 68 | 69 | public abstract Range interval(); 70 | 71 | public abstract V value(); 72 | 73 | @Override 74 | public int compareTo(MetricPoint other) { 75 | int minLength = Math.min(this.labelValues().size(), other.labelValues().size()); 76 | for (int index = 0; index < minLength; index++) { 77 | int comparisonResult = 78 | this.labelValues().get(index).compareTo(other.labelValues().get(index)); 79 | if (comparisonResult != 0) { 80 | return comparisonResult; 81 | } 82 | } 83 | return Integer.compare(this.labelValues().size(), other.labelValues().size()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/MetricRegistry.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import com.google.common.base.Supplier; 18 | import com.google.common.collect.ImmutableList; 19 | import com.google.common.collect.ImmutableMap; 20 | import com.google.common.collect.ImmutableSet; 21 | 22 | /** An interface to create and keep track of metrics. */ 23 | public interface MetricRegistry { 24 | 25 | /** 26 | * Returns a new Gauge metric. 27 | * 28 | *

The metric's values are computed at sample time via the supplied callback function. The 29 | * metric will be registered at the time of creation and collected for subsequent write intervals. 30 | * 31 | *

Since the metric's values are computed by a pre-defined callback function, this method only 32 | * returns a read-only {@link Metric} view. 33 | * 34 | * @param name name of the metric. Should be in the form of '/foo/bar'. 35 | * @param description human readable description of the metric. Must not be empty. 36 | * @param valueDisplayName human readable description of the metric's value type. Must not be 37 | * empty. 38 | * @param labels list of the metric's labels. The labels (if there are any) must be of type 39 | * STRING. 40 | * @param metricCallback {@link Supplier} to compute the on-demand values of the metric. The 41 | * function should be lightweight to compute and must be thread-safe. The map keys, which are 42 | * lists of strings, must match in quantity and order with the provided labels. 43 | * @param valueClass type hint to allow for compile-time encoding. Must match {@code V}. 44 | * @param value type of the metric. Must be one of {@link Boolean}, {@link Double}, {@link 45 | * Long}, or {@link String}. 46 | * @throws IllegalStateException if a metric of the same name is already registered. 47 | */ 48 | Metric newGauge( 49 | String name, 50 | String description, 51 | String valueDisplayName, 52 | ImmutableSet labels, 53 | Supplier, V>> metricCallback, 54 | Class valueClass); 55 | 56 | /** 57 | * Returns a new {@link SettableMetric}. 58 | * 59 | *

The metric's value is stateful and can be set to different values over time. 60 | * 61 | *

The metric is thread-safe. 62 | * 63 | *

The metric will be registered at the time of creation and collected for subsequent write 64 | * intervals. 65 | * 66 | * @param name name of the metric. Should be in the form of '/foo/bar'. 67 | * @param description human readable description of the metric. 68 | * @param valueDisplayName human readable description of the metric's value type. 69 | * @param labels list of the metric's labels. The labels (if there are any) must be of type 70 | * STRING. 71 | * @param valueClass type hint to allow for compile-time encoding. Must match {@code V}. 72 | * @param value type of the metric. Must be one of {@link Boolean}, {@link Double}, {@link 73 | * Long}, or {@link String}. 74 | * @throws IllegalStateException if a metric of the same name is already registered. 75 | */ 76 | SettableMetric newSettableMetric( 77 | String name, 78 | String description, 79 | String valueDisplayName, 80 | ImmutableSet labels, 81 | Class valueClass); 82 | 83 | /** 84 | * Returns a new {@link IncrementableMetric}. 85 | * 86 | *

The metric's values are {@link Long}, and can be incremented, and decremented. The metric is 87 | * thread-safe. The metric will be registered at the time of creation and collected for subsequent 88 | * write intervals. 89 | * 90 | *

This metric type is generally intended for monotonically increasing values, although the 91 | * metric can in fact be incremented by negative numbers. 92 | * 93 | * @param name name of the metric. Should be in the form of '/foo/bar'. 94 | * @param description human readable description of the metric. 95 | * @param valueDisplayName human readable description of the metric's value type. 96 | * @param labels list of the metric's labels. The labels (if there are any) must be of type 97 | * STRING. 98 | * @throws IllegalStateException if a metric of the same name is already registered. 99 | */ 100 | IncrementableMetric newIncrementableMetric( 101 | String name, 102 | String description, 103 | String valueDisplayName, 104 | ImmutableSet labels); 105 | 106 | /** 107 | * Returns a new {@link EventMetric}. 108 | * 109 | *

This metric type is intended for recording aspects of an "event" -- things like latency or 110 | * payload size. 111 | * 112 | *

The metric's values are {@link Distribution} instances which are updated via {@link 113 | * EventMetric#record(double, String...)}. 114 | * 115 | *

The metric is thread-safe. The metric will be registered at the time of creation and 116 | * collected for subsequent write intervals. 117 | * 118 | * @param name name of the metric. Should be in the form of '/foo/bar'. 119 | * @param description human readable description of the metric. 120 | * @param valueDisplayName human readable description of the metric's value type. 121 | * @param labels list of the metric's labels. 122 | * @param distributionFitter fit to apply to the underlying {@link Distribution} instances of this 123 | * metric. 124 | * @throws IllegalStateException if a metric of the same name is already registered. 125 | */ 126 | EventMetric newEventMetric( 127 | String name, 128 | String description, 129 | String valueDisplayName, 130 | ImmutableSet labels, 131 | DistributionFitter distributionFitter); 132 | 133 | /** 134 | * Fetches a snapshot of the currently registered metrics 135 | * 136 | *

Users who wish to manually sample and write metrics without relying on the scheduled {@link 137 | * MetricReporter} can use this method to gather the list of metrics to report. 138 | */ 139 | ImmutableList> getRegisteredMetrics(); 140 | } 141 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/MetricRegistryImpl.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkState; 18 | 19 | import com.google.common.annotations.VisibleForTesting; 20 | import com.google.common.base.Supplier; 21 | import com.google.common.collect.ImmutableList; 22 | import com.google.common.collect.ImmutableMap; 23 | import com.google.common.collect.ImmutableSet; 24 | import com.google.errorprone.annotations.CanIgnoreReturnValue; 25 | import java.util.concurrent.ConcurrentHashMap; 26 | import java.util.logging.Logger; 27 | import javax.annotation.concurrent.ThreadSafe; 28 | 29 | /** A singleton registry of {@link Metric}s. */ 30 | @ThreadSafe 31 | public final class MetricRegistryImpl implements MetricRegistry { 32 | 33 | private static final Logger logger = Logger.getLogger(MetricRegistryImpl.class.getName()); 34 | private static final MetricRegistryImpl INSTANCE = new MetricRegistryImpl(); 35 | 36 | /** The canonical registry for metrics. The map key is the metric name. */ 37 | private final ConcurrentHashMap> registeredMetrics = new ConcurrentHashMap<>(); 38 | 39 | /** 40 | * Production code must use {@link #getDefault}, since this returns the {@link MetricRegistry} 41 | * that {@link MetricReporter} uses. Test code that does not use {@link MetricReporter} can use 42 | * this constructor to get an isolated instance of the registry. 43 | */ 44 | @VisibleForTesting 45 | public MetricRegistryImpl() {} 46 | 47 | public static MetricRegistryImpl getDefault() { 48 | return INSTANCE; 49 | } 50 | 51 | /** 52 | * Creates a new event metric. 53 | * 54 | *

Note that the order of the labels is significant. 55 | */ 56 | @Override 57 | public EventMetric newEventMetric( 58 | String name, 59 | String description, 60 | String valueDisplayName, 61 | ImmutableSet labels, 62 | DistributionFitter distributionFitter) { 63 | EventMetric metric = 64 | new EventMetric(name, description, valueDisplayName, distributionFitter, labels); 65 | registerMetric(name, metric); 66 | logger.info("Registered new event metric: " + name); 67 | 68 | return metric; 69 | } 70 | 71 | /** 72 | * Creates a new gauge metric. 73 | * 74 | *

Note that the order of the labels is significant. 75 | */ 76 | @Override 77 | @CanIgnoreReturnValue 78 | public Metric newGauge( 79 | String name, 80 | String description, 81 | String valueDisplayName, 82 | ImmutableSet labels, 83 | Supplier, V>> metricCallback, 84 | Class valueClass) { 85 | VirtualMetric metric = 86 | new VirtualMetric<>( 87 | name, description, valueDisplayName, labels, metricCallback, valueClass); 88 | registerMetric(name, metric); 89 | logger.info("Registered new callback metric: " + name); 90 | 91 | return metric; 92 | } 93 | 94 | /** 95 | * Creates a new settable metric. 96 | * 97 | *

Note that the order of the labels is significant. 98 | */ 99 | @Override 100 | public SettableMetric newSettableMetric( 101 | String name, 102 | String description, 103 | String valueDisplayName, 104 | ImmutableSet labels, 105 | Class valueClass) { 106 | StoredMetric metric = 107 | new StoredMetric<>(name, description, valueDisplayName, labels, valueClass); 108 | registerMetric(name, metric); 109 | logger.info("Registered new stored metric: " + name); 110 | 111 | return metric; 112 | } 113 | 114 | /** 115 | * Creates a new incrementable metric. 116 | * 117 | *

Note that the order of the labels is significant. 118 | */ 119 | @Override 120 | public IncrementableMetric newIncrementableMetric( 121 | String name, 122 | String description, 123 | String valueDisplayName, 124 | ImmutableSet labels) { 125 | Counter metric = new Counter(name, description, valueDisplayName, labels); 126 | registerMetric(name, metric); 127 | logger.info("Registered new counter: " + name); 128 | 129 | return metric; 130 | } 131 | 132 | @Override 133 | public ImmutableList> getRegisteredMetrics() { 134 | return ImmutableList.copyOf(registeredMetrics.values()); 135 | } 136 | 137 | /** 138 | * Unregisters a metric. 139 | * 140 | *

This is a no-op if the metric is not already registered. 141 | * 142 | *

{@link MetricWriter} implementations should not send unregistered metrics on subsequent 143 | * write intervals. 144 | */ 145 | @VisibleForTesting 146 | public void unregisterMetric(String name) { 147 | registeredMetrics.remove(name); 148 | logger.info("Unregistered metric: " + name); 149 | } 150 | 151 | @VisibleForTesting 152 | public void unregisterAllMetrics() { 153 | registeredMetrics.clear(); 154 | } 155 | 156 | /** Registers a metric. */ 157 | @VisibleForTesting 158 | void registerMetric(String name, Metric metric) { 159 | Metric previousMetric = registeredMetrics.putIfAbsent(name, metric); 160 | 161 | checkState(previousMetric == null, "Duplicate metric of same name: %s", name); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/MetricReporter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | import static com.google.common.util.concurrent.MoreExecutors.directExecutor; 19 | 20 | import com.google.common.annotations.VisibleForTesting; 21 | import com.google.common.collect.ImmutableList; 22 | import com.google.common.util.concurrent.AbstractScheduledService; 23 | import java.util.Optional; 24 | import java.util.concurrent.ArrayBlockingQueue; 25 | import java.util.concurrent.BlockingQueue; 26 | import java.util.concurrent.Executors; 27 | import java.util.concurrent.ScheduledExecutorService; 28 | import java.util.concurrent.ThreadFactory; 29 | import java.util.concurrent.TimeUnit; 30 | import java.util.concurrent.TimeoutException; 31 | import java.util.logging.Level; 32 | import java.util.logging.Logger; 33 | 34 | /** 35 | * Engine to write metrics to a {@link MetricWriter} on a regular periodic basis. 36 | * 37 | *

In the Producer/Consumer pattern, this class is the Producer and {@link MetricExporter} is the 38 | * consumer. 39 | */ 40 | public class MetricReporter extends AbstractScheduledService { 41 | 42 | private static final Logger logger = Logger.getLogger(MetricReporter.class.getName()); 43 | 44 | private final long writeInterval; 45 | private final MetricRegistry metricRegistry; 46 | private final BlockingQueue>>> writeQueue; 47 | private MetricExporter metricExporter; 48 | private final MetricWriter metricWriter; 49 | private final ThreadFactory threadFactory; 50 | 51 | /** 52 | * Returns a new MetricReporter. 53 | * 54 | * @param metricWriter {@link MetricWriter} implementation to write metrics to. 55 | * @param writeInterval time period between metric writes, in seconds. 56 | * @param threadFactory factory to use when creating background threads. 57 | */ 58 | public MetricReporter( 59 | MetricWriter metricWriter, long writeInterval, ThreadFactory threadFactory) { 60 | this( 61 | metricWriter, 62 | writeInterval, 63 | threadFactory, 64 | MetricRegistryImpl.getDefault(), 65 | new ArrayBlockingQueue<>(1000)); 66 | } 67 | 68 | @VisibleForTesting 69 | MetricReporter( 70 | MetricWriter metricWriter, 71 | long writeInterval, 72 | ThreadFactory threadFactory, 73 | MetricRegistry metricRegistry, 74 | BlockingQueue>>> writeQueue) { 75 | checkArgument(writeInterval > 0, "writeInterval must be greater than zero"); 76 | 77 | this.metricWriter = metricWriter; 78 | this.writeInterval = writeInterval; 79 | this.threadFactory = threadFactory; 80 | this.metricRegistry = metricRegistry; 81 | this.writeQueue = writeQueue; 82 | this.metricExporter = new MetricExporter(writeQueue, metricWriter, threadFactory); 83 | } 84 | 85 | @Override 86 | protected void runOneIteration() { 87 | logger.fine("Running background metric push"); 88 | 89 | if (metricExporter.state() == State.FAILED) { 90 | startMetricExporter(); 91 | } 92 | 93 | ImmutableList.Builder> points = new ImmutableList.Builder<>(); 94 | 95 | /* 96 | TODO(shikhman): Right now timestamps are recorded for each datapoint, which may use more storage 97 | on the backend than if one timestamp were recorded for a batch. This should be configurable. 98 | */ 99 | for (Metric metric : metricRegistry.getRegisteredMetrics()) { 100 | points.addAll(metric.getTimestampedValues()); 101 | logger.fine(String.format("Enqueued metric %s", metric)); 102 | MetricMetrics.pushedPoints.increment( 103 | metric.getMetricSchema().kind().name(), metric.getValueClass().toString()); 104 | } 105 | 106 | if (!writeQueue.offer(Optional.of(points.build()))) { 107 | logger.severe("writeQueue full, dropped a reporting interval of points"); 108 | } 109 | 110 | MetricMetrics.pushIntervals.increment(); 111 | } 112 | 113 | @Override 114 | protected void shutDown() { 115 | // Make sure to run one iteration on shutdown so that short-lived programs still report at 116 | // least once. 117 | runOneIteration(); 118 | 119 | // Offer a poision pill to inform the exporter to stop. 120 | writeQueue.offer(Optional.empty()); 121 | try { 122 | metricExporter.awaitTerminated(10, TimeUnit.SECONDS); 123 | logger.info("Shut down MetricExporter"); 124 | } catch (IllegalStateException exception) { 125 | logger.log( 126 | Level.SEVERE, 127 | "Failed to shut down MetricExporter because it was FAILED", 128 | metricExporter.failureCause()); 129 | } catch (TimeoutException exception) { 130 | logger.log(Level.SEVERE, "Failed to shut down MetricExporter within the timeout", exception); 131 | } 132 | } 133 | 134 | @Override 135 | protected void startUp() { 136 | startMetricExporter(); 137 | } 138 | 139 | @Override 140 | protected Scheduler scheduler() { 141 | // Start writing after waiting for one writeInterval. 142 | return Scheduler.newFixedDelaySchedule(writeInterval, writeInterval, TimeUnit.SECONDS); 143 | } 144 | 145 | @Override 146 | protected ScheduledExecutorService executor() { 147 | final ScheduledExecutorService executor = 148 | Executors.newSingleThreadScheduledExecutor(threadFactory); 149 | // Make sure the ExecutorService terminates when this service does. 150 | addListener( 151 | new Listener() { 152 | @Override 153 | public void terminated(State from) { 154 | executor.shutdown(); 155 | } 156 | 157 | @Override 158 | public void failed(State from, Throwable failure) { 159 | executor.shutdown(); 160 | } 161 | }, 162 | directExecutor()); 163 | return executor; 164 | } 165 | 166 | private void startMetricExporter() { 167 | switch (metricExporter.state()) { 168 | case NEW: 169 | metricExporter.startAsync(); 170 | break; 171 | case FAILED: 172 | logger.log( 173 | Level.SEVERE, 174 | "MetricExporter died unexpectedly, restarting", 175 | metricExporter.failureCause()); 176 | this.metricExporter = new MetricExporter(writeQueue, metricWriter, threadFactory); 177 | this.metricExporter.startAsync(); 178 | break; 179 | default: 180 | throw new IllegalStateException( 181 | "MetricExporter not FAILED or NEW, should not be calling startMetricExporter"); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/MetricSchema.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | 19 | import com.google.auto.value.AutoValue; 20 | import com.google.common.annotations.VisibleForTesting; 21 | import com.google.common.collect.ImmutableSet; 22 | 23 | /** The description of a metric's schema. */ 24 | @AutoValue 25 | public abstract class MetricSchema { 26 | 27 | MetricSchema() {} 28 | 29 | /** 30 | * Returns an instance of {@link MetricSchema}. 31 | * 32 | * @param name must have a URL-like hierarchical name, for example "/cpu/utilization". 33 | * @param description a human readable description of the metric. Must not be blank. 34 | * @param valueDisplayName a human readable description of the metric's value. Must not be blank. 35 | * @param kind the kind of metric. 36 | * @param labels an ordered set of mandatory metric labels. For example, a metric may track error 37 | * code as a label. If labels are set, corresponding label values must be provided when values 38 | * are set. The set of labels may be empty. 39 | */ 40 | @VisibleForTesting 41 | public static MetricSchema create( 42 | String name, 43 | String description, 44 | String valueDisplayName, 45 | Kind kind, 46 | ImmutableSet labels) { 47 | checkArgument(!name.isEmpty(), "Name must not be blank"); 48 | checkArgument(!description.isEmpty(), "Description must not be blank"); 49 | checkArgument(!valueDisplayName.isEmpty(), "Value Display Name must not be empty"); 50 | checkArgument(name.startsWith("/"), "Name must be URL-like and start with a '/'"); 51 | // TODO(b/30916431): strengthen metric name validation. 52 | 53 | return new AutoValue_MetricSchema(name, description, valueDisplayName, kind, labels); 54 | } 55 | 56 | public abstract String name(); 57 | 58 | public abstract String description(); 59 | 60 | public abstract String valueDisplayName(); 61 | 62 | public abstract Kind kind(); 63 | 64 | public abstract ImmutableSet labels(); 65 | 66 | /** 67 | * The kind of metric. CUMULATIVE metrics have values relative to an initial value, and GAUGE 68 | * metrics have values which are standalone. 69 | */ 70 | public enum Kind { 71 | CUMULATIVE, 72 | GAUGE, 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/MetricWriter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import java.io.Flushable; 18 | import java.io.IOException; 19 | 20 | /** An interface for exporting Metrics. */ 21 | public interface MetricWriter extends Flushable { 22 | 23 | /** 24 | * Writes a {@link MetricPoint} to the writer's destination. 25 | * 26 | *

The write may be asynchronous. 27 | * 28 | * @throws IOException if the provided metric cannot be represented by the writer or if the metric 29 | * cannot be flushed. 30 | */ 31 | void write(MetricPoint metricPoint) throws IOException; 32 | 33 | /** Forces the writer to synchronously write all buffered metric values. */ 34 | @Override 35 | void flush() throws IOException; 36 | } 37 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/MetricsUtils.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | 19 | import com.google.common.collect.ImmutableList; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | 22 | /** Static helper methods for the Metrics library. */ 23 | final class MetricsUtils { 24 | 25 | private static final Double NEGATIVE_ZERO = -0.0; 26 | private static final String LABEL_SIZE_ERROR = 27 | "The count of labelValues must be equal to the underlying Metric's count of labels."; 28 | 29 | /** 30 | * The below constants replicate the default initial capacity, load factor, and concurrency level 31 | * for {@link ConcurrentHashMap} as of Java SE 7. They are recorded here so that a {@link 32 | * com.google.common.util.concurrent.Striped} object can be constructed with a concurrency level 33 | * matching the default concurrency level of a {@link ConcurrentHashMap}. 34 | */ 35 | private static final int HASHMAP_INITIAL_CAPACITY = 16; 36 | private static final float HASHMAP_LOAD_FACTOR = 0.75f; 37 | static final int DEFAULT_CONCURRENCY_LEVEL = 16; 38 | 39 | private MetricsUtils() {} 40 | 41 | /** 42 | * Check that the given {@code labelValues} match in count with the count of {@link 43 | * LabelDescriptor} instances on the given {@code metric} 44 | * 45 | * @throws IllegalArgumentException if there is a count mismatch. 46 | */ 47 | static void checkLabelValuesLength(Metric metric, String[] labelValues) { 48 | checkArgument(labelValues.length == metric.getMetricSchema().labels().size(), LABEL_SIZE_ERROR); 49 | } 50 | 51 | /** 52 | * Check that the given {@code labelValues} match in count with the count of {@link 53 | * LabelDescriptor} instances on the given {@code metric} 54 | * 55 | * @throws IllegalArgumentException if there is a count mismatch. 56 | */ 57 | static void checkLabelValuesLength(Metric metric, ImmutableList labelValues) { 58 | checkArgument(labelValues.size() == metric.getMetricSchema().labels().size(), LABEL_SIZE_ERROR); 59 | } 60 | 61 | /** Check that the given double is not infinite, {@code NaN}, or {@code -0.0}. */ 62 | static void checkDouble(double value) { 63 | checkArgument( 64 | !Double.isInfinite(value) && !Double.isNaN(value) && !NEGATIVE_ZERO.equals(value), 65 | "value must be finite, not NaN, and not -0.0"); 66 | } 67 | 68 | static ConcurrentHashMap newConcurrentHashMap(int concurrencyLevel) { 69 | return new ConcurrentHashMap<>(HASHMAP_INITIAL_CAPACITY, HASHMAP_LOAD_FACTOR, concurrencyLevel); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/MutableDistribution.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | import static com.google.monitoring.metrics.MetricsUtils.checkDouble; 20 | 21 | import com.google.common.collect.ImmutableRangeMap; 22 | import com.google.common.collect.ImmutableSortedSet; 23 | import com.google.common.collect.Ordering; 24 | import com.google.common.collect.Range; 25 | import com.google.common.collect.TreeRangeMap; 26 | import com.google.common.primitives.Doubles; 27 | import java.util.Map; 28 | import javax.annotation.concurrent.NotThreadSafe; 29 | 30 | /** 31 | * A mutable {@link Distribution}. Instances of this class should not be used to construct 32 | * {@link MetricPoint} instances as {@link MetricPoint} instances are supposed to represent 33 | * immutable values. 34 | * 35 | * @see ImmutableDistribution 36 | */ 37 | @NotThreadSafe 38 | public final class MutableDistribution implements Distribution { 39 | 40 | private final TreeRangeMap intervalCounts; 41 | private final DistributionFitter distributionFitter; 42 | private double sumOfSquaredDeviation = 0.0; 43 | private double mean = 0.0; 44 | private long count = 0; 45 | 46 | /** Constructs an empty Distribution with the specified {@link DistributionFitter}. */ 47 | public MutableDistribution(DistributionFitter distributionFitter) { 48 | this.distributionFitter = checkNotNull(distributionFitter); 49 | ImmutableSortedSet boundaries = distributionFitter.boundaries(); 50 | 51 | checkArgument(boundaries.size() > 0); 52 | checkArgument(Ordering.natural().isOrdered(boundaries)); 53 | 54 | this.intervalCounts = TreeRangeMap.create(); 55 | 56 | double[] boundariesArray = Doubles.toArray(distributionFitter.boundaries()); 57 | 58 | // Add underflow and overflow intervals 59 | this.intervalCounts.put(Range.lessThan(boundariesArray[0]), 0L); 60 | this.intervalCounts.put(Range.atLeast(boundariesArray[boundariesArray.length - 1]), 0L); 61 | 62 | // Add finite intervals 63 | for (int i = 1; i < boundariesArray.length; i++) { 64 | this.intervalCounts.put(Range.closedOpen(boundariesArray[i - 1], boundariesArray[i]), 0L); 65 | } 66 | } 67 | 68 | public void add(double value) { 69 | add(value, 1L); 70 | } 71 | 72 | public void add(double value, long numSamples) { 73 | checkArgument(numSamples >= 0, "numSamples must be non-negative"); 74 | checkDouble(value); 75 | 76 | // having numSamples = 0 works as expected (does nothing) even if we let it continue, but we 77 | // can short-circuit it by returning early. 78 | if (numSamples == 0) { 79 | return; 80 | } 81 | 82 | Map.Entry, Long> entry = intervalCounts.getEntry(value); 83 | intervalCounts.put(entry.getKey(), entry.getValue() + numSamples); 84 | this.count += numSamples; 85 | 86 | // Update mean and sumOfSquaredDeviation using Welford's method 87 | // See Knuth, "The Art of Computer Programming", Vol. 2, page 232, 3rd edition 88 | double delta = value - mean; 89 | mean += delta * numSamples / count; 90 | sumOfSquaredDeviation += delta * (value - mean) * numSamples; 91 | } 92 | 93 | @Override 94 | public double mean() { 95 | return mean; 96 | } 97 | 98 | @Override 99 | public double sumOfSquaredDeviation() { 100 | return sumOfSquaredDeviation; 101 | } 102 | 103 | @Override 104 | public long count() { 105 | return count; 106 | } 107 | 108 | @Override 109 | public ImmutableRangeMap intervalCounts() { 110 | return ImmutableRangeMap.copyOf(intervalCounts); 111 | } 112 | 113 | @Override 114 | public DistributionFitter distributionFitter() { 115 | return distributionFitter; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/SettableMetric.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | /** A {@link Metric} which can be set to different values over time. */ 18 | public interface SettableMetric extends Metric { 19 | 20 | /** 21 | * Sets the metric's value for a given set of label values. The count of labelValues must equal to 22 | * the underlying metric's count of labels. 23 | */ 24 | void set(V value, String... labelValues); 25 | } 26 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/StoredMetric.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import com.google.common.annotations.VisibleForTesting; 18 | import com.google.common.collect.ImmutableList; 19 | import com.google.common.collect.ImmutableList.Builder; 20 | import com.google.common.collect.ImmutableSet; 21 | import com.google.monitoring.metrics.MetricSchema.Kind; 22 | import java.time.Instant; 23 | import java.util.Map.Entry; 24 | import java.util.concurrent.ConcurrentHashMap; 25 | import javax.annotation.concurrent.ThreadSafe; 26 | 27 | /** 28 | * A metric which is stateful. 29 | * 30 | *

The values are stored and set over time. This metric is generally suitable for state 31 | * indicators, such as indicating that a server is in a RUNNING state or in a STOPPED state. 32 | * 33 | *

See {@link Counter} for a subclass which is suitable for stateful incremental values. 34 | * 35 | *

The {@link MetricPoint#interval()} of values of instances of this metric will always have a 36 | * start time equal to the end time, since the metric value represents a point-in-time snapshot with 37 | * no relationship to prior values. 38 | */ 39 | @ThreadSafe 40 | public class StoredMetric extends AbstractMetric implements SettableMetric { 41 | 42 | private final ConcurrentHashMap, V> values = new ConcurrentHashMap<>(); 43 | 44 | StoredMetric( 45 | String name, 46 | String description, 47 | String valueDisplayName, 48 | ImmutableSet labels, 49 | Class valueClass) { 50 | super(name, description, valueDisplayName, Kind.GAUGE, labels, valueClass); 51 | } 52 | 53 | @VisibleForTesting 54 | final void set(V value, ImmutableList labelValues) { 55 | this.values.put(labelValues, value); 56 | } 57 | 58 | @Override 59 | public final void set(V value, String... labelValues) { 60 | MetricsUtils.checkLabelValuesLength(this, labelValues); 61 | 62 | set(value, ImmutableList.copyOf(labelValues)); 63 | } 64 | 65 | /** 66 | * Returns a snapshot of the metric's values. The timestamp of each MetricPoint will be the last 67 | * modification time for that tuple of label values. 68 | */ 69 | @Override 70 | public final ImmutableList> getTimestampedValues() { 71 | return getTimestampedValues(Instant.now()); 72 | } 73 | 74 | @Override 75 | public final int getCardinality() { 76 | return values.size(); 77 | } 78 | 79 | @VisibleForTesting 80 | final ImmutableList> getTimestampedValues(Instant timestamp) { 81 | ImmutableList.Builder> timestampedValues = new Builder<>(); 82 | for (Entry, V> entry : values.entrySet()) { 83 | timestampedValues.add( 84 | MetricPoint.create(this, entry.getKey(), timestamp, timestamp, entry.getValue())); 85 | } 86 | 87 | return timestampedValues.build(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/VirtualMetric.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import com.google.common.annotations.VisibleForTesting; 18 | import com.google.common.base.Supplier; 19 | import com.google.common.collect.ImmutableList; 20 | import com.google.common.collect.ImmutableMap; 21 | import com.google.common.collect.ImmutableSet; 22 | import com.google.monitoring.metrics.MetricSchema.Kind; 23 | import java.time.Instant; 24 | import java.util.Map.Entry; 25 | import javax.annotation.concurrent.ThreadSafe; 26 | 27 | /** 28 | * A metric whose value is computed at sample-time. 29 | * 30 | *

This pattern works well for gauge-like metrics, such as CPU usage, memory usage, and file 31 | * descriptor counts. 32 | * 33 | *

The {@link MetricPoint#interval()} of values of instances of this metric will always have a 34 | * start time equal to the end time, since the metric value represents a point-in-time snapshot with 35 | * no relationship to prior values. 36 | */ 37 | @ThreadSafe 38 | public final class VirtualMetric extends AbstractMetric { 39 | 40 | private final Supplier, V>> valuesSupplier; 41 | 42 | /** 43 | * Local cache of the count of values so that we don't have to evaluate the callback function to 44 | * get the metric's cardinality. 45 | */ 46 | private volatile int cardinality; 47 | 48 | VirtualMetric( 49 | String name, 50 | String description, 51 | String valueDisplayName, 52 | ImmutableSet labels, 53 | Supplier, V>> valuesSupplier, 54 | Class valueClass) { 55 | super(name, description, valueDisplayName, Kind.GAUGE, labels, valueClass); 56 | 57 | this.valuesSupplier = valuesSupplier; 58 | } 59 | 60 | /** 61 | * Returns a snapshot of the metric's values. This will evaluate the stored callback function. The 62 | * timestamp for each MetricPoint will be the current time. 63 | */ 64 | @Override 65 | public ImmutableList> getTimestampedValues() { 66 | return getTimestampedValues(Instant.now()); 67 | } 68 | 69 | /** 70 | * Returns the cached value of the cardinality of this metric. The cardinality is computed when 71 | * the metric is evaluated. If the metric has never been evaluated, the cardinality is zero. 72 | */ 73 | @Override 74 | public int getCardinality() { 75 | return cardinality; 76 | } 77 | 78 | @VisibleForTesting 79 | ImmutableList> getTimestampedValues(Instant timestamp) { 80 | ImmutableMap, V> values = valuesSupplier.get(); 81 | 82 | ImmutableList.Builder> metricPoints = new ImmutableList.Builder<>(); 83 | for (Entry, V> entry : values.entrySet()) { 84 | metricPoints.add( 85 | MetricPoint.create(this, entry.getKey(), timestamp, timestamp, entry.getValue())); 86 | } 87 | 88 | cardinality = values.size(); 89 | return metricPoints.build(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /metrics/src/main/java/com/google/monitoring/metrics/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @javax.annotation.ParametersAreNonnullByDefault 16 | package com.google.monitoring.metrics; 17 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/CounterTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import com.google.common.collect.ImmutableList; 21 | import com.google.common.collect.ImmutableSet; 22 | import java.time.Instant; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.junit.runners.JUnit4; 26 | 27 | /** Unit tests for {@link Counter}. */ 28 | @RunWith(JUnit4.class) 29 | public class CounterTest { 30 | 31 | @Test 32 | public void testGetCardinality_reflectsCurrentCardinality() { 33 | Counter counter = 34 | new Counter( 35 | "/metric", 36 | "description", 37 | "vdn", 38 | ImmutableSet.of(LabelDescriptor.create("label1", "bar"))); 39 | assertThat(counter.getCardinality()).isEqualTo(0); 40 | 41 | counter.increment("foo"); 42 | assertThat(counter.getCardinality()).isEqualTo(1); 43 | counter.increment("bar"); 44 | assertThat(counter.getCardinality()).isEqualTo(2); 45 | counter.increment("foo"); 46 | assertThat(counter.getCardinality()).isEqualTo(2); 47 | } 48 | 49 | @Test 50 | public void testIncrementBy_wrongLabelValueCount_throwsException() { 51 | Counter counter = 52 | new Counter( 53 | "/metric", 54 | "description", 55 | "vdn", 56 | ImmutableSet.of( 57 | LabelDescriptor.create("label1", "bar"), LabelDescriptor.create("label2", "bar"))); 58 | 59 | IllegalArgumentException thrown = 60 | assertThrows(IllegalArgumentException.class, () -> counter.increment("blah")); 61 | assertThat(thrown) 62 | .hasMessageThat() 63 | .contains( 64 | "The count of labelValues must be equal to the underlying Metric's count of labels."); 65 | } 66 | 67 | @Test 68 | public void testIncrement_incrementsValues() { 69 | Counter counter = 70 | new Counter( 71 | "/metric", 72 | "description", 73 | "vdn", 74 | ImmutableSet.of(LabelDescriptor.create("label1", "bar"))); 75 | 76 | assertThat(counter.getTimestampedValues()).isEmpty(); 77 | 78 | // use package-private incrementBy once to set the start timestamp predictably. 79 | counter.incrementBy(1, Instant.ofEpochMilli(1337), ImmutableList.of("test_value1")); 80 | assertThat(counter.getTimestampedValues(Instant.ofEpochMilli(1337))) 81 | .containsExactly( 82 | MetricPoint.create( 83 | counter, ImmutableList.of("test_value1"), Instant.ofEpochMilli(1337), 1L)); 84 | 85 | counter.increment("test_value1"); 86 | assertThat(counter.getTimestampedValues(Instant.ofEpochMilli(1337))) 87 | .containsExactly( 88 | MetricPoint.create( 89 | counter, ImmutableList.of("test_value1"), Instant.ofEpochMilli(1337), 2L)); 90 | } 91 | 92 | @Test 93 | public void testIncrementBy_incrementsValues() { 94 | Counter counter = 95 | new Counter( 96 | "/metric", 97 | "description", 98 | "vdn", 99 | ImmutableSet.of(LabelDescriptor.create("label1", "bar"))); 100 | 101 | assertThat(counter.getTimestampedValues()).isEmpty(); 102 | 103 | counter.incrementBy(1, Instant.ofEpochMilli(1337), ImmutableList.of("test_value1")); 104 | assertThat(counter.getTimestampedValues(Instant.ofEpochMilli(1337))) 105 | .containsExactly( 106 | MetricPoint.create( 107 | counter, ImmutableList.of("test_value1"), Instant.ofEpochMilli(1337), 1L)); 108 | 109 | counter.set(-10L, Instant.ofEpochMilli(1337), ImmutableList.of("test_value2")); 110 | counter.incrementBy(5, Instant.ofEpochMilli(1337), ImmutableList.of("test_value2")); 111 | assertThat(counter.getTimestampedValues(Instant.ofEpochMilli(1337))) 112 | .containsExactly( 113 | MetricPoint.create( 114 | counter, ImmutableList.of("test_value1"), Instant.ofEpochMilli(1337), 1L), 115 | MetricPoint.create( 116 | counter, ImmutableList.of("test_value2"), Instant.ofEpochMilli(1337), -5L)); 117 | } 118 | 119 | @Test 120 | public void testIncrementBy_negativeOffset_throwsException() { 121 | Counter counter = 122 | new Counter( 123 | "/metric", 124 | "description", 125 | "vdn", 126 | ImmutableSet.of(LabelDescriptor.create("label1", "bar"))); 127 | 128 | IllegalArgumentException thrown = 129 | assertThrows(IllegalArgumentException.class, () -> counter.incrementBy(-1L, "foo")); 130 | assertThat(thrown).hasMessageThat().contains("The offset provided must be non-negative"); 131 | } 132 | 133 | @Test 134 | public void testResetAll_resetsAllValuesAndStartTimestamps() { 135 | Counter counter = 136 | new Counter( 137 | "/metric", 138 | "description", 139 | "vdn", 140 | ImmutableSet.of(LabelDescriptor.create("label1", "bar"))); 141 | 142 | counter.incrementBy(3, Instant.ofEpochMilli(1337), ImmutableList.of("foo")); 143 | counter.incrementBy(5, Instant.ofEpochMilli(1338), ImmutableList.of("moo")); 144 | 145 | assertThat(counter.getTimestampedValues(Instant.ofEpochMilli(1400))) 146 | .containsExactly( 147 | MetricPoint.create( 148 | counter, 149 | ImmutableList.of("foo"), 150 | Instant.ofEpochMilli(1337), 151 | Instant.ofEpochMilli(1400), 152 | 3L), 153 | MetricPoint.create( 154 | counter, 155 | ImmutableList.of("moo"), 156 | Instant.ofEpochMilli(1338), 157 | Instant.ofEpochMilli(1400), 158 | 5L)); 159 | 160 | counter.reset(Instant.ofEpochMilli(1339)); 161 | 162 | assertThat(counter.getTimestampedValues(Instant.ofEpochMilli(1400))) 163 | .containsExactly( 164 | MetricPoint.create( 165 | counter, 166 | ImmutableList.of("foo"), 167 | Instant.ofEpochMilli(1339), 168 | Instant.ofEpochMilli(1400), 169 | 0L), 170 | MetricPoint.create( 171 | counter, 172 | ImmutableList.of("moo"), 173 | Instant.ofEpochMilli(1339), 174 | Instant.ofEpochMilli(1400), 175 | 0L)); 176 | } 177 | 178 | @Test 179 | public void testReset_resetsValuesAndStartTimestamps() { 180 | Counter counter = 181 | new Counter( 182 | "/metric", 183 | "description", 184 | "vdn", 185 | ImmutableSet.of(LabelDescriptor.create("label1", "bar"))); 186 | 187 | counter.incrementBy(3, Instant.ofEpochMilli(1337), ImmutableList.of("foo")); 188 | counter.incrementBy(5, Instant.ofEpochMilli(1338), ImmutableList.of("moo")); 189 | 190 | assertThat(counter.getTimestampedValues(Instant.ofEpochMilli(1400))) 191 | .containsExactly( 192 | MetricPoint.create( 193 | counter, 194 | ImmutableList.of("foo"), 195 | Instant.ofEpochMilli(1337), 196 | Instant.ofEpochMilli(1400), 197 | 3L), 198 | MetricPoint.create( 199 | counter, 200 | ImmutableList.of("moo"), 201 | Instant.ofEpochMilli(1338), 202 | Instant.ofEpochMilli(1400), 203 | 5L)); 204 | 205 | counter.reset(Instant.ofEpochMilli(1339), ImmutableList.of("foo")); 206 | 207 | assertThat(counter.getTimestampedValues(Instant.ofEpochMilli(1400))) 208 | .containsExactly( 209 | MetricPoint.create( 210 | counter, 211 | ImmutableList.of("foo"), 212 | Instant.ofEpochMilli(1339), 213 | Instant.ofEpochMilli(1400), 214 | 0L), 215 | MetricPoint.create( 216 | counter, 217 | ImmutableList.of("moo"), 218 | Instant.ofEpochMilli(1338), 219 | Instant.ofEpochMilli(1400), 220 | 5L)); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/CustomFitterTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import com.google.common.collect.ImmutableSet; 21 | import com.google.common.collect.ImmutableSortedSet; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.JUnit4; 25 | 26 | /** Tests for {@link CustomFitter}. */ 27 | @RunWith(JUnit4.class) 28 | public class CustomFitterTest { 29 | @Test 30 | public void testCreateCustomFitter_emptyBounds_throwsException() throws Exception { 31 | IllegalArgumentException thrown = 32 | assertThrows( 33 | IllegalArgumentException.class, () -> CustomFitter.create(ImmutableSet.of())); 34 | assertThat(thrown).hasMessageThat().contains("boundaries must not be empty"); 35 | } 36 | 37 | @Test 38 | public void testCreateCustomFitter_outOfOrderBounds_throwsException() throws Exception { 39 | IllegalArgumentException thrown = 40 | assertThrows( 41 | IllegalArgumentException.class, () -> CustomFitter.create(ImmutableSet.of(2.0, 0.0))); 42 | assertThat(thrown).hasMessageThat().contains("boundaries must be sorted"); 43 | } 44 | 45 | @Test 46 | public void testCreateCustomFitter_hasGivenBounds() { 47 | CustomFitter fitter = CustomFitter.create(ImmutableSortedSet.of(1.0, 2.0)); 48 | 49 | assertThat(fitter.boundaries()).containsExactly(1.0, 2.0).inOrder(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/ExponentialFitterTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | 24 | /** Unit tests for {@link ExponentialFitter}. */ 25 | @RunWith(JUnit4.class) 26 | public class ExponentialFitterTest { 27 | @Test 28 | public void testCreateExponentialFitter_zeroNumIntervals_throwsException() throws Exception { 29 | IllegalArgumentException thrown = 30 | assertThrows(IllegalArgumentException.class, () -> ExponentialFitter.create(0, 3.0, 1.0)); 31 | assertThat(thrown).hasMessageThat().contains("numFiniteIntervals must be greater than 0"); 32 | } 33 | 34 | @Test 35 | public void testCreateExponentialFitter_negativeNumIntervals_throwsException() throws Exception { 36 | IllegalArgumentException thrown = 37 | assertThrows(IllegalArgumentException.class, () -> ExponentialFitter.create(-1, 3.0, 1.0)); 38 | assertThat(thrown).hasMessageThat().contains("numFiniteIntervals must be greater than 0"); 39 | } 40 | 41 | @Test 42 | public void testCreateExponentialFitter_invalidBase_throwsException() throws Exception { 43 | IllegalArgumentException thrown = 44 | assertThrows(IllegalArgumentException.class, () -> ExponentialFitter.create(3, 0.5, 1.0)); 45 | assertThat(thrown).hasMessageThat().contains("base must be greater than 1"); 46 | } 47 | 48 | @Test 49 | public void testCreateExponentialFitter_zeroScale_throwsException() throws Exception { 50 | IllegalArgumentException thrown = 51 | assertThrows(IllegalArgumentException.class, () -> ExponentialFitter.create(3, 2.0, 0.0)); 52 | assertThat(thrown).hasMessageThat().contains("scale must not be 0"); 53 | } 54 | 55 | @Test 56 | public void testCreateExponentialFitter_NanScale_throwsException() throws Exception { 57 | IllegalArgumentException thrown = 58 | assertThrows( 59 | IllegalArgumentException.class, () -> ExponentialFitter.create(3, 2.0, Double.NaN)); 60 | assertThat(thrown).hasMessageThat().contains("value must be finite, not NaN, and not -0.0"); 61 | } 62 | 63 | @Test 64 | public void testCreateExponentialFitter_hasCorrectBounds() { 65 | ExponentialFitter fitter = ExponentialFitter.create(3, 5.0, 2.0); 66 | 67 | assertThat(fitter.boundaries()).containsExactly(2.0, 10.0, 50.0, 250.0).inOrder(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/FibonacciFitterTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import com.google.common.collect.ImmutableList; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.junit.runners.JUnit4; 24 | 25 | /** Unit tests for {@link FibonacciFitter}. */ 26 | @RunWith(JUnit4.class) 27 | public class FibonacciFitterTest { 28 | 29 | @Test 30 | public void testCreate_maxBucketSizeNegative_throwsException() { 31 | IllegalArgumentException e = 32 | assertThrows(IllegalArgumentException.class, () -> FibonacciFitter.create(-1)); 33 | assertThat(e).hasMessageThat().isEqualTo("maxBucketSize must be greater than 0"); 34 | } 35 | 36 | @Test 37 | public void testCreate_maxBucketSizeZero_throwsException() { 38 | IllegalArgumentException e = 39 | assertThrows(IllegalArgumentException.class, () -> FibonacciFitter.create(0)); 40 | assertThat(e).hasMessageThat().isEqualTo("maxBucketSize must be greater than 0"); 41 | } 42 | 43 | @Test 44 | public void testCreate_maxBucketSizeOne_createsTwoBoundaries() { 45 | assertThat(FibonacciFitter.create(1).boundaries()).containsExactly(0.0, 1.0).inOrder(); 46 | } 47 | 48 | @Test 49 | public void testCreate_maxBucketSizeTwo_createsThreeBoundaries() { 50 | assertThat(FibonacciFitter.create(2).boundaries()).containsExactly(0.0, 1.0, 2.0).inOrder(); 51 | } 52 | 53 | @Test 54 | public void testCreate_maxBucketSizeThree_createsFourBoundaries() { 55 | assertThat(FibonacciFitter.create(3).boundaries()) 56 | .containsExactly(0.0, 1.0, 2.0, 3.0) 57 | .inOrder(); 58 | } 59 | 60 | @Test 61 | public void testCreate_maxBucketSizeFour_createsFourBoundaries() { 62 | assertThat(FibonacciFitter.create(4).boundaries()) 63 | .containsExactly(0.0, 1.0, 2.0, 3.0) 64 | .inOrder(); 65 | } 66 | 67 | @Test 68 | public void testCreate_maxBucketSizeLarge_createsFibonacciSequenceBoundaries() { 69 | ImmutableList expectedBoundaries = 70 | ImmutableList.of( 71 | 0.0, 1.0, 2.0, 3.0, 5.0, 8.0, 13.0, 21.0, 34.0, 55.0, 89.0, 144.0, 233.0, 377.0, 610.0, 72 | 987.0); 73 | assertThat(FibonacciFitter.create(1000).boundaries()) 74 | .containsExactlyElementsIn(expectedBoundaries) 75 | .inOrder(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/LabelDescriptorTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | 24 | /** Unit tests for {@link LabelDescriptor}. */ 25 | @RunWith(JUnit4.class) 26 | public class LabelDescriptorTest { 27 | 28 | @Test 29 | public void testCreate_invalidLabel_throwsException() { 30 | IllegalArgumentException thrown = 31 | assertThrows( 32 | IllegalArgumentException.class, () -> LabelDescriptor.create("@", "description")); 33 | assertThat(thrown).hasMessageThat().contains("Label name must match the regex"); 34 | } 35 | 36 | @Test 37 | public void testCreate_blankNameField_throwsException() { 38 | IllegalArgumentException thrown = 39 | assertThrows( 40 | IllegalArgumentException.class, () -> LabelDescriptor.create("", "description")); 41 | assertThat(thrown).hasMessageThat().contains("Name must not be empty"); 42 | } 43 | 44 | @Test 45 | public void testCreate_blankDescriptionField_throwsException() { 46 | IllegalArgumentException thrown = 47 | assertThrows(IllegalArgumentException.class, () -> LabelDescriptor.create("name", "")); 48 | assertThat(thrown).hasMessageThat().contains("Description must not be empty"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/LinearFitterTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | 24 | /** Unit tests for {@link LinearFitter}. */ 25 | @RunWith(JUnit4.class) 26 | public class LinearFitterTest { 27 | @Test 28 | public void testCreateLinearFitter_zeroNumIntervals_throwsException() throws Exception { 29 | IllegalArgumentException thrown = 30 | assertThrows(IllegalArgumentException.class, () -> LinearFitter.create(0, 3.0, 0.0)); 31 | assertThat(thrown).hasMessageThat().contains("numFiniteIntervals must be greater than 0"); 32 | } 33 | 34 | @Test 35 | public void testCreateLinearFitter_negativeNumIntervals_throwsException() throws Exception { 36 | IllegalArgumentException thrown = 37 | assertThrows(IllegalArgumentException.class, () -> LinearFitter.create(0, 3.0, 0.0)); 38 | assertThat(thrown).hasMessageThat().contains("numFiniteIntervals must be greater than 0"); 39 | } 40 | 41 | @Test 42 | public void testCreateLinearFitter_zeroWidth_throwsException() throws Exception { 43 | IllegalArgumentException thrown = 44 | assertThrows(IllegalArgumentException.class, () -> LinearFitter.create(3, 0.0, 0.0)); 45 | assertThat(thrown).hasMessageThat().contains("width must be greater than 0"); 46 | } 47 | 48 | @Test 49 | public void testCreateLinearFitter_negativeWidth_throwsException() throws Exception { 50 | IllegalArgumentException thrown = 51 | assertThrows(IllegalArgumentException.class, () -> LinearFitter.create(3, 0.0, 0.0)); 52 | assertThat(thrown).hasMessageThat().contains("width must be greater than 0"); 53 | } 54 | 55 | @Test 56 | public void testCreateLinearFitter_NaNWidth_throwsException() throws Exception { 57 | IllegalArgumentException thrown = 58 | assertThrows(IllegalArgumentException.class, () -> LinearFitter.create(3, Double.NaN, 0.0)); 59 | assertThat(thrown).hasMessageThat().contains("width must be greater than 0"); 60 | } 61 | 62 | @Test 63 | public void testCreateLinearFitter_NaNOffset_throwsException() throws Exception { 64 | IllegalArgumentException thrown = 65 | assertThrows(IllegalArgumentException.class, () -> LinearFitter.create(3, 1.0, Double.NaN)); 66 | assertThat(thrown).hasMessageThat().contains("value must be finite, not NaN, and not -0.0"); 67 | } 68 | 69 | @Test 70 | public void testCreateLinearFitter_hasCorrectBounds() { 71 | LinearFitter fitter = LinearFitter.create(1, 10, 0); 72 | 73 | assertThat(fitter.boundaries()).containsExactly(0.0, 10.0).inOrder(); 74 | } 75 | 76 | @Test 77 | public void testCreateLinearFitter_withOffset_hasCorrectBounds() { 78 | LinearFitter fitter = LinearFitter.create(1, 10, 5); 79 | 80 | assertThat(fitter.boundaries()).containsExactly(5.0, 15.0).inOrder(); 81 | } 82 | 83 | @Test 84 | public void testCreateLinearFitter_withOffsetAndMultipleIntervals_hasCorrectBounds() { 85 | LinearFitter fitter = LinearFitter.create(3, 10, 5); 86 | 87 | assertThat(fitter.boundaries()).containsExactly(5.0, 15.0, 25.0, 35.0).inOrder(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/MetricExporterTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static org.junit.Assert.fail; 19 | import static org.mockito.Mockito.doThrow; 20 | import static org.mockito.Mockito.times; 21 | import static org.mockito.Mockito.verify; 22 | 23 | import com.google.common.collect.ImmutableList; 24 | import com.google.common.util.concurrent.Service.State; 25 | import java.io.IOException; 26 | import java.util.Optional; 27 | import java.util.concurrent.ArrayBlockingQueue; 28 | import java.util.concurrent.BlockingQueue; 29 | import java.util.concurrent.Executors; 30 | import java.util.concurrent.TimeUnit; 31 | import java.util.concurrent.TimeoutException; 32 | import org.junit.Before; 33 | import org.junit.Test; 34 | import org.junit.runner.RunWith; 35 | import org.mockito.Matchers; 36 | import org.mockito.Mock; 37 | import org.mockito.runners.MockitoJUnitRunner; 38 | 39 | /** Unit tests for {@link MetricExporter}. */ 40 | @RunWith(MockitoJUnitRunner.class) 41 | public class MetricExporterTest { 42 | 43 | @Mock private MetricWriter writer; 44 | @Mock private MetricPoint point; 45 | private MetricExporter exporter; 46 | private BlockingQueue>>> writeQueue; 47 | private final Optional>> poisonPill = Optional.empty(); 48 | private final Optional>> emptyBatch = 49 | Optional.of(ImmutableList.>of()); 50 | 51 | @Before 52 | public void setUp() throws Exception { 53 | writeQueue = new ArrayBlockingQueue<>(1); 54 | exporter = new MetricExporter(writeQueue, writer, Executors.defaultThreadFactory()); 55 | } 56 | 57 | @Test 58 | public void testRun_takesFromQueue_whileRunning() throws Exception { 59 | exporter.startAsync().awaitRunning(); 60 | 61 | insertAndAssert(emptyBatch); 62 | // Insert more batches to verify that the exporter hasn't gotten stuck 63 | insertAndAssert(emptyBatch); 64 | insertAndAssert(emptyBatch); 65 | 66 | assertThat(exporter.state()).isEqualTo(State.RUNNING); 67 | } 68 | 69 | @Test 70 | public void testRun_terminates_afterPoisonPill() throws Exception { 71 | exporter.startAsync().awaitRunning(); 72 | 73 | insertAndAssert(poisonPill); 74 | try { 75 | exporter.awaitTerminated(500, TimeUnit.MILLISECONDS); 76 | } catch (TimeoutException timeout) { 77 | fail("MetricExporter did not reach the TERMINATED state after receiving a poison pill"); 78 | } 79 | 80 | assertThat(exporter.state()).isEqualTo(State.TERMINATED); 81 | } 82 | 83 | @Test 84 | public void testRun_staysRunning_afterIOException() throws Exception { 85 | Optional>> threeBatch = 86 | Optional.of(ImmutableList.of(point, point, point)); 87 | doThrow(new IOException()).when(writer).write(Matchers.>any()); 88 | exporter.startAsync(); 89 | 90 | insertAndAssert(threeBatch); 91 | // Insert another batch in order to block until the exporter has processed the last one 92 | insertAndAssert(threeBatch); 93 | // Insert another to make sure the exporter hasn't gotten stuck 94 | insertAndAssert(threeBatch); 95 | 96 | assertThat(exporter.state()).isNotEqualTo(State.FAILED); 97 | } 98 | 99 | @Test 100 | public void testRun_writesMetrics() throws Exception { 101 | Optional>> threeBatch = 102 | Optional.of(ImmutableList.of(point, point, point)); 103 | exporter.startAsync(); 104 | 105 | insertAndAssert(threeBatch); 106 | // Insert another batch in order to block until the exporter has processed the last one 107 | insertAndAssert(threeBatch); 108 | 109 | // Force the exporter to finish so that the verify counts below are deterministic 110 | insertAndAssert(poisonPill); 111 | try { 112 | exporter.awaitTerminated(500, TimeUnit.MILLISECONDS); 113 | } catch (TimeoutException timeout) { 114 | fail("MetricExporter did not reach the TERMINATED state after receiving a poison pill"); 115 | } 116 | 117 | assertThat(exporter.state()).isNotEqualTo(State.FAILED); 118 | verify(writer, times(6)).write(point); 119 | verify(writer, times(2)).flush(); 120 | } 121 | 122 | /** 123 | * Helper method to insert into the {@link BlockingQueue} and assert that the item has been 124 | * enqueued. 125 | */ 126 | private void insertAndAssert(Optional>> batch) throws Exception { 127 | boolean isTaken = writeQueue.offer(batch, 500, TimeUnit.MILLISECONDS); 128 | assertThat(isTaken).isTrue(); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/MetricRegistryImplTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static org.junit.Assert.assertThrows; 19 | import static org.mockito.Mockito.mock; 20 | 21 | import com.google.common.collect.ImmutableList; 22 | import com.google.common.collect.ImmutableMap; 23 | import com.google.common.collect.ImmutableSet; 24 | import com.google.monitoring.metrics.MetricSchema.Kind; 25 | import org.junit.After; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.junit.runners.JUnit4; 29 | 30 | /** 31 | * Unit tests for {@link MetricRegistryImpl}. 32 | * 33 | *

The MetricRegistryImpl is a singleton, so we have to be careful to empty it after every test 34 | * to maintain a blank slate. 35 | */ 36 | @RunWith(JUnit4.class) 37 | public class MetricRegistryImplTest { 38 | private final LabelDescriptor label = 39 | LabelDescriptor.create("test_labelname", "test_labeldescription"); 40 | 41 | @After 42 | public void clearMetrics() { 43 | MetricRegistryImpl.getDefault().unregisterAllMetrics(); 44 | } 45 | 46 | @Test 47 | public void testRegisterAndUnregister_tracksRegistrations() { 48 | assertThat(MetricRegistryImpl.getDefault().getRegisteredMetrics()).isEmpty(); 49 | 50 | AbstractMetric metric = mock(AbstractMetric.class); 51 | MetricRegistryImpl.getDefault().registerMetric("/test/metric", metric); 52 | 53 | assertThat(MetricRegistryImpl.getDefault().getRegisteredMetrics()).containsExactly(metric); 54 | 55 | MetricRegistryImpl.getDefault().unregisterMetric("/test/metric"); 56 | 57 | assertThat(MetricRegistryImpl.getDefault().getRegisteredMetrics()).isEmpty(); 58 | } 59 | 60 | @Test 61 | public void testNewGauge_createsGauge() { 62 | Metric testGauge = 63 | MetricRegistryImpl.getDefault() 64 | .newGauge( 65 | "/test_metric", 66 | "test_description", 67 | "test_valuedisplayname", 68 | ImmutableSet.of(label), 69 | () -> ImmutableMap.of(ImmutableList.of("foo"), 1L), 70 | Long.class); 71 | 72 | assertThat(testGauge.getValueClass()).isSameInstanceAs(Long.class); 73 | assertThat(testGauge.getMetricSchema()) 74 | .isEqualTo( 75 | MetricSchema.create( 76 | "/test_metric", 77 | "test_description", 78 | "test_valuedisplayname", 79 | Kind.GAUGE, 80 | ImmutableSet.of(label))); 81 | } 82 | 83 | @Test 84 | public void testNewCounter_createsCounter() { 85 | IncrementableMetric testCounter = 86 | MetricRegistryImpl.getDefault() 87 | .newIncrementableMetric( 88 | "/test_counter", 89 | "test_description", 90 | "test_valuedisplayname", 91 | ImmutableSet.of(label)); 92 | 93 | assertThat(testCounter.getValueClass()).isSameInstanceAs(Long.class); 94 | assertThat(testCounter.getMetricSchema()) 95 | .isEqualTo( 96 | MetricSchema.create( 97 | "/test_counter", 98 | "test_description", 99 | "test_valuedisplayname", 100 | Kind.CUMULATIVE, 101 | ImmutableSet.of(label))); 102 | } 103 | 104 | @Test 105 | public void testNewSettableMetric_createsSettableMetric() { 106 | SettableMetric testMetric = 107 | MetricRegistryImpl.getDefault() 108 | .newSettableMetric( 109 | "/test_metric", 110 | "test_description", 111 | "test_valuedisplayname", 112 | ImmutableSet.of(label), 113 | Boolean.class); 114 | 115 | assertThat(testMetric.getValueClass()).isSameInstanceAs(Boolean.class); 116 | assertThat(testMetric.getMetricSchema()) 117 | .isEqualTo( 118 | MetricSchema.create( 119 | "/test_metric", 120 | "test_description", 121 | "test_valuedisplayname", 122 | Kind.GAUGE, 123 | ImmutableSet.of(label))); 124 | } 125 | 126 | @Test 127 | public void testNewEventMetric_createsEventMetric() { 128 | DistributionFitter fitter = CustomFitter.create(ImmutableSet.of(0.0)); 129 | EventMetric testMetric = 130 | MetricRegistryImpl.getDefault() 131 | .newEventMetric( 132 | "/test_metric", 133 | "test_description", 134 | "test_valuedisplayname", 135 | ImmutableSet.of(label), 136 | fitter); 137 | 138 | assertThat(testMetric.getValueClass()).isSameInstanceAs(Distribution.class); 139 | assertThat(testMetric.getMetricSchema()) 140 | .isEqualTo( 141 | MetricSchema.create( 142 | "/test_metric", 143 | "test_description", 144 | "test_valuedisplayname", 145 | Kind.CUMULATIVE, 146 | ImmutableSet.of(label))); 147 | } 148 | 149 | @Test 150 | public void testRegister_duplicateMetric_throwsException() { 151 | SettableMetric testMetric = 152 | MetricRegistryImpl.getDefault() 153 | .newSettableMetric( 154 | "/test_metric", 155 | "test_description", 156 | "test_valuedisplayname", 157 | ImmutableSet.of(label), 158 | Boolean.class); 159 | MetricRegistryImpl.getDefault().registerMetric("/test/metric", testMetric); 160 | 161 | IllegalStateException thrown = 162 | assertThrows( 163 | IllegalStateException.class, 164 | () -> MetricRegistryImpl.getDefault().registerMetric("/test/metric", testMetric)); 165 | assertThat(thrown).hasMessageThat().contains("Duplicate metric of same name"); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/MetricReporterTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static org.mockito.Mockito.spy; 18 | import static org.mockito.Mockito.verify; 19 | import static org.mockito.Mockito.when; 20 | 21 | import com.google.common.collect.ImmutableList; 22 | import com.google.common.collect.ImmutableSet; 23 | import java.util.Optional; 24 | import java.util.concurrent.BlockingQueue; 25 | import java.util.concurrent.ThreadFactory; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.mockito.InOrder; 29 | import org.mockito.Mock; 30 | import org.mockito.Mockito; 31 | import org.mockito.runners.MockitoJUnitRunner; 32 | 33 | /** Unit tests for {@link MetricReporter}. */ 34 | @RunWith(MockitoJUnitRunner.class) 35 | public class MetricReporterTest { 36 | 37 | @Mock MetricRegistry registry; 38 | @Mock Metric metric; 39 | @Mock ThreadFactory threadFactory; 40 | @Mock MetricWriter writer; 41 | @Mock MetricSchema metricSchema; 42 | @Mock BlockingQueue>>> writeQueue; 43 | 44 | @Test 45 | public void testRunOneIteration_enqueuesBatch() throws Exception { 46 | Metric metric = 47 | new Counter("/name", "description", "vdn", ImmutableSet.of()); 48 | when(registry.getRegisteredMetrics()).thenReturn(ImmutableList.of(metric, metric)); 49 | MetricReporter reporter = new MetricReporter(writer, 10L, threadFactory, registry, writeQueue); 50 | 51 | reporter.runOneIteration(); 52 | 53 | verify(writeQueue).offer(Optional.of(ImmutableList.>of())); 54 | } 55 | 56 | @Test 57 | public void testShutDown_enqueuesBatchAndPoisonPill() throws Exception { 58 | // Set up a registry with no metrics. 59 | when(registry.getRegisteredMetrics()).thenReturn(ImmutableList.>of()); 60 | MetricReporter reporter = 61 | spy(new MetricReporter(writer, 10L, threadFactory, registry, writeQueue)); 62 | 63 | reporter.shutDown(); 64 | 65 | verify(reporter).runOneIteration(); 66 | InOrder interactions = Mockito.inOrder(writeQueue); 67 | interactions.verify(writeQueue).offer(Optional.of(ImmutableList.>of())); 68 | interactions.verify(writeQueue).offer(Optional.>>empty()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/MetricSchemaTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import com.google.common.collect.ImmutableSet; 21 | import com.google.monitoring.metrics.MetricSchema.Kind; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.JUnit4; 25 | 26 | /** Unit tests for {@link MetricSchema}. */ 27 | @RunWith(JUnit4.class) 28 | public class MetricSchemaTest { 29 | 30 | @Test 31 | public void testCreate_blankNameField_throwsException() { 32 | IllegalArgumentException thrown = 33 | assertThrows( 34 | IllegalArgumentException.class, 35 | () -> 36 | MetricSchema.create( 37 | "", 38 | "description", 39 | "valueDisplayName", 40 | Kind.GAUGE, 41 | ImmutableSet.of())); 42 | assertThat(thrown).hasMessageThat().contains("Name must not be blank"); 43 | } 44 | 45 | @Test 46 | public void testCreate_blankDescriptionField_throwsException() { 47 | IllegalArgumentException thrown = 48 | assertThrows( 49 | IllegalArgumentException.class, 50 | () -> 51 | MetricSchema.create( 52 | "/name", 53 | "", 54 | "valueDisplayName", 55 | Kind.GAUGE, 56 | ImmutableSet.of())); 57 | assertThat(thrown).hasMessageThat().contains("Description must not be blank"); 58 | } 59 | 60 | @Test 61 | public void testCreate_blankValueDisplayNameField_throwsException() { 62 | IllegalArgumentException thrown = 63 | assertThrows( 64 | IllegalArgumentException.class, 65 | () -> 66 | MetricSchema.create( 67 | "/name", "description", "", Kind.GAUGE, ImmutableSet.of())); 68 | assertThat(thrown).hasMessageThat().contains("Value Display Name must not be empty"); 69 | } 70 | 71 | @Test 72 | public void testCreate_nakedNames_throwsException() { 73 | IllegalArgumentException thrown = 74 | assertThrows( 75 | IllegalArgumentException.class, 76 | () -> 77 | MetricSchema.create( 78 | "foo", 79 | "description", 80 | "valueDisplayName", 81 | Kind.GAUGE, 82 | ImmutableSet.of())); 83 | assertThat(thrown).hasMessageThat().contains("Name must be URL-like and start with a '/'"); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/MutableDistributionTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import com.google.common.collect.ImmutableRangeMap; 21 | import com.google.common.collect.ImmutableSet; 22 | import com.google.common.collect.Range; 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.junit.runners.JUnit4; 27 | 28 | /** Tests for {@link MutableDistribution} */ 29 | @RunWith(JUnit4.class) 30 | public class MutableDistributionTest { 31 | 32 | private MutableDistribution distribution; 33 | @Before 34 | public void setUp() throws Exception { 35 | distribution = new MutableDistribution(CustomFitter.create(ImmutableSet.of(3.0, 5.0))); 36 | } 37 | 38 | @Test 39 | public void testAdd_oneValue() { 40 | distribution.add(5.0); 41 | 42 | assertThat(distribution.count()).isEqualTo(1); 43 | assertThat(distribution.mean()).isWithin(0.0).of(5.0); 44 | assertThat(distribution.sumOfSquaredDeviation()).isWithin(0.0).of(0); 45 | assertThat(distribution.intervalCounts()) 46 | .isEqualTo( 47 | ImmutableRangeMap.builder() 48 | .put(Range.lessThan(3.0), 0L) 49 | .put(Range.closedOpen(3.0, 5.0), 0L) 50 | .put(Range.atLeast(5.0), 1L) 51 | .build()); 52 | } 53 | 54 | @Test 55 | public void testAdd_zero() { 56 | distribution.add(0.0); 57 | 58 | assertThat(distribution.count()).isEqualTo(1); 59 | assertThat(distribution.mean()).isWithin(0.0).of(0.0); 60 | assertThat(distribution.sumOfSquaredDeviation()).isWithin(0.0).of(0); 61 | assertThat(distribution.intervalCounts()) 62 | .isEqualTo( 63 | ImmutableRangeMap.builder() 64 | .put(Range.lessThan(3.0), 1L) 65 | .put(Range.closedOpen(3.0, 5.0), 0L) 66 | .put(Range.atLeast(5.0), 0L) 67 | .build()); 68 | } 69 | 70 | @Test 71 | public void testAdd_multipleOfOneValue() { 72 | distribution.add(4.0, 2); 73 | 74 | assertThat(distribution.count()).isEqualTo(2); 75 | assertThat(distribution.mean()).isWithin(0.0).of(4.0); 76 | assertThat(distribution.sumOfSquaredDeviation()).isWithin(0.0).of(0); 77 | assertThat(distribution.intervalCounts()) 78 | .isEqualTo( 79 | ImmutableRangeMap.builder() 80 | .put(Range.lessThan(3.0), 0L) 81 | .put(Range.closedOpen(3.0, 5.0), 2L) 82 | .put(Range.atLeast(5.0), 0L) 83 | .build()); 84 | } 85 | 86 | @Test 87 | public void testAdd_positiveThenNegativeValue() { 88 | distribution.add(2.0); 89 | distribution.add(-2.0); 90 | 91 | assertThat(distribution.count()).isEqualTo(2); 92 | assertThat(distribution.mean()).isWithin(0.0).of(0.0); 93 | assertThat(distribution.sumOfSquaredDeviation()).isWithin(0.0).of(8.0); 94 | assertThat(distribution.intervalCounts()) 95 | .isEqualTo( 96 | ImmutableRangeMap.builder() 97 | .put(Range.lessThan(3.0), 2L) 98 | .put(Range.closedOpen(3.0, 5.0), 0L) 99 | .put(Range.atLeast(5.0), 0L) 100 | .build()); 101 | } 102 | 103 | @Test 104 | public void testAdd_wideRangeOfValues() { 105 | distribution.add(2.0); 106 | distribution.add(16.0); 107 | distribution.add(128.0, 5); 108 | distribution.add(1024.0, 0); 109 | 110 | assertThat(distribution.count()).isEqualTo(7); 111 | assertThat(distribution.mean()).isWithin(0.0).of(94.0); 112 | assertThat(distribution.sumOfSquaredDeviation()).isWithin(0.0).of(20328.0); 113 | assertThat(distribution.intervalCounts()) 114 | .isEqualTo( 115 | ImmutableRangeMap.builder() 116 | .put(Range.lessThan(3.0), 1L) 117 | .put(Range.closedOpen(3.0, 5.0), 0L) 118 | .put(Range.atLeast(5.0), 6L) 119 | .build()); 120 | } 121 | 122 | @Test 123 | public void testAdd_negativeZero_throwsException() { 124 | IllegalArgumentException thrown = 125 | assertThrows( 126 | IllegalArgumentException.class, 127 | () -> distribution.add(Double.longBitsToDouble(0x80000000))); 128 | assertThat(thrown).hasMessageThat().contains("value must be finite, not NaN, and not -0.0"); 129 | } 130 | 131 | @Test 132 | public void testAdd_NaN_throwsException() { 133 | IllegalArgumentException thrown = 134 | assertThrows(IllegalArgumentException.class, () -> distribution.add(Double.NaN)); 135 | assertThat(thrown).hasMessageThat().contains("value must be finite, not NaN, and not -0.0"); 136 | } 137 | 138 | @Test 139 | public void testAdd_positiveInfinity_throwsException() { 140 | IllegalArgumentException thrown = 141 | assertThrows( 142 | IllegalArgumentException.class, () -> distribution.add(Double.POSITIVE_INFINITY)); 143 | assertThat(thrown).hasMessageThat().contains("value must be finite, not NaN, and not -0.0"); 144 | } 145 | 146 | @Test 147 | public void testAdd_negativeInfinity_throwsException() { 148 | IllegalArgumentException thrown = 149 | assertThrows( 150 | IllegalArgumentException.class, () -> distribution.add(Double.NEGATIVE_INFINITY)); 151 | assertThat(thrown).hasMessageThat().contains("value must be finite, not NaN, and not -0.0"); 152 | } 153 | 154 | @Test 155 | public void testAdd_iteratedFloatingPointValues_hasLowAccumulatedError() { 156 | for (int i = 0; i < 500; i++) { 157 | distribution.add(1 / 3.0); 158 | distribution.add(1 / 7.0); 159 | } 160 | 161 | // Test for nine significant figures of accuracy. 162 | assertThat(distribution.mean()).isWithin(0.000000001).of(5.0 / 21.0); 163 | assertThat(distribution.sumOfSquaredDeviation()) 164 | .isWithin(0.000000001) 165 | .of(1000 * 4.0 / (21.0 * 21.0)); 166 | } 167 | 168 | @Test 169 | public void testAdd_fitterWithNoFiniteIntervals_underflowValue_returnsUnderflowInterval() 170 | throws Exception { 171 | MutableDistribution distribution = 172 | new MutableDistribution(CustomFitter.create(ImmutableSet.of(5.0))); 173 | 174 | distribution.add(3.0); 175 | 176 | assertThat(distribution.intervalCounts()) 177 | .isEqualTo( 178 | ImmutableRangeMap.builder() 179 | .put(Range.lessThan(5.0), 1L) 180 | .put(Range.atLeast(5.0), 0L) 181 | .build()); 182 | } 183 | 184 | @Test 185 | public void testAdd_noFiniteIntervals_overflowValue_returnsOverflowInterval() throws Exception { 186 | MutableDistribution distribution = 187 | new MutableDistribution(CustomFitter.create(ImmutableSet.of(5.0))); 188 | 189 | distribution.add(10.0); 190 | 191 | assertThat(distribution.intervalCounts()) 192 | .isEqualTo( 193 | ImmutableRangeMap.builder() 194 | .put(Range.lessThan(5.0), 0L) 195 | .put(Range.atLeast(5.0), 1L) 196 | .build()); 197 | } 198 | 199 | @Test 200 | public void testAdd_noFiniteIntervals_edgeValue_returnsOverflowInterval() throws Exception { 201 | MutableDistribution distribution = 202 | new MutableDistribution(CustomFitter.create(ImmutableSet.of(2.0))); 203 | 204 | distribution.add(2.0); 205 | 206 | assertThat(distribution.intervalCounts()) 207 | .isEqualTo( 208 | ImmutableRangeMap.builder() 209 | .put(Range.lessThan(2.0), 0L) 210 | .put(Range.atLeast(2.0), 1L) 211 | .build()); 212 | } 213 | 214 | @Test 215 | public void testAdd_oneFiniteInterval_underflowValue_returnsUnderflowInterval() throws Exception { 216 | MutableDistribution distribution = 217 | new MutableDistribution(CustomFitter.create(ImmutableSet.of(1.0, 5.0))); 218 | 219 | distribution.add(0.0); 220 | 221 | assertThat(distribution.intervalCounts()) 222 | .isEqualTo( 223 | ImmutableRangeMap.builder() 224 | .put(Range.lessThan(1.0), 1L) 225 | .put(Range.closedOpen(1.0, 5.0), 0L) 226 | .put(Range.atLeast(5.0), 0L) 227 | .build()); 228 | } 229 | 230 | @Test 231 | public void testAdd_oneFiniteInterval_overflowValue_returnsOverflowInterval() throws Exception { 232 | MutableDistribution distribution = 233 | new MutableDistribution(CustomFitter.create(ImmutableSet.of(1.0, 5.0))); 234 | 235 | distribution.add(10.0); 236 | 237 | assertThat(distribution.intervalCounts()) 238 | .isEqualTo( 239 | ImmutableRangeMap.builder() 240 | .put(Range.lessThan(1.0), 0L) 241 | .put(Range.closedOpen(1.0, 5.0), 0L) 242 | .put(Range.atLeast(5.0), 1L) 243 | .build()); 244 | } 245 | 246 | @Test 247 | public void testAdd_oneFiniteInterval_inBoundsValue_returnsInBoundsInterval() throws Exception { 248 | MutableDistribution distribution = 249 | new MutableDistribution(CustomFitter.create(ImmutableSet.of(1.0, 5.0))); 250 | 251 | distribution.add(3.0); 252 | 253 | assertThat(distribution.intervalCounts()) 254 | .isEqualTo( 255 | ImmutableRangeMap.builder() 256 | .put(Range.lessThan(1.0), 0L) 257 | .put(Range.closedOpen(1.0, 5.0), 1L) 258 | .put(Range.atLeast(5.0), 0L) 259 | .build()); 260 | } 261 | 262 | @Test 263 | public void testAdd_oneFiniteInterval_firstEdgeValue_returnsFiniteInterval() throws Exception { 264 | MutableDistribution distribution = 265 | new MutableDistribution(CustomFitter.create(ImmutableSet.of(1.0, 5.0))); 266 | 267 | distribution.add(1.0); 268 | 269 | assertThat(distribution.intervalCounts()) 270 | .isEqualTo( 271 | ImmutableRangeMap.builder() 272 | .put(Range.lessThan(1.0), 0L) 273 | .put(Range.closedOpen(1.0, 5.0), 1L) 274 | .put(Range.atLeast(5.0), 0L) 275 | .build()); 276 | } 277 | 278 | @Test 279 | public void testAdd_oneFiniteInterval_secondEdgeValue_returnsOverflowInterval() throws Exception { 280 | MutableDistribution distribution = 281 | new MutableDistribution(CustomFitter.create(ImmutableSet.of(1.0, 5.0))); 282 | 283 | distribution.add(5.0); 284 | 285 | assertThat(distribution.intervalCounts()) 286 | .isEqualTo( 287 | ImmutableRangeMap.builder() 288 | .put(Range.lessThan(1.0), 0L) 289 | .put(Range.closedOpen(1.0, 5.0), 0L) 290 | .put(Range.atLeast(5.0), 1L) 291 | .build()); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/StoredMetricTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import com.google.common.collect.ImmutableList; 21 | import com.google.common.collect.ImmutableSet; 22 | import java.time.Instant; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.junit.runners.JUnit4; 26 | 27 | /** Unit tests for {@link StoredMetric}. */ 28 | @RunWith(JUnit4.class) 29 | public class StoredMetricTest { 30 | 31 | @Test 32 | public void testGetCardinality_reflectsCurrentCardinality() { 33 | StoredMetric smallMetric = 34 | new StoredMetric<>("/metric", "description", "vdn", ImmutableSet.of(), Boolean.class); 35 | assertThat(smallMetric.getCardinality()).isEqualTo(0); 36 | 37 | smallMetric.set(true); 38 | 39 | assertThat(smallMetric.getCardinality()).isEqualTo(1); 40 | 41 | StoredMetric dimensionalMetric = 42 | new StoredMetric<>( 43 | "/metric", 44 | "description", 45 | "vdn", 46 | ImmutableSet.of(LabelDescriptor.create("foo", "bar")), 47 | Boolean.class); 48 | 49 | dimensionalMetric.set(true, "test_value1"); 50 | dimensionalMetric.set(true, "test_value2"); 51 | 52 | assertThat(dimensionalMetric.getCardinality()).isEqualTo(2); 53 | } 54 | 55 | @Test 56 | public void testSet_wrongNumberOfLabels_throwsException() { 57 | StoredMetric dimensionalMetric = 58 | new StoredMetric<>( 59 | "/metric", 60 | "description", 61 | "vdn", 62 | ImmutableSet.of( 63 | LabelDescriptor.create("label1", "bar"), LabelDescriptor.create("label2", "bar")), 64 | Boolean.class); 65 | 66 | IllegalArgumentException thrown = 67 | assertThrows(IllegalArgumentException.class, () -> dimensionalMetric.set(true, "foo")); 68 | assertThat(thrown) 69 | .hasMessageThat() 70 | .contains( 71 | "The count of labelValues must be equal to the underlying Metric's count of labels."); 72 | } 73 | 74 | @Test 75 | public void testSet_setsValue() { 76 | StoredMetric metric = 77 | new StoredMetric<>( 78 | "/metric", 79 | "description", 80 | "vdn", 81 | ImmutableSet.of(LabelDescriptor.create("label1", "bar")), 82 | Boolean.class); 83 | 84 | assertThat(metric.getTimestampedValues()).isEmpty(); 85 | 86 | metric.set(true, ImmutableList.of("test_value1")); 87 | assertThat(metric.getTimestampedValues(Instant.ofEpochMilli(1337))) 88 | .containsExactly( 89 | MetricPoint.create( 90 | metric, ImmutableList.of("test_value1"), Instant.ofEpochMilli(1337), true)); 91 | 92 | metric.set(false, ImmutableList.of("test_value1")); 93 | metric.set(true, ImmutableList.of("test_value2")); 94 | assertThat(metric.getTimestampedValues(Instant.ofEpochMilli(1338))) 95 | .containsExactly( 96 | MetricPoint.create( 97 | metric, ImmutableList.of("test_value1"), Instant.ofEpochMilli(1338), false), 98 | MetricPoint.create( 99 | metric, ImmutableList.of("test_value2"), Instant.ofEpochMilli(1338), true)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /metrics/src/test/java/com/google/monitoring/metrics/VirtualMetricTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | 19 | import com.google.common.base.Suppliers; 20 | import com.google.common.collect.ImmutableList; 21 | import com.google.common.collect.ImmutableMap; 22 | import com.google.common.collect.ImmutableSet; 23 | import java.time.Instant; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.junit.runners.JUnit4; 27 | 28 | /** Unit tests for {@link VirtualMetric}. */ 29 | @RunWith(JUnit4.class) 30 | public class VirtualMetricTest { 31 | 32 | private final VirtualMetric metric = 33 | new VirtualMetric<>( 34 | "/metric", 35 | "description", 36 | "vdn", 37 | ImmutableSet.of(LabelDescriptor.create("label1", "bar")), 38 | Suppliers.ofInstance( 39 | ImmutableMap.of( 40 | ImmutableList.of("label_value1"), "value1", 41 | ImmutableList.of("label_value2"), "value2")), 42 | String.class); 43 | 44 | @Test 45 | public void testGetCardinality_afterGetTimestampedValues_returnsLastCardinality() { 46 | metric.getTimestampedValues(); 47 | assertThat(metric.getCardinality()).isEqualTo(2); 48 | } 49 | 50 | @Test 51 | public void testGetCardinality_beforeGetTimestampedValues_returnsZero() { 52 | assertThat(metric.getCardinality()).isEqualTo(0); 53 | } 54 | 55 | @Test 56 | public void testGetTimestampedValues_returnsValues() { 57 | assertThat(metric.getTimestampedValues(Instant.ofEpochMilli(1337))) 58 | .containsExactly( 59 | MetricPoint.create( 60 | metric, ImmutableList.of("label_value1"), Instant.ofEpochMilli(1337), "value1"), 61 | MetricPoint.create( 62 | metric, ImmutableList.of("label_value2"), Instant.ofEpochMilli(1337), "value2")); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | com.google.monitoring-client 7 | monitoring 8 | 1.0.8-SNAPSHOT 9 | pom 10 | 11 | Monitoring Client Library for Java 12 | 13 | Java idiomatic client for metrics monitoring, with Stackdriver backend as an example. 14 | 15 | https://github.com/google/java-monitoring-client-library 16 | 17 | 18 | 1.8 19 | 1.8 20 | 21 | 22 | 23 | 24 | The Apache Software License, Version 2.0 25 | http://www.apache.org/licenses/LICENSE-2.0.txt 26 | 27 | 28 | 29 | 30 | 31 | Greg Shikhman 32 | shikhman@google.com 33 | Google 34 | http://google.com 35 | 36 | 37 | Lai Jiang 38 | jianglai@google.com 39 | Google 40 | http://google.com 41 | 42 | 43 | 44 | 45 | scm:git:https://github.com/google/java-monitoring-client-library.git 46 | scm:git:git@github.com:google/java-monitoring-client-library.git 47 | 48 | https://github.com/google/java-monitoring-client-library 49 | HEAD 50 | 51 | 52 | 53 | 54 | sonatype-nexus-staging 55 | https://oss.sonatype.org/content/repositories/snapshots 56 | 57 | 58 | sonatype-nexus-staging 59 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 60 | 61 | 62 | 63 | 64 | metrics 65 | contrib 66 | stackdriver 67 | 68 | 69 | 70 | 71 | 72 | org.apache.maven.plugins 73 | maven-surefire-plugin 74 | 3.0.0-M4 75 | 76 | 77 | org.apache.maven.plugins 78 | maven-release-plugin 79 | 2.5.3 80 | 81 | v@{project.version} 82 | true 83 | release 84 | 85 | 86 | 87 | org.sonatype.plugins 88 | nexus-staging-maven-plugin 89 | 1.6.8 90 | true 91 | 92 | sonatype-nexus-staging 93 | https://oss.sonatype.org/ 94 | true 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | release 103 | 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-source-plugin 108 | 3.0.1 109 | 110 | 111 | attach-sources 112 | 113 | jar-no-fork 114 | 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-javadoc-plugin 121 | 3.0.0 122 | 123 | 124 | -Xdoclint:all,-missing 125 | 126 | 127 | 128 | 129 | attach-javadocs 130 | 131 | jar 132 | 133 | 134 | 135 | 136 | 137 | org.apache.maven.plugins 138 | maven-gpg-plugin 139 | 1.6 140 | 141 | 142 | sign-artifacts 143 | verify 144 | 145 | sign 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /stackdriver/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.google.monitoring-client 8 | monitoring 9 | 1.0.8-SNAPSHOT 10 | .. 11 | 12 | 13 | stackdriver 14 | Stackdriver Writer 15 | Metrics writer using stackdriver backend. 16 | 17 | jar 18 | 19 | 20 | 21 | 22 | 23 | com.google.guava 24 | guava 25 | 29.0-jre 26 | 27 | 28 | 29 | com.google.apis 30 | google-api-services-monitoring 31 | v3-rev426-1.23.0 32 | 33 | 34 | 35 | com.google.monitoring-client 36 | metrics 37 | 1.0.8-SNAPSHOT 38 | 39 | 40 | 41 | 42 | 43 | junit 44 | junit 45 | 4.13.1 46 | test 47 | 48 | 49 | 50 | org.mockito 51 | mockito-all 52 | 2.0.2-beta 53 | test 54 | 55 | 56 | 57 | com.google.truth 58 | truth 59 | 0.44 60 | test 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /stackdriver/src/test/java/com/google/monitoring/metrics/example/SheepCounterExample.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics.example; 16 | 17 | import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; 18 | import com.google.api.client.http.HttpTransport; 19 | import com.google.api.client.http.javanet.NetHttpTransport; 20 | import com.google.api.client.json.JsonFactory; 21 | import com.google.api.client.json.jackson2.JacksonFactory; 22 | import com.google.api.services.monitoring.v3.Monitoring; 23 | import com.google.api.services.monitoring.v3.MonitoringScopes; 24 | import com.google.api.services.monitoring.v3.model.MonitoredResource; 25 | import com.google.common.collect.ImmutableList; 26 | import com.google.common.collect.ImmutableMap; 27 | import com.google.common.collect.ImmutableSet; 28 | import com.google.monitoring.metrics.EventMetric; 29 | import com.google.monitoring.metrics.IncrementableMetric; 30 | import com.google.monitoring.metrics.LabelDescriptor; 31 | import com.google.monitoring.metrics.LinearFitter; 32 | import com.google.monitoring.metrics.Metric; 33 | import com.google.monitoring.metrics.MetricRegistryImpl; 34 | import com.google.monitoring.metrics.MetricReporter; 35 | import com.google.monitoring.metrics.MetricWriter; 36 | import com.google.monitoring.metrics.SettableMetric; 37 | import com.google.monitoring.metrics.stackdriver.StackdriverWriter; 38 | import java.io.IOException; 39 | import java.util.Random; 40 | import java.util.concurrent.Executors; 41 | import java.util.logging.Handler; 42 | import java.util.logging.Level; 43 | import java.util.logging.LogManager; 44 | import java.util.logging.Logger; 45 | 46 | /** A sample application which uses the Metrics API to count sheep while sleeping. */ 47 | public final class SheepCounterExample { 48 | 49 | /* 50 | * The code below for using a custom {@link LogManager} is only necessary to enable logging at JVM 51 | * shutdown to show the shutdown logs of {@link MetricReporter} in this small standalone 52 | * application. 53 | * 54 | *

It is NOT necessary for normal use of the Metrics library. 55 | */ 56 | static { 57 | // must be called before any Logger method is used. 58 | System.setProperty("java.util.logging.manager", DelayedShutdownLogManager.class.getName()); 59 | } 60 | 61 | private static final Logger logger = Logger.getLogger(SheepCounterExample.class.getName()); 62 | 63 | /** 64 | * The time interval, in seconds, between when the {@link MetricReporter} will read {@link Metric} 65 | * instances and enqueue them to the {@link MetricWriter}. 66 | * 67 | * @see MetricReporter 68 | */ 69 | private static final long METRICS_REPORTING_INTERVAL = 30L; 70 | 71 | /** 72 | * The maximum queries per second to the Stackdriver API. Contact Cloud Support to raise this from 73 | * the default value if necessary. 74 | */ 75 | private static final int STACKDRIVER_MAX_QPS = 30; 76 | 77 | /** 78 | * The maximum number of {@link com.google.api.services.monitoring.v3.model.TimeSeries} that can 79 | * be bundled into a single {@link 80 | * com.google.api.services.monitoring.v3.model.CreateTimeSeriesRequest}. This must be at most 200. 81 | * Setting this lower will cause the {@link StackdriverWriter} to {@link 82 | * StackdriverWriter#flush()} more frequently. 83 | */ 84 | private static final int STACKDRIVER_MAX_POINTS_PER_REQUEST = 200; 85 | 86 | // Create some metrics to track your ZZZs. 87 | private static final ImmutableList SHEEP_COLORS = 88 | ImmutableList.of("Green", "Yellow", "Red", "Blue"); 89 | private static final ImmutableList SHEEP_SPECIES = 90 | ImmutableList.of("Domestic", "Bighorn"); 91 | private static final ImmutableSet SHEEP_ATTRIBUTES = 92 | ImmutableSet.of( 93 | LabelDescriptor.create("color", "Sheep Color"), 94 | LabelDescriptor.create("species", "Sheep Species")); 95 | 96 | /** 97 | * Counters are good for tracking monotonically increasing values, like request counts or error 98 | * counts. Or, in this case, sheep. 99 | */ 100 | private static final IncrementableMetric sheepCounter = 101 | MetricRegistryImpl.getDefault() 102 | .newIncrementableMetric( 103 | "/sheep", "Counts sheep over time.", "Number of Sheep", SHEEP_ATTRIBUTES); 104 | 105 | /** 106 | * Settable metrics are good for state indicators. For example, you could use one to track the 107 | * lifecycle of a {@link com.google.common.util.concurrent.Service}. In this case, we are just 108 | * using it to track the sleep state of this application. 109 | */ 110 | private static final SettableMetric isSleeping = 111 | MetricRegistryImpl.getDefault() 112 | .newSettableMetric( 113 | "/is_sleeping", 114 | "Tracks sleep state.", 115 | "Sleeping?", 116 | ImmutableSet.of(), 117 | Boolean.class); 118 | 119 | /** 120 | * Gauge metrics never need to be accessed, so the assignment here is unnecessary. You only need 121 | * it if you plan on calling {@link Metric#getTimestampedValues()} to read the values of the 122 | * metric in the code yourself. 123 | */ 124 | @SuppressWarnings("unused") 125 | private static final Metric sleepQuality = 126 | MetricRegistryImpl.getDefault() 127 | .newGauge( 128 | "/sleep_quality", 129 | "Quality of the sleep.", 130 | "Quality", 131 | ImmutableSet.of(), 132 | () -> ImmutableMap.of(ImmutableList.of(), new Random().nextDouble()), 133 | Double.class); 134 | 135 | /** 136 | * Event metrics track aspects of an "event." Here, we track the fluffiness of the sheep we've 137 | * seen. 138 | */ 139 | private static final EventMetric sheepFluffiness = 140 | MetricRegistryImpl.getDefault() 141 | .newEventMetric( 142 | "/sheep_fluffiness", 143 | "Measures the fluffiness of seen sheep.", 144 | "Fill Power", 145 | SHEEP_ATTRIBUTES, 146 | LinearFitter.create(5, 20.0, 20.0)); 147 | 148 | private static Monitoring createAuthorizedMonitoringClient() throws IOException { 149 | // Grab the Application Default Credentials from the environment. 150 | // Generate these with 'gcloud beta auth application-default login' 151 | GoogleCredential credential = 152 | GoogleCredential.getApplicationDefault().createScoped(MonitoringScopes.all()); 153 | 154 | // Create and return the CloudMonitoring service object 155 | HttpTransport httpTransport = new NetHttpTransport(); 156 | JsonFactory jsonFactory = new JacksonFactory(); 157 | return new Monitoring.Builder(httpTransport, jsonFactory, credential) 158 | .setApplicationName("Monitoring Sample") 159 | .build(); 160 | } 161 | 162 | public static void main(String[] args) throws Exception { 163 | if (args.length < 1) { 164 | System.err.println("Missing required project argument"); 165 | System.err.printf( 166 | "Usage: java %s gcp-project-id [verbose]\n", SheepCounterExample.class.getName()); 167 | return; 168 | } 169 | String project = args[0]; 170 | 171 | // Turn up the logging verbosity 172 | if (args.length > 1) { 173 | Logger log = LogManager.getLogManager().getLogger(""); 174 | log.setLevel(Level.ALL); 175 | for (Handler h : log.getHandlers()) { 176 | h.setLevel(Level.ALL); 177 | } 178 | } 179 | 180 | // Create a sample resource. In this case, a GCE Instance. 181 | // See https://cloud.google.com/monitoring/api/resources for a list of resource types. 182 | MonitoredResource monitoredResource = 183 | new MonitoredResource() 184 | .setType("gce_instance") 185 | .setLabels( 186 | ImmutableMap.of( 187 | "instance_id", "test-instance", 188 | "zone", "us-central1-f")); 189 | 190 | // Set up the Metrics infrastructure. 191 | MetricWriter stackdriverWriter = 192 | new StackdriverWriter( 193 | createAuthorizedMonitoringClient(), 194 | project, 195 | monitoredResource, 196 | STACKDRIVER_MAX_QPS, 197 | STACKDRIVER_MAX_POINTS_PER_REQUEST); 198 | final MetricReporter reporter = 199 | new MetricReporter( 200 | stackdriverWriter, METRICS_REPORTING_INTERVAL, Executors.defaultThreadFactory()); 201 | reporter.startAsync().awaitRunning(); 202 | 203 | // Set up a handler to stop sleeping on SIGINT. 204 | Runtime.getRuntime() 205 | .addShutdownHook( 206 | new Thread( 207 | () -> { 208 | reporter.stopAsync().awaitTerminated(); 209 | // Allow the LogManager to cleanup the loggers. 210 | DelayedShutdownLogManager.resetFinally(); 211 | })); 212 | 213 | System.err.println("Send SIGINT (Ctrl+C) to stop sleeping."); 214 | while (true) { 215 | // Count some Googley sheep. 216 | int colorIndex = new Random().nextInt(SHEEP_COLORS.size()); 217 | int speciesIndex = new Random().nextInt(SHEEP_SPECIES.size()); 218 | sheepCounter.incrementBy(1, SHEEP_COLORS.get(colorIndex), SHEEP_SPECIES.get(speciesIndex)); 219 | sheepFluffiness.record( 220 | new Random().nextDouble() * 200, 221 | SHEEP_COLORS.get(colorIndex), 222 | SHEEP_SPECIES.get(speciesIndex)); 223 | isSleeping.set(true); 224 | 225 | logger.info("zzz..."); 226 | Thread.sleep(5000); 227 | } 228 | } 229 | 230 | /** 231 | * Special {@link LogManager} with a no-op {@link LogManager#reset()} so that logging can proceed 232 | * as usual until stopped in in another runtime shutdown hook. 233 | * 234 | *

The class is marked public because it is loaded by the JVM classloader at runtime. 235 | */ 236 | @SuppressWarnings("WeakerAccess") 237 | public static class DelayedShutdownLogManager extends LogManager { 238 | 239 | private static DelayedShutdownLogManager instance; 240 | 241 | public DelayedShutdownLogManager() { 242 | instance = this; 243 | } 244 | 245 | /** A no-op implementation. */ 246 | @Override 247 | public void reset() { 248 | /* don't reset yet. */ 249 | } 250 | 251 | static void resetFinally() { 252 | instance.delayedReset(); 253 | } 254 | 255 | private void delayedReset() { 256 | super.reset(); 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /stackdriver/src/test/java/com/google/monitoring/metrics/stackdriver/GoogleJsonResponseExceptionHelper.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.monitoring.metrics.stackdriver; 16 | 17 | import com.google.api.client.googleapis.json.GoogleJsonResponseException; 18 | import com.google.api.client.http.GenericUrl; 19 | import com.google.api.client.http.HttpContent; 20 | import com.google.api.client.http.HttpRequest; 21 | import com.google.api.client.http.HttpRequestFactory; 22 | import com.google.api.client.http.HttpResponse; 23 | import com.google.api.client.http.HttpTransport; 24 | import com.google.api.client.http.LowLevelHttpRequest; 25 | import com.google.api.client.http.LowLevelHttpResponse; 26 | import com.google.api.client.json.jackson2.JacksonFactory; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.io.OutputStream; 30 | 31 | /** A helper to create instances of {@link GoogleJsonResponseException}. */ 32 | public class GoogleJsonResponseExceptionHelper { 33 | /** 34 | * @param statusCode the status code that should be in the returned {@link 35 | * GoogleJsonResponseException} 36 | * @return a {@link GoogleJsonResponseException} with the status code {@code statusCode} 37 | * @throws IOException shouldn't occur 38 | */ 39 | public static GoogleJsonResponseException create(int statusCode) throws IOException { 40 | HttpResponse response = createHttpResponse(statusCode, null); 41 | return GoogleJsonResponseException.from(new JacksonFactory(), response); 42 | } 43 | 44 | public static HttpResponse createHttpResponse(int statusCode, InputStream content) 45 | throws IOException { 46 | FakeHttpTransport transport = new FakeHttpTransport(statusCode, content); 47 | HttpRequestFactory factory = transport.createRequestFactory(); 48 | HttpRequest request = 49 | factory.buildRequest( 50 | "foo", new GenericUrl("http://example.com/bar"), new EmptyHttpContent()); 51 | request.setThrowExceptionOnExecuteError(false); 52 | return request.execute(); 53 | } 54 | 55 | private static class FakeHttpTransport extends HttpTransport { 56 | private final int statusCode; 57 | private final InputStream content; 58 | 59 | FakeHttpTransport(int statusCode, InputStream content) { 60 | this.statusCode = statusCode; 61 | this.content = content; 62 | } 63 | 64 | @Override 65 | protected LowLevelHttpRequest buildRequest(String method, String url) throws IOException { 66 | return new FakeLowLevelHttpRequest(statusCode, content); 67 | } 68 | } 69 | 70 | private static class FakeLowLevelHttpRequest extends LowLevelHttpRequest { 71 | private final int statusCode; 72 | private final InputStream content; 73 | 74 | FakeLowLevelHttpRequest(int statusCode, InputStream content) { 75 | this.statusCode = statusCode; 76 | this.content = content; 77 | } 78 | 79 | @Override 80 | public void addHeader(String name, String value) throws IOException { 81 | // Nothing! 82 | } 83 | 84 | @Override 85 | public LowLevelHttpResponse execute() throws IOException { 86 | return new FakeLowLevelHttpResponse(statusCode, content); 87 | } 88 | } 89 | 90 | private static class FakeLowLevelHttpResponse extends LowLevelHttpResponse { 91 | private final int statusCode; 92 | private final InputStream content; 93 | 94 | FakeLowLevelHttpResponse(int statusCode, InputStream content) { 95 | this.statusCode = statusCode; 96 | this.content = content; 97 | } 98 | 99 | @Override 100 | public InputStream getContent() throws IOException { 101 | return content; 102 | } 103 | 104 | @Override 105 | public String getContentEncoding() throws IOException { 106 | return null; 107 | } 108 | 109 | @Override 110 | public long getContentLength() throws IOException { 111 | return 0; 112 | } 113 | 114 | @Override 115 | public String getContentType() throws IOException { 116 | return "text/json"; 117 | } 118 | 119 | @Override 120 | public String getStatusLine() throws IOException { 121 | return null; 122 | } 123 | 124 | @Override 125 | public int getStatusCode() throws IOException { 126 | return statusCode; 127 | } 128 | 129 | @Override 130 | public String getReasonPhrase() throws IOException { 131 | return null; 132 | } 133 | 134 | @Override 135 | public int getHeaderCount() throws IOException { 136 | return 0; 137 | } 138 | 139 | @Override 140 | public String getHeaderName(int index) throws IOException { 141 | return null; 142 | } 143 | 144 | @Override 145 | public String getHeaderValue(int index) throws IOException { 146 | return null; 147 | } 148 | } 149 | 150 | private static class EmptyHttpContent implements HttpContent { 151 | @Override 152 | public long getLength() throws IOException { 153 | return 0; 154 | } 155 | 156 | @Override 157 | public String getType() { 158 | return "text/json"; 159 | } 160 | 161 | @Override 162 | public boolean retrySupported() { 163 | return false; 164 | } 165 | 166 | @Override 167 | public void writeTo(OutputStream out) throws IOException { 168 | // Nothing! 169 | } 170 | } 171 | } 172 | --------------------------------------------------------------------------------