Skip to content

Commit 408314b

Browse files
authored
Merge pull request #95 from cisco-open/feature/master/histogram-metrics-types-support
Add support for histogram metrics
2 parents 8a0eaff + 9c9858e commit 408314b

File tree

7 files changed

+180
-22
lines changed

7 files changed

+180
-22
lines changed

example-definitions/json/metric-definition.json

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,16 @@
9191
{
9292
"name": "container.cpu.used.utilization",
9393
"unit": "%",
94-
"otelType": "summary",
95-
"valueFunction": "arithmeticSequenceSummary(1, 2, \"\", 7)",
94+
"otelType": "histogram",
95+
"aggregationTemporality": "delta",
96+
"valueFunction": "arithmeticSequenceSummary(5, 2, \"\", 7)",
9697
"isDouble": true,
97-
"quantiles": [
98-
0,
99-
0.5,
100-
0.75,
101-
1
98+
"bounds": [
99+
10,
100+
30,
101+
50,
102+
70,
103+
90
102104
],
103105
"reportingResources": [
104106
"container"

example-definitions/qa/metric-definition.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,11 @@ metrics:
6363
reportingResources: [resource_quota]
6464
- name: container.cpu.used.utilization
6565
unit: "%"
66-
otelType: summary
67-
valueFunction: 'arithmeticSequenceSummary(1, 2, "", 7)'
66+
otelType: histogram
67+
aggregationTemporality: delta
68+
valueFunction: 'arithmeticSequenceSummary(5, 2, "", 7)'
6869
isDouble: true
69-
quantiles: [ 0, 0.5, 0.75, 1 ]
70+
bounds: [10, 30, 50, 70, 90]
7071
reportingResources: [ container ]
7172
- name: cpu.usage
7273
unit: "{cores}"
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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.GeneratorsStateProvider;
5+
import io.opentelemetry.contrib.generator.telemetry.metrics.dto.MetricDefinition;
6+
import io.opentelemetry.contrib.generator.telemetry.misc.GeneratorUtils;
7+
import io.opentelemetry.proto.metrics.v1.*;
8+
import lombok.extern.slf4j.Slf4j;
9+
10+
import java.util.ArrayList;
11+
import java.util.Collections;
12+
import java.util.List;
13+
import java.util.concurrent.TimeUnit;
14+
15+
/**
16+
* Class to generate metric values of Histogram type.
17+
*/
18+
@Slf4j
19+
public class HistogramGenerator {
20+
21+
private final String requestID;
22+
private final ExpressionProcessor jelProcessor;
23+
24+
public HistogramGenerator(String requestID, ExpressionProcessor jelProcessor) {
25+
this.requestID = requestID;
26+
this.jelProcessor = jelProcessor;
27+
}
28+
29+
public Metric.Builder getOTelMetric(MetricDefinition metricDefinition) {
30+
return Metric.newBuilder()
31+
.setName(metricDefinition.getName())
32+
.setUnit(metricDefinition.getUnit())
33+
.setHistogram(getHistogramDataPoint(metricDefinition));
34+
35+
}
36+
37+
private Histogram getHistogramDataPoint(MetricDefinition metricDefinition) {
38+
long[] times = getTimes(metricDefinition);
39+
List<Double> values = getCountValues(metricDefinition);
40+
double sum = values.stream().mapToDouble(Double::doubleValue).sum();
41+
int count = (values.size());
42+
return Histogram.newBuilder()
43+
.setAggregationTemporality(metricDefinition.getAggregationTemporality())
44+
.addDataPoints(HistogramDataPoint.newBuilder()
45+
.setCount(count)
46+
.setSum(sum)
47+
.setStartTimeUnixNano(times[0])
48+
.setTimeUnixNano(times[1])
49+
.addAllExplicitBounds(metricDefinition.getBounds())
50+
.addAllBucketCounts(getBucketCounts(metricDefinition, values))
51+
.addAllAttributes(GeneratorUtils.getEvaluatedAttributes(jelProcessor, metricDefinition.getAttributes()))
52+
.build())
53+
.build();
54+
}
55+
56+
private long[] getTimes(MetricDefinition metricDefinition) {
57+
long[] times = GeneratorUtils.normalizeTimestamp(TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()),
58+
metricDefinition.getPayloadFrequencySeconds());
59+
if (metricDefinition.getAggregationTemporality() == AggregationTemporality.AGGREGATION_TEMPORALITY_CUMULATIVE) {
60+
if (GeneratorsStateProvider.getMetricGeneratorState(requestID).getFixedStartTime() == 0) {
61+
GeneratorsStateProvider.getMetricGeneratorState(requestID).setFixedStartTime(times[0]);
62+
}
63+
times[0] = GeneratorsStateProvider.getMetricGeneratorState(requestID).getFixedStartTime();
64+
}
65+
return times;
66+
}
67+
68+
private List<Double> getCountValues(MetricDefinition metricDefinition) {
69+
Object value = jelProcessor.eval(metricDefinition.getValueFunction());
70+
List<Object> rawValues = value instanceof List<?> ? (List<Object>) value : Collections.nCopies(5, value);
71+
return rawValues.stream().map(val -> Double.parseDouble(val.toString())).toList();
72+
}
73+
74+
private List<Long> getBucketCounts(MetricDefinition metricDefinition, List<Double> values) {
75+
List<Long> bucketCounts = new ArrayList<>();
76+
for (int i = 0; i < metricDefinition.getBounds().size(); i++) {
77+
double lowerBound = i == 0 ? Double.MIN_VALUE : metricDefinition.getBounds().get(i - 1) + 1;
78+
double upperBound = i == metricDefinition.getBounds().size() - 1 ? Double.MAX_VALUE :
79+
metricDefinition.getBounds().get(i);
80+
long count = countValuesInRange(values, lowerBound, upperBound);
81+
bucketCounts.add(count);
82+
}
83+
84+
return bucketCounts;
85+
}
86+
87+
private long countValuesInRange(List<Double> values, double lowerBound, double upperBound) {
88+
long count = 0;
89+
for (Double eachValue : values) {
90+
if (eachValue >= lowerBound && eachValue <= upperBound) {
91+
count++;
92+
}
93+
}
94+
return count;
95+
}
96+
97+
98+
}

src/main/java/io/opentelemetry/contrib/generator/telemetry/metrics/MetricGeneratorThread.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public class MetricGeneratorThread implements Runnable {
5252
private final GeneratorState<MetricGeneratorThread> metricGeneratorState;
5353
private final GaugeGenerator gaugeGenerator;
5454
private final SumGenerator sumGenerator;
55+
private final HistogramGenerator histogramGenerator;
5556
private final SummaryGenerator summaryGenerator;
5657
private int currentCount;
5758

@@ -65,6 +66,7 @@ public MetricGeneratorThread(String groupKey, List<MetricDefinition> metrics, Pa
6566
ExpressionProcessor jelProcessor = JELProvider.getJelProcessor();
6667
gaugeGenerator = new GaugeGenerator(jelProcessor);
6768
sumGenerator = new SumGenerator(requestID, jelProcessor);
69+
histogramGenerator = new HistogramGenerator(requestID, jelProcessor);
6870
summaryGenerator = new SummaryGenerator(jelProcessor);
6971
currentCount = 0;
7072
}
@@ -116,7 +118,7 @@ public void run() {
116118
.addAllResourceMetrics(resourceMetricsList)
117119
.build();
118120
log.info(requestID + ": Sending payload for: " + groupKey);
119-
//log.debug(requestID + ": Complete payload for " + groupKey + ": " + resourceMetrics);
121+
log.debug(requestID + ": Complete payload for " + groupKey + ": " + resourceMetrics);
120122
boolean responseStatus = payloadHandler.postPayload(resourceMetrics);
121123
if (metricGeneratorState.getTransportStorage() != null) {
122124
metricGeneratorState.getTransportStorage().store(groupKey, resourceMetrics, responseStatus);
@@ -131,6 +133,7 @@ private Metric.Builder getMetric(MetricDefinition metricDefinition) {
131133
return switch (metricDefinition.getOtelType()) {
132134
case Constants.GAUGE -> gaugeGenerator.getOTelMetric(metricDefinition);
133135
case Constants.SUM -> sumGenerator.getOTelMetric(metricDefinition);
136+
case Constants.HISTOGRAM -> histogramGenerator.getOTelMetric(metricDefinition);
134137
default -> summaryGenerator.getOTelMetric(metricDefinition);
135138
};
136139
}
@@ -195,18 +198,29 @@ private Metric getMetricWithResourceAttributes(Metric.Builder partialMetric, Lis
195198
.map(NumberDataPoint::toBuilder)
196199
.map(bdp -> bdp.addAllAttributes(resourceAttributes).build())
197200
.toList();
198-
partialMetric.getSum().toBuilder()
201+
Sum newSum = partialMetric.getSum().toBuilder()
199202
.clearDataPoints()
200203
.addAllDataPoints(dataPointsWAttrs)
201204
.build();
202-
Sum newSum = partialMetric.getSum().toBuilder()
205+
return Metric.newBuilder()
206+
.setName(partialMetric.getName())
207+
.setUnit(partialMetric.getUnit())
208+
.setSum(newSum)
209+
.build();
210+
} else if (metricType.equals(Metric.DataCase.HISTOGRAM)) {
211+
List<HistogramDataPoint> dataPoints = partialMetric.getHistogram().getDataPointsList();
212+
List<HistogramDataPoint> dataPointsWAttrs = dataPoints.stream()
213+
.map(HistogramDataPoint::toBuilder)
214+
.map(bdp -> bdp.addAllAttributes(resourceAttributes).build())
215+
.toList();
216+
Histogram newHistogram = partialMetric.getHistogram().toBuilder()
203217
.clearDataPoints()
204218
.addAllDataPoints(dataPointsWAttrs)
205219
.build();
206220
return Metric.newBuilder()
207221
.setName(partialMetric.getName())
208222
.setUnit(partialMetric.getUnit())
209-
.setSum(newSum)
223+
.setHistogram(newHistogram)
210224
.build();
211225
} else {
212226
List<SummaryDataPoint> dataPoints = partialMetric.getSummary().getDataPointsList();

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

src/test/resources/test-definitions/metrics-test.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ payloadCount: 10
1919
metrics:
2020
- name: system.network.in.kb.sec
2121
unit: kBy/s
22-
otelType: summary
22+
otelType: histogram
2323
valueFunction: 'absoluteCosineSequenceSummary("*7000", 5)'
2424
isDouble: true
25-
quantiles: [0, 50, 100]
25+
aggregationTemporality: cumulative
26+
bounds: [10, 100, 1000, 10000, 100000]
2627
reportingResources: [network_interface, container, machine]
2728
attributes:
2829
system.internal.ip: 'IPv4Sequence("10.134.1.34")'

0 commit comments

Comments
 (0)