Skip to content

Commit 6a31354

Browse files
committed
Added Histogram Metric Type
1 parent d8ae46b commit 6a31354

File tree

8 files changed

+560
-2
lines changed

8 files changed

+560
-2
lines changed

src/main/java/software/amazon/cloudwatchlogs/emf/model/AggregationType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
public enum AggregationType {
2020
LIST(0),
2121
STATISTIC_SET(1),
22+
HISTOGRAM(2),
2223
UNKNOWN_TO_SDK_VERSION(-1);
2324

2425
private final int value;
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/* Copyright Amazon.com, Inc. or its affiliates. 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+
16+
package software.amazon.cloudwatchlogs.emf.model;
17+
18+
import com.fasterxml.jackson.annotation.JsonIgnore;
19+
import com.fasterxml.jackson.annotation.JsonProperty;
20+
import java.util.ArrayList;
21+
import java.util.Collections;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
import software.amazon.cloudwatchlogs.emf.Constants;
26+
import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException;
27+
28+
/** Histogram metric type */
29+
class Histogram {
30+
Histogram(List<Double> values, List<Integer> counts) {
31+
if (counts.size() != values.size()) {
32+
throw new IllegalArgumentException("Counts and values must have the same size");
33+
}
34+
35+
if (counts.size() > Constants.MAX_DATAPOINTS_PER_METRIC) {
36+
throw new InvalidMetricException(
37+
String.format(
38+
"Histogram provided with %d bins but CloudWatch will drop Histograms with more than %d bins",
39+
counts.size(), Constants.MAX_DATAPOINTS_PER_METRIC));
40+
}
41+
this.max = Collections.max(values);
42+
this.min = Collections.min(values);
43+
this.count = counts.stream().mapToInt(Integer::intValue).sum();
44+
this.sum = 0d;
45+
for (int i = 0; i < counts.size(); i++) {
46+
this.sum += values.get(i) * counts.get(i);
47+
}
48+
this.counts = counts;
49+
this.values = values;
50+
}
51+
52+
Histogram() {
53+
count = 0;
54+
sum = 0.;
55+
values = new ArrayList<>();
56+
counts = new ArrayList<>();
57+
};
58+
59+
@JsonProperty("Max")
60+
public Double max;
61+
62+
@JsonProperty("Min")
63+
public Double min;
64+
65+
@JsonProperty("Count")
66+
public int count;
67+
68+
@JsonProperty("Sum")
69+
public Double sum;
70+
71+
@JsonProperty("Values")
72+
public List<Double> values;
73+
74+
@JsonProperty("Counts")
75+
public List<Integer> counts;
76+
77+
@JsonIgnore private boolean reduced = false;
78+
79+
@JsonIgnore private static final double EPSILON = 0.1;
80+
@JsonIgnore private static final double BIN_SIZE = Math.log(1 + EPSILON);
81+
@JsonIgnore private Map<Double, Integer> buckets = new HashMap<>();
82+
83+
/**
84+
* @param value the value to add to the histogram
85+
* @throws InvalidMetricException if adding this value would increase the number of bins in the
86+
* histogram to more than {@value Constants#MAX_DATAPOINTS_PER_METRIC}
87+
* @see Constants#MAX_DATAPOINTS_PER_METRIC
88+
*/
89+
void addValue(double value) throws InvalidMetricException {
90+
reduced = false;
91+
92+
count++;
93+
sum += value;
94+
if (max == null || value > max) {
95+
max = value;
96+
}
97+
if (min == null || value < min) {
98+
min = value;
99+
}
100+
101+
double bucket = getBucket(value);
102+
if (buckets.size() == Constants.MAX_DATAPOINTS_PER_METRIC && !buckets.containsKey(bucket)) {
103+
throw new InvalidMetricException(
104+
String.format(
105+
"Adding this value increases the number of bins in this histogram to %d"
106+
+ ", CloudWatch will drop any Histogram metrics with more than %d bins",
107+
buckets.size() + 1, Constants.MAX_DATAPOINTS_PER_METRIC));
108+
}
109+
// Add the value to the appropriate bucket (or create a new bucket if necessary
110+
buckets.compute(
111+
bucket,
112+
(k, v) -> {
113+
if (v == null) {
114+
return 1;
115+
} else {
116+
return v + 1;
117+
}
118+
});
119+
}
120+
121+
/**
122+
* Updates the Values and Counts lists to represent the buckets of this histogram.
123+
*
124+
* @return the reduced histogram
125+
*/
126+
Histogram reduce() {
127+
if (reduced) {
128+
return this;
129+
}
130+
131+
this.values = new ArrayList<>(buckets.size());
132+
this.counts = new ArrayList<>(buckets.size());
133+
134+
for (Map.Entry<Double, Integer> entry : buckets.entrySet()) {
135+
this.values.add(entry.getKey());
136+
this.counts.add(entry.getValue());
137+
}
138+
139+
reduced = true;
140+
return this;
141+
}
142+
143+
/**
144+
* Gets the value of the bucket for the given value.
145+
*
146+
* @param value the value to find the closest bucket for
147+
* @return the value of the bucket the given value goes in
148+
*/
149+
private static double getBucket(double value) {
150+
short index = (short) Math.floor(Math.log(value) / BIN_SIZE);
151+
return Math.exp((index + 0.5) * BIN_SIZE);
152+
}
153+
154+
@Override
155+
public boolean equals(Object o) {
156+
if (this == o) return true;
157+
if (o == null || getClass() != o.getClass()) return false;
158+
Histogram that = (Histogram) o;
159+
return count == that.count
160+
&& that.sum.equals(sum)
161+
&& that.max.equals(max)
162+
&& that.min.equals(min)
163+
&& buckets == that.buckets;
164+
}
165+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package software.amazon.cloudwatchlogs.emf.model;
18+
19+
import java.util.LinkedList;
20+
import java.util.List;
21+
import software.amazon.cloudwatchlogs.emf.Constants;
22+
import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException;
23+
24+
/** Represents the Histogram of the EMF schema. */
25+
public class HistogramMetric extends Metric<Histogram> {
26+
27+
HistogramMetric(
28+
Unit unit,
29+
StorageResolution storageResolution,
30+
List<Double> values,
31+
List<Integer> counts) {
32+
this(unit, storageResolution, new Histogram(values, counts));
33+
}
34+
35+
protected HistogramMetric(
36+
String name, Unit unit, StorageResolution storageResolution, Histogram histogram) {
37+
this.unit = unit;
38+
this.storageResolution = storageResolution;
39+
this.values = histogram;
40+
this.name = name;
41+
}
42+
43+
HistogramMetric(Unit unit, StorageResolution storageResolution, Histogram histogram) {
44+
this.unit = unit;
45+
this.storageResolution = storageResolution;
46+
this.values = histogram;
47+
}
48+
49+
@Override
50+
protected LinkedList<Metric> serialize() throws InvalidMetricException {
51+
if (isOversized()) {
52+
throw new InvalidMetricException(
53+
String.format(
54+
"Histogram metric, %s, has %d values which exceeds the maximum amount "
55+
+ "of bins allowed, %d, and Histograms cannot be broken into "
56+
+ "multiple metrics therefore it will not be published",
57+
name, values.values.size(), Constants.MAX_DATAPOINTS_PER_METRIC));
58+
}
59+
LinkedList<Metric> metrics = new LinkedList<>();
60+
metrics.offer(this);
61+
return metrics;
62+
}
63+
64+
@Override
65+
protected boolean isOversized() {
66+
return values.values.size() > Constants.MAX_DATAPOINTS_PER_METRIC;
67+
}
68+
69+
@Override
70+
public boolean hasValidValues() {
71+
return values != null && values.count > 0 && !isOversized();
72+
}
73+
74+
public static HistogramMetricBuilder builder() {
75+
return new HistogramMetricBuilder();
76+
}
77+
78+
public static class HistogramMetricBuilder
79+
extends Metric.MetricBuilder<Histogram, HistogramMetricBuilder> {
80+
81+
@Override
82+
protected HistogramMetricBuilder getThis() {
83+
return this;
84+
}
85+
86+
public HistogramMetricBuilder() {
87+
this.values = new Histogram();
88+
}
89+
90+
@Override
91+
public Histogram getValues() {
92+
return values.reduce();
93+
}
94+
95+
@Override
96+
public HistogramMetricBuilder addValue(double value) {
97+
this.values.addValue(value);
98+
return this;
99+
}
100+
101+
@Override
102+
public HistogramMetric build() {
103+
values.reduce();
104+
if (name == null) {
105+
return new HistogramMetric(unit, storageResolution, values);
106+
}
107+
return new HistogramMetric(name, unit, storageResolution, values);
108+
}
109+
}
110+
}

src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricDirective.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ void putMetric(
9797
case STATISTIC_SET:
9898
builder = StatisticSet.builder();
9999
break;
100+
case HISTOGRAM:
101+
builder = HistogramMetric.builder();
102+
break;
100103
case LIST:
101104
default:
102105
builder = MetricDefinition.builder();
@@ -129,6 +132,18 @@ void setMetric(String key, Metric value) {
129132
metrics.put(key, value);
130133
}
131134

135+
/**
136+
* Sets a metric to the given value. If a metric with the same name already exists, it will be
137+
* overwritten.
138+
*
139+
* @param key the name of the metric
140+
* @param value the value of the metric
141+
*/
142+
void setMetric(String key, HistogramMetric value) {
143+
value.setName(key);
144+
metrics.put(key, value);
145+
}
146+
132147
@JsonProperty("Metrics")
133148
Collection<Metric> getAllMetrics() {
134149
return metrics.values();

src/main/java/software/amazon/cloudwatchlogs/emf/model/MetricsContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ public MetricsContext createCopyWithContext(boolean preserveDimensions) {
432432
* @return the serialized strings.
433433
* @throws JsonProcessingException if there's any object that cannot be serialized
434434
*/
435-
public List<String> serialize() throws JsonProcessingException {
435+
public List<String> serialize() throws JsonProcessingException, InvalidMetricException {
436436
if (rootNode.metrics().size() <= Constants.MAX_METRICS_PER_EVENT
437437
&& !anyMetricWithTooManyDataPoints(rootNode)) {
438438
return Arrays.asList(this.rootNode.serialize());

0 commit comments

Comments
 (0)