Skip to content

Commit c00e1d1

Browse files
committed
Added Histogram Metric Type
1 parent d8ae46b commit c00e1d1

File tree

8 files changed

+546
-4
lines changed

8 files changed

+546
-4
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: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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 extends Statistics {
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+
42+
this.max = Collections.max(values);
43+
this.min = Collections.min(values);
44+
this.count = counts.stream().mapToInt(Integer::intValue).sum();
45+
this.sum = 0d;
46+
for (int i = 0; i < counts.size(); i++) {
47+
this.sum += values.get(i) * counts.get(i);
48+
}
49+
this.counts = counts;
50+
this.values = values;
51+
}
52+
53+
Histogram() {
54+
count = 0;
55+
sum = 0.;
56+
values = new ArrayList<>();
57+
counts = new ArrayList<>();
58+
};
59+
60+
@JsonProperty("Values")
61+
public List<Double> values;
62+
63+
@JsonProperty("Counts")
64+
public List<Integer> counts;
65+
66+
@JsonIgnore private boolean reduced = false;
67+
68+
@JsonIgnore private static final double EPSILON = 0.1;
69+
@JsonIgnore private static final double BIN_SIZE = Math.log(1 + EPSILON);
70+
@JsonIgnore private final Map<Double, Integer> buckets = new HashMap<>();
71+
72+
/**
73+
* @param value the value to add to the histogram
74+
* @throws InvalidMetricException if adding this value would increase the number of bins in the
75+
* histogram to more than {@value Constants#MAX_DATAPOINTS_PER_METRIC}
76+
* @see Constants#MAX_DATAPOINTS_PER_METRIC
77+
*/
78+
@Override
79+
void addValue(double value) throws InvalidMetricException {
80+
reduced = false;
81+
super.addValue(value);
82+
83+
double bucket = getBucket(value);
84+
if (buckets.size() == Constants.MAX_DATAPOINTS_PER_METRIC && !buckets.containsKey(bucket)) {
85+
throw new InvalidMetricException(
86+
String.format(
87+
"Adding this value increases the number of bins in this histogram to %d"
88+
+ ", CloudWatch will drop any Histogram metrics with more than %d bins",
89+
buckets.size() + 1, Constants.MAX_DATAPOINTS_PER_METRIC));
90+
}
91+
// Add the value to the appropriate bucket (or create a new bucket if necessary
92+
buckets.compute(
93+
bucket,
94+
(k, v) -> {
95+
if (v == null) {
96+
return 1;
97+
} else {
98+
return v + 1;
99+
}
100+
});
101+
}
102+
103+
/**
104+
* Updates the Values and Counts lists to represent the buckets of this histogram.
105+
*
106+
* @return the reduced histogram
107+
*/
108+
Histogram reduce() {
109+
if (reduced) {
110+
return this;
111+
}
112+
113+
this.values = new ArrayList<>(buckets.size());
114+
this.counts = new ArrayList<>(buckets.size());
115+
116+
for (Map.Entry<Double, Integer> entry : buckets.entrySet()) {
117+
this.values.add(entry.getKey());
118+
this.counts.add(entry.getValue());
119+
}
120+
121+
reduced = true;
122+
return this;
123+
}
124+
125+
/**
126+
* Gets the value of the bucket for the given value.
127+
*
128+
* @param value the value to find the closest bucket for
129+
* @return the value of the bucket the given value goes in
130+
*/
131+
private static double getBucket(double value) {
132+
short index = (short) Math.floor(Math.log(value) / BIN_SIZE);
133+
return Math.exp((index + 0.5) * BIN_SIZE);
134+
}
135+
136+
@Override
137+
public boolean equals(Object o) {
138+
if (this == o) return true;
139+
if (o == null || getClass() != o.getClass()) return false;
140+
Histogram that = (Histogram) o;
141+
return count == that.count
142+
&& that.sum.equals(sum)
143+
&& that.max.equals(max)
144+
&& that.min.equals(min)
145+
&& buckets == that.buckets;
146+
}
147+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
// Histograms will be rejected from CWL if they have more than
52+
// Constants.MAX_DATAPOINTS_PER_METRIC number of bins. Unlike MetricDefinition histograms
53+
// cannot be broken into multiple messages therefore an error is raised to let users know
54+
// their message won't be sent otherwise only this metric will be sent
55+
if (isOversized()) {
56+
throw new InvalidMetricException(
57+
String.format(
58+
"Histogram metric, %s, has %d values which exceeds the maximum amount "
59+
+ "of bins allowed, %d, and Histograms cannot be broken into "
60+
+ "multiple metrics therefore it will not be published",
61+
name, values.values.size(), Constants.MAX_DATAPOINTS_PER_METRIC));
62+
}
63+
LinkedList<Metric> metrics = new LinkedList<>();
64+
metrics.offer(this);
65+
return metrics;
66+
}
67+
68+
@Override
69+
protected boolean isOversized() {
70+
return values.values.size() > Constants.MAX_DATAPOINTS_PER_METRIC;
71+
}
72+
73+
@Override
74+
public boolean hasValidValues() {
75+
return values != null && values.count > 0 && !isOversized();
76+
}
77+
78+
public static HistogramMetricBuilder builder() {
79+
return new HistogramMetricBuilder();
80+
}
81+
82+
public static class HistogramMetricBuilder
83+
extends Metric.MetricBuilder<Histogram, HistogramMetricBuilder> {
84+
85+
@Override
86+
protected HistogramMetricBuilder getThis() {
87+
return this;
88+
}
89+
90+
public HistogramMetricBuilder() {
91+
this.values = new Histogram();
92+
}
93+
94+
@Override
95+
public Histogram getValues() {
96+
return values.reduce();
97+
}
98+
99+
@Override
100+
public HistogramMetricBuilder addValue(double value) {
101+
this.values.addValue(value);
102+
return this;
103+
}
104+
105+
@Override
106+
public HistogramMetric build() {
107+
values.reduce();
108+
if (name == null) {
109+
return new HistogramMetric(unit, storageResolution, values);
110+
}
111+
return new HistogramMetric(name, unit, storageResolution, values);
112+
}
113+
}
114+
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,20 @@
1818

1919
import com.fasterxml.jackson.annotation.JsonIgnore;
2020
import com.fasterxml.jackson.annotation.JsonProperty;
21-
import java.util.*;
21+
import java.util.ArrayList;
22+
import java.util.Arrays;
23+
import java.util.Collection;
24+
import java.util.Collections;
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.Set;
2228
import java.util.concurrent.ConcurrentHashMap;
2329
import java.util.stream.Collectors;
24-
import lombok.*;
30+
import lombok.AccessLevel;
31+
import lombok.AllArgsConstructor;
32+
import lombok.Getter;
33+
import lombok.Setter;
34+
import lombok.With;
2535
import software.amazon.cloudwatchlogs.emf.exception.DimensionSetExceededException;
2636
import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException;
2737

@@ -97,6 +107,9 @@ void putMetric(
97107
case STATISTIC_SET:
98108
builder = StatisticSet.builder();
99109
break;
110+
case HISTOGRAM:
111+
builder = HistogramMetric.builder();
112+
break;
100113
case LIST:
101114
default:
102115
builder = MetricDefinition.builder();

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)