Skip to content

Commit 6f20c01

Browse files
authored
OTLP: add support for summaries (elastic#133906)
1 parent f3f19cd commit 6f20c01

File tree

7 files changed

+146
-11
lines changed

7 files changed

+146
-11
lines changed

x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/datapoint/DataPoint.java

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.opentelemetry.proto.common.v1.KeyValue;
1111
import io.opentelemetry.proto.metrics.v1.Metric;
1212
import io.opentelemetry.proto.metrics.v1.NumberDataPoint;
13+
import io.opentelemetry.proto.metrics.v1.SummaryDataPoint;
1314

1415
import org.elasticsearch.xcontent.XContentBuilder;
1516
import org.elasticsearch.xpack.oteldata.otlp.docbuilder.MappingHints;
@@ -72,10 +73,11 @@ public interface DataPoint {
7273
/**
7374
* Builds the metric value for the data point and writes it to the provided XContentBuilder.
7475
*
76+
* @param mappingHints hints for building the metric value
7577
* @param builder the XContentBuilder to write the metric value to
7678
* @throws IOException if an I/O error occurs while writing to the builder
7779
*/
78-
void buildMetricValue(XContentBuilder builder) throws IOException;
80+
void buildMetricValue(MappingHints mappingHints, XContentBuilder builder) throws IOException;
7981

8082
/**
8183
* Returns the dynamic template name for the data point based on its type and value.
@@ -130,7 +132,7 @@ public String getMetricName() {
130132
}
131133

132134
@Override
133-
public void buildMetricValue(XContentBuilder builder) throws IOException {
135+
public void buildMetricValue(MappingHints mappingHints, XContentBuilder builder) throws IOException {
134136
switch (dataPoint.getValueCase()) {
135137
case AS_DOUBLE -> builder.value(dataPoint.getAsDouble());
136138
case AS_INT -> builder.value(dataPoint.getAsInt());
@@ -168,4 +170,60 @@ public boolean isValid(Set<String> errors) {
168170
return true;
169171
}
170172
}
173+
174+
record Summary(SummaryDataPoint dataPoint, Metric metric) implements DataPoint {
175+
176+
@Override
177+
public long getTimestampUnixNano() {
178+
return dataPoint.getTimeUnixNano();
179+
}
180+
181+
@Override
182+
public List<KeyValue> getAttributes() {
183+
return dataPoint.getAttributesList();
184+
}
185+
186+
@Override
187+
public long getStartTimestampUnixNano() {
188+
return dataPoint.getStartTimeUnixNano();
189+
}
190+
191+
@Override
192+
public String getUnit() {
193+
return metric.getUnit();
194+
}
195+
196+
@Override
197+
public String getMetricName() {
198+
return metric.getName();
199+
}
200+
201+
@Override
202+
public void buildMetricValue(MappingHints mappingHints, XContentBuilder builder) throws IOException {
203+
// TODO: Add support for quantiles
204+
buildAggregateMetricDouble(builder, dataPoint.getSum(), dataPoint.getCount());
205+
}
206+
207+
@Override
208+
public long getDocCount() {
209+
return dataPoint.getCount();
210+
}
211+
212+
@Override
213+
public String getDynamicTemplate(MappingHints mappingHints) {
214+
return "summary";
215+
}
216+
217+
@Override
218+
public boolean isValid(Set<String> errors) {
219+
return true;
220+
}
221+
}
222+
223+
private static void buildAggregateMetricDouble(XContentBuilder builder, double sum, long valueCount) throws IOException {
224+
builder.startObject();
225+
builder.field("sum", sum);
226+
builder.field("value_count", valueCount);
227+
builder.endObject();
228+
}
171229
}

x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/datapoint/DataPointGroupingContext.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,7 @@ public void groupDataPoints(ExportMetricsServiceRequest exportMetricsServiceRequ
7777
ignoredDataPointMessages.add("Histogram is not supported yet. Dropping " + metric.getName());
7878
break;
7979
case SUMMARY:
80-
ignoredDataPoints += metric.getSummary().getDataPointsList().size();
81-
ignoredDataPointMessages.add("Summary is not supported yet. Dropping " + metric.getName());
80+
scopeGroup.addDataPoints(metric, metric.getSummary().getDataPointsList(), DataPoint.Summary::new);
8281
break;
8382
default:
8483
ignoredDataPoints++;

x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/MappingHints.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
* In these cases, the behavior is undefined but does not lead to data loss.
2828
*/
2929
public record MappingHints(boolean aggregateMetricDouble, boolean docCount) {
30-
private static final String MAPPING_HINTS = "elasticsearch.mapping.hints";
30+
31+
public static final String MAPPING_HINTS = "elasticsearch.mapping.hints";
32+
public static final String AGGREGATE_METRIC_DOUBLE = "aggregate_metric_double";
33+
public static final String DOC_COUNT = "_doc_count";
34+
3135
private static final MappingHints EMPTY = new MappingHints(false, false);
32-
private static final String AGGREGATE_METRIC_DOUBLE = "aggregate_metric_double";
33-
private static final String DOC_COUNT = "_doc_count";
3436

3537
public static MappingHints fromAttributes(List<KeyValue> attributes) {
3638
boolean aggregateMetricDouble = false;

x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/MetricDocumentBuilder.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,25 @@ public HashMap<String, String> buildMetricDocument(XContentBuilder builder, Data
5555
buildDataPointAttributes(builder, dataPointGroup.dataPointAttributes(), dataPointGroup.unit());
5656
builder.field("_metric_names_hash", dataPointGroup.getMetricNamesHash(hasher));
5757

58+
long docCount = 0;
5859
builder.startObject("metrics");
5960
for (int i = 0, dataPointsSize = dataPoints.size(); i < dataPointsSize; i++) {
6061
DataPoint dataPoint = dataPoints.get(i);
6162
builder.field(dataPoint.getMetricName());
62-
dataPoint.buildMetricValue(builder);
63-
String dynamicTemplate = dataPoint.getDynamicTemplate(MappingHints.empty());
63+
MappingHints mappingHints = MappingHints.fromAttributes(dataPoint.getAttributes());
64+
dataPoint.buildMetricValue(mappingHints, builder);
65+
String dynamicTemplate = dataPoint.getDynamicTemplate(mappingHints);
6466
if (dynamicTemplate != null) {
6567
dynamicTemplates.put("metrics." + dataPoint.getMetricName(), dynamicTemplate);
6668
}
69+
if (mappingHints.docCount()) {
70+
docCount = dataPoint.getDocCount();
71+
}
6772
}
6873
builder.endObject();
74+
if (docCount > 0) {
75+
builder.field("_doc_count", docCount);
76+
}
6977
builder.endObject();
7078
return dynamicTemplates;
7179
}

x-pack/plugin/otel-data/src/test/java/org/elasticsearch/xpack/oteldata/otlp/OtlpUtils.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@
1919
import io.opentelemetry.proto.metrics.v1.ResourceMetrics;
2020
import io.opentelemetry.proto.metrics.v1.ScopeMetrics;
2121
import io.opentelemetry.proto.metrics.v1.Sum;
22+
import io.opentelemetry.proto.metrics.v1.Summary;
23+
import io.opentelemetry.proto.metrics.v1.SummaryDataPoint;
2224
import io.opentelemetry.proto.resource.v1.Resource;
2325

26+
import org.elasticsearch.xpack.oteldata.otlp.docbuilder.MappingHints;
27+
2428
import java.util.ArrayList;
2529
import java.util.Arrays;
2630
import java.util.List;
@@ -38,6 +42,10 @@ public static KeyValue keyValue(String key, String value) {
3842
return KeyValue.newBuilder().setKey(key).setValue(AnyValue.newBuilder().setStringValue(value).build()).build();
3943
}
4044

45+
public static List<KeyValue> mappingHints(String... mappingHints) {
46+
return List.of(keyValue(MappingHints.MAPPING_HINTS, mappingHints));
47+
}
48+
4149
public static KeyValue keyValue(String key, String... values) {
4250
return KeyValue.newBuilder()
4351
.setKey(key)
@@ -105,6 +113,14 @@ public static Metric createSumMetric(
105113
.build();
106114
}
107115

116+
public static Metric createSummaryMetric(String name, String unit, List<SummaryDataPoint> dataPoints) {
117+
return Metric.newBuilder()
118+
.setName(name)
119+
.setUnit(unit)
120+
.setSummary(Summary.newBuilder().addAllDataPoints(dataPoints).build())
121+
.build();
122+
}
123+
108124
public static NumberDataPoint createDoubleDataPoint(long timestamp) {
109125
return createDoubleDataPoint(timestamp, timestamp, List.of());
110126
}
@@ -131,6 +147,16 @@ public static NumberDataPoint createLongDataPoint(long timeUnixNano, long startT
131147
.build();
132148
}
133149

150+
public static SummaryDataPoint createSummaryDataPoint(long timestamp, List<KeyValue> attributes) {
151+
return SummaryDataPoint.newBuilder()
152+
.setTimeUnixNano(timestamp)
153+
.setStartTimeUnixNano(timestamp)
154+
.addAllAttributes(attributes)
155+
.setCount(randomLong())
156+
.setSum(randomDouble())
157+
.build();
158+
}
159+
134160
public static ExportMetricsServiceRequest createMetricsRequest(List<Metric> metrics) {
135161

136162
List<ResourceMetrics> resourceMetrics = new ArrayList<>();

x-pack/plugin/otel-data/src/test/java/org/elasticsearch/xpack/oteldata/otlp/datapoint/DataPointGroupingContextTests.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.createResourceMetrics;
2626
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.createScopeMetrics;
2727
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.createSumMetric;
28+
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.createSummaryDataPoint;
29+
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.createSummaryMetric;
2830
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.keyValue;
2931
import static org.hamcrest.Matchers.containsInAnyOrder;
3032
import static org.hamcrest.Matchers.containsString;
@@ -46,11 +48,12 @@ public void testGroupingSameGroup() throws Exception {
4648
List.of(createLongDataPoint(nowUnixNanos)),
4749
true,
4850
AGGREGATION_TEMPORALITY_CUMULATIVE
49-
)
51+
),
52+
createSummaryMetric("summary", "", List.of(createSummaryDataPoint(nowUnixNanos, List.of())))
5053
)
5154
);
5255
context.groupDataPoints(metricsRequest);
53-
assertEquals(3, context.totalDataPoints());
56+
assertEquals(4, context.totalDataPoints());
5457
assertEquals(0, context.getIgnoredDataPoints());
5558
assertEquals("", context.getIgnoredDataPointsMessage());
5659

x-pack/plugin/otel-data/src/test/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/MetricDocumentBuilderTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.opentelemetry.proto.common.v1.InstrumentationScope;
1111
import io.opentelemetry.proto.common.v1.KeyValue;
1212
import io.opentelemetry.proto.metrics.v1.AggregationTemporality;
13+
import io.opentelemetry.proto.metrics.v1.SummaryDataPoint;
1314
import io.opentelemetry.proto.resource.v1.Resource;
1415

1516
import com.google.protobuf.ByteString;
@@ -37,7 +38,9 @@
3738
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.createGaugeMetric;
3839
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.createLongDataPoint;
3940
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.createSumMetric;
41+
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.createSummaryMetric;
4042
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.keyValue;
43+
import static org.elasticsearch.xpack.oteldata.otlp.OtlpUtils.mappingHints;
4144
import static org.hamcrest.Matchers.equalTo;
4245
import static org.hamcrest.Matchers.hasEntry;
4346
import static org.hamcrest.Matchers.is;
@@ -197,4 +200,40 @@ public void testEmptyFields() throws IOException {
197200
assertThat(doc.evaluate("unit"), is(nullValue()));
198201
}
199202

203+
public void testSummary() throws Exception {
204+
Resource resource = Resource.newBuilder().build();
205+
InstrumentationScope scope = InstrumentationScope.newBuilder().build();
206+
207+
DataPointGroupingContext.DataPointGroup dataPointGroup = new DataPointGroupingContext.DataPointGroup(
208+
resource,
209+
null,
210+
scope,
211+
null,
212+
List.of(),
213+
"",
214+
TargetIndex.defaultMetrics()
215+
);
216+
dataPointGroup.addDataPoint(
217+
Set.of(),
218+
new DataPoint.Summary(
219+
SummaryDataPoint.newBuilder()
220+
.setTimeUnixNano(timestamp)
221+
.setStartTimeUnixNano(startTimestamp)
222+
.setCount(1)
223+
.setSum(42.0)
224+
.addAllAttributes(mappingHints(MappingHints.DOC_COUNT))
225+
.build(),
226+
createSummaryMetric("summary", "", List.of())
227+
)
228+
);
229+
230+
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
231+
HashMap<String, String> dynamicTemplates = documentBuilder.buildMetricDocument(builder, dataPointGroup);
232+
233+
ObjectPath doc = ObjectPath.createFromXContent(JsonXContent.jsonXContent, BytesReference.bytes(builder));
234+
assertThat(doc.evaluate("metrics.summary.sum"), equalTo(42.0));
235+
assertThat(doc.evaluate("metrics.summary.value_count"), equalTo(1));
236+
assertThat(doc.evaluate("_doc_count"), equalTo(1));
237+
assertThat(dynamicTemplates, hasEntry("metrics.summary", "summary"));
238+
}
200239
}

0 commit comments

Comments
 (0)