├── .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 | [](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 extends Number> 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.