Skip to content

Commit bbf6d3e

Browse files
committed
Implemented support for histogram metrics
1 parent 8a0eaff commit bbf6d3e

File tree

4 files changed

+139
-6
lines changed

4 files changed

+139
-6
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package io.opentelemetry.contrib.generator.telemetry.metrics;
2+
3+
import io.opentelemetry.contrib.generator.core.jel.ExpressionProcessor;
4+
import io.opentelemetry.contrib.generator.telemetry.metrics.dto.MetricDefinition;
5+
import io.opentelemetry.contrib.generator.telemetry.misc.GeneratorUtils;
6+
import io.opentelemetry.proto.metrics.v1.*;
7+
import lombok.extern.slf4j.Slf4j;
8+
9+
import java.util.ArrayList;
10+
import java.util.Collections;
11+
import java.util.List;
12+
import java.util.concurrent.TimeUnit;
13+
14+
/**
15+
* Class to generate metric values of Histogram type.
16+
*/
17+
@Slf4j
18+
public class HistogramGenerator {
19+
20+
private final ExpressionProcessor jelProcessor;
21+
22+
public HistogramGenerator(ExpressionProcessor jelProcessor) {
23+
this.jelProcessor = jelProcessor;
24+
}
25+
26+
public Metric.Builder getOTelMetric(MetricDefinition metricDefinition) {
27+
return Metric.newBuilder()
28+
.setName(metricDefinition.getName())
29+
.setUnit(metricDefinition.getUnit())
30+
.setHistogram(getHistogramDataPoint(metricDefinition));
31+
32+
}
33+
34+
private Histogram getHistogramDataPoint(MetricDefinition metricDefinition) {
35+
long[] times = GeneratorUtils.normalizeTimestamp(TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()),
36+
metricDefinition.getPayloadFrequencySeconds());
37+
List<Double> values = getCountValues(metricDefinition);
38+
double sum = values.stream().mapToDouble(Double::doubleValue).sum();
39+
int count = (values.size());
40+
return Histogram.newBuilder()
41+
.setAggregationTemporality(metricDefinition.getAggregationTemporality())
42+
.addDataPoints(HistogramDataPoint.newBuilder()
43+
.setCount(count)
44+
.setSum(sum)
45+
.setStartTimeUnixNano(times[0])
46+
.setTimeUnixNano(times[1])
47+
.addAllExplicitBounds(metricDefinition.getBounds())
48+
.addAllBucketCounts(getBucketCounts(metricDefinition, values))
49+
.addAllAttributes(GeneratorUtils.getEvaluatedAttributes(jelProcessor, metricDefinition.getAttributes()))
50+
.build())
51+
.build();
52+
}
53+
54+
private List<Double> getCountValues(MetricDefinition metricDefinition) {
55+
Object value = jelProcessor.eval(metricDefinition.getValueFunction());
56+
List<Object> rawValues = value instanceof List<?> ? (List<Object>) value : Collections.nCopies(5, value);
57+
return rawValues.stream().map(val -> Double.parseDouble(val.toString())).toList();
58+
}
59+
60+
private List<Long> getBucketCounts(MetricDefinition metricDefinition, List<Double> values) {
61+
List<Long> bucketCounts = new ArrayList<>();
62+
for (int i = 0; i < metricDefinition.getBounds().size(); i++) {
63+
double lowerBound = i == 0 ? Double.MIN_VALUE : metricDefinition.getBounds().get(i - 1) + 1;
64+
double upperBound = i == metricDefinition.getBounds().size() - 1 ? Double.MAX_VALUE :
65+
metricDefinition.getBounds().get(i);
66+
long count = countValuesInRange(values, lowerBound, upperBound);
67+
bucketCounts.add(count);
68+
}
69+
70+
return bucketCounts;
71+
}
72+
73+
private long countValuesInRange(List<Double> values, double lowerBound, double upperBound) {
74+
long count = 0;
75+
for (Double eachValue : values) {
76+
if (eachValue >= lowerBound && eachValue <= upperBound) {
77+
count++;
78+
}
79+
}
80+
return count;
81+
}
82+
83+
84+
}

src/main/java/io/opentelemetry/contrib/generator/telemetry/metrics/dto/MetricDefinition.java

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.commons.lang3.StringUtils;
2929

3030
import java.util.*;
31+
import java.util.stream.IntStream;
3132

