Skip to content

Commit 9399154

Browse files
carsonipjriguera
authored andcommitted
[exporter/elasticsearch] Emit _doc_count for metric documents in OTel mode when data point attribute _doc_count is true (open-telemetry#35348)
**Description:** <Describe what has changed.> <!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> Emit _doc_count for metric documents in OTel mode when data point attribute _doc_count is true **Link to tracking Issue:** <Issue number if applicable> **Testing:** <Describe what testing was performed and which tests were added.> **Documentation:** <Describe the documentation added.>
1 parent 37764b5 commit 9399154

File tree

4 files changed

+100
-0
lines changed

4 files changed

+100
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: elasticsearchexporter
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Emit _doc_count for metric documents in OTel mode when data point attribute _doc_count is true
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [35348]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

exporter/elasticsearchexporter/exporter_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,53 @@ func TestExporterMetrics(t *testing.T) {
947947
assert.Equal(t, `{"some.resource.attribute":["foo","bar"]}`, gjson.GetBytes(doc, `resource.attributes`).Raw)
948948
})
949949

950+
t.Run("otel mode _doc_count", func(t *testing.T) {
951+
rec := newBulkRecorder()
952+
server := newESTestServer(t, func(docs []itemRequest) ([]itemResponse, error) {
953+
rec.Record(docs)
954+
return itemsAllOK(docs)
955+
})
956+
957+
exporter := newTestMetricsExporter(t, server.URL, func(cfg *Config) {
958+
cfg.Mapping.Mode = "otel"
959+
})
960+
961+
metrics := pmetric.NewMetrics()
962+
resourceMetric := metrics.ResourceMetrics().AppendEmpty()
963+
scopeMetric := resourceMetric.ScopeMetrics().AppendEmpty()
964+
965+
sumMetric := scopeMetric.Metrics().AppendEmpty()
966+
sumMetric.SetName("sum")
967+
sumDP := sumMetric.SetEmptySum().DataPoints().AppendEmpty()
968+
sumDP.SetIntValue(0)
969+
970+
summaryMetric := scopeMetric.Metrics().AppendEmpty()
971+
summaryMetric.SetName("summary")
972+
summaryDP := summaryMetric.SetEmptySummary().DataPoints().AppendEmpty()
973+
summaryDP.SetSum(1)
974+
summaryDP.SetCount(10)
975+
fillAttributeMap(summaryDP.Attributes(), map[string]any{
976+
"_doc_count": true,
977+
})
978+
979+
mustSendMetrics(t, exporter, metrics)
980+
981+
rec.WaitItems(2)
982+
expected := []itemRequest{
983+
{
984+
Action: []byte(`{"create":{"_index":"metrics-generic.otel-default","dynamic_templates":{"metrics.summary":"summary_metrics"}}}`),
985+
Document: []byte(`{"@timestamp":"1970-01-01T00:00:00.000000000Z","_doc_count":10,"attributes":{"_doc_count":true},"data_stream":{"dataset":"generic.otel","namespace":"default","type":"metrics"},"metrics":{"summary":{"sum":1.0,"value_count":10}},"resource":{"dropped_attributes_count":0},"scope":{"dropped_attributes_count":0}}`),
986+
},
987+
{
988+
Action: []byte(`{"create":{"_index":"metrics-generic.otel-default","dynamic_templates":{"metrics.sum":"gauge_long"}}}`),
989+
Document: []byte(`{"@timestamp":"1970-01-01T00:00:00.000000000Z","data_stream":{"dataset":"generic.otel","namespace":"default","type":"metrics"},"metrics":{"sum":0},"resource":{"dropped_attributes_count":0},"scope":{"dropped_attributes_count":0}}`),
990+
},
991+
}
992+
993+
assertItemsEqual(t, expected, rec.Items(), false)
994+
995+
})
996+
950997
t.Run("publish summary", func(t *testing.T) {
951998
rec := newBulkRecorder()
952999
server := newESTestServer(t, func(docs []itemRequest) ([]itemResponse, error) {

exporter/elasticsearchexporter/model.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ type dataPoint interface {
8989
Attributes() pcommon.Map
9090
Value() (pcommon.Value, error)
9191
DynamicTemplate(pmetric.Metric) string
92+
DocCount() uint64
9293
}
9394

9495
const (
@@ -284,6 +285,7 @@ func (m *encodeModel) upsertMetricDataPointValueOTelMode(documents map[uint32]ob
284285
if err != nil {
285286
return err
286287
}
288+
287289
// documents is per-resource. Therefore, there is no need to hash resource attributes
288290
hash := metricOTelHash(dp, scope.Attributes(), metric.Unit())
289291
var (
@@ -302,6 +304,12 @@ func (m *encodeModel) upsertMetricDataPointValueOTelMode(documents map[uint32]ob
302304
m.encodeScopeOTelMode(&document, scope, scopeSchemaURL)
303305
}
304306

307+
// Emit _doc_count if data point contains attribute _doc_count: true
308+
if val, ok := dp.Attributes().Get("_doc_count"); ok && val.Bool() {
309+
docCount := dp.DocCount()
310+
document.AddInt("_doc_count", int64(docCount))
311+
}
312+
305313
switch value.Type() {
306314
case pcommon.ValueTypeMap:
307315
m := pcommon.NewMap()
@@ -340,6 +348,10 @@ func (dp summaryDataPoint) DynamicTemplate(_ pmetric.Metric) string {
340348
return "summary_metrics"
341349
}
342350

351+
func (dp summaryDataPoint) DocCount() uint64 {
352+
return dp.Count()
353+
}
354+
343355
type exponentialHistogramDataPoint struct {
344356
pmetric.ExponentialHistogramDataPoint
345357
}
@@ -367,6 +379,10 @@ func (dp exponentialHistogramDataPoint) DynamicTemplate(_ pmetric.Metric) string
367379
return "histogram"
368380
}
369381

382+
func (dp exponentialHistogramDataPoint) DocCount() uint64 {
383+
return dp.Count()
384+
}
385+
370386
type histogramDataPoint struct {
371387
pmetric.HistogramDataPoint
372388
}
@@ -379,6 +395,10 @@ func (dp histogramDataPoint) DynamicTemplate(_ pmetric.Metric) string {
379395
return "histogram"
380396
}
381397

398+
func (dp histogramDataPoint) DocCount() uint64 {
399+
return dp.HistogramDataPoint.Count()
400+
}
401+
382402
func histogramToValue(dp pmetric.HistogramDataPoint) (pcommon.Value, error) {
383403
// Histogram conversion function is from
384404
// https://github.com/elastic/apm-data/blob/3b28495c3cbdc0902983134276eb114231730249/input/otlp/metrics.go#L277
@@ -475,6 +495,10 @@ func (dp numberDataPoint) DynamicTemplate(metric pmetric.Metric) string {
475495
return ""
476496
}
477497

498+
func (dp numberDataPoint) DocCount() uint64 {
499+
return 1
500+
}
501+
478502
var errInvalidNumberDataPoint = errors.New("invalid number data point")
479503

480504
func (m *encodeModel) encodeResourceOTelMode(document *objmodel.Document, resource pcommon.Resource, resourceSchemaURL string) {

exporter/elasticsearchexporter/utils_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ func fillAttributeMap(attrs pcommon.Map, m map[string]any) {
288288
attrs.EnsureCapacity(len(m))
289289
for k, v := range m {
290290
switch vv := v.(type) {
291+
case bool:
292+
attrs.PutBool(k, vv)
291293
case string:
292294
attrs.PutStr(k, vv)
293295
case []string:

0 commit comments

Comments
 (0)