3233
@Data
3334
@Slf4j
@@ -40,6 +41,7 @@ public class MetricDefinition implements Cloneable {
4041
private Boolean isMonotonic;
4142
private Boolean isDouble;
4243
private List<Double> quantiles;
44+
private List<Double> bounds;
4345
private String valueFunction;
4446
private Integer payloadFrequencySeconds;
4547
private Integer payloadCount;
@@ -93,14 +95,15 @@ private void validateOTelType() {
9395
throw new GeneratorException("Invalid OTeltype '" + otelType + "' found for metric " + name + " ." +
9496
"Valid types are " + StringUtils.join(Constants.validMetricTypes));
9597
}
96-
validateAggregationTemporalityForSum();
98+
validateAggregationTemporality();
9799
}
98100

99-
private void validateAggregationTemporalityForSum() {
100-
if (otelType.equalsIgnoreCase(Constants.SUM)) {
101+
private void validateAggregationTemporality() {
102+
if (otelType.equals(Constants.SUM) || otelType.equals(Constants.HISTOGRAM) ||
103+
otelType.equals(Constants.EXP_HISTOGRAM)) {
101104
if (aggregationTemporality==null || aggregationTemporality.isBlank()) {
102-
throw new GeneratorException("OTel type for metric " + name + " is of 'sum' type but Aggregation " +
103-
"temporality not provided");
105+
throw new GeneratorException("OTel type for metric " + name + " is of '" + otelType + "' type but " +
106+
"Aggregation temporality not provided");
104107
}
105108
//Check aggregation temporality is valid
106109
if (!(aggregationTemporality.equalsIgnoreCase(Constants.CUMULATIVE) ||
@@ -109,11 +112,48 @@ private void validateAggregationTemporalityForSum() {
109112
" specified for metric " + name + ". Valid types are (Cumulative, Delta)");
110113
}
111114
}
115+
validateQuantiles();
116+
}
117+
118+
private void validateQuantiles() {
119+
if (otelType.equals(Constants.SUMMARY)) {
120+
if (quantiles == null || quantiles.isEmpty()) {
121+
throw new GeneratorException("OTel metric " + name + " of type summary does not have any quantiles " +
122+
"specified.");
123+
}
124+
}
125+
validateBounds();
126+
}
127+
128+
private void validateBounds() {
129+
if (otelType.equals(Constants.HISTOGRAM)) {
130+
if (bounds == null || bounds.isEmpty()) {
131+
throw new GeneratorException("OTel metric " + name + " of type histogram does not have any bounds " +
132+
"specified.");
133+
}
134+
if (bounds.size() > 1 && IntStream.range(0, bounds.size()-1)
135+
.anyMatch(idx -> bounds.get(idx) >= bounds.get(idx + 1))) {
136+
throw new GeneratorException("Invalid bounds provided for metric " + name + ". Bound values must be " +
137+
"in strictly ascending order");
138+
}
139+
}
112140
validateAttributes();
113141
}
114142

115143
private void validateAttributes() {
116144
attributes = GeneratorUtils.validateAttributes(attributes);
145+
checkValueFunction();
146+
}
147+
148+
private void checkValueFunction() {
149+
if ((otelType.equals(Constants.SUMMARY) || otelType.equals(Constants.HISTOGRAM) ||
150+
otelType.equals(Constants.EXP_HISTOGRAM)) && (!valueFunction.contains("Summary"))) {
151+
log.warn("Metric " + name + " should be using a summary variant of the value expression");
152+
}
153+
if ((otelType.equals(Constants.SUM) || otelType.equals(Constants.GAUGE)) &&
154+
(valueFunction.contains("Summary"))) {
155+
log.warn("Metric " + name + " should not be using the summary variant of the value expression");
156+
}
117157
}
118158

119159
private void validateResourceTypes(Set<String> allResourceTypes) {

src/main/java/io/opentelemetry/contrib/generator/telemetry/misc/Constants.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ private Constants() {}
2626
public static final String SUM = "sum";
2727
public static final String GAUGE = "gauge";
2828
public static final String SUMMARY = "summary";
29-
public static final List<String> validMetricTypes = Arrays.asList(SUM, GAUGE, SUMMARY);
29+
public static final String HISTOGRAM = "histogram";
30+
public static final String EXP_HISTOGRAM = "exponential-histogram";
31+
public static final List<String> validMetricTypes = Arrays.asList(SUM, GAUGE, SUMMARY, HISTOGRAM, EXP_HISTOGRAM);
3032
public static final String CUMULATIVE = "cumulative";
3133
public static final String DELTA = "delta";
3234

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.opentelemetry.contrib.generator.fso_demo;
2+
3+
public class Creds {
4+
5+
public static final String CLIENT_ID = "agt_4MCO9jhas747ibtlLyjiRl";
6+
public static final String CLIENT_SECRET = "Zwy7Ougmab75p2JmWBcAtiomK_TuCawNhTTXkDs5ZmE";
7+
}

0 commit comments

Comments
 (0)