diff --git a/.chloggen/append-exemplars-to-prometheus-receiver.yaml b/.chloggen/append-exemplars-to-prometheus-receiver.yaml new file mode 100644 index 000000000000..cd3e690e8b43 --- /dev/null +++ b/.chloggen/append-exemplars-to-prometheus-receiver.yaml @@ -0,0 +1,9 @@ +change_type: enhancement + +component: receiver/prometheusreceiver + +note: Append exemplars to the metrics received by prometheus receiver + +issues: [8353] + +subtext: Acknowledge exemplars coming from prometheus receiver and append it to otel format diff --git a/receiver/prometheusreceiver/README.md b/receiver/prometheusreceiver/README.md index 988642d5474c..3072a4993466 100644 --- a/receiver/prometheusreceiver/README.md +++ b/receiver/prometheusreceiver/README.md @@ -94,6 +94,12 @@ receivers: interval: 30s collector_id: collector-1 ``` +## Exemplars +This receiver accepts exemplars coming in Prometheus format and converts it to OTLP format. +1. Value is expected to be received in `float64` format +2. Timestamp is expected to be received in `ms` +3. Labels with key `span_id` in prometheus exemplars are set as OTLP `span id` and labels with key `trace_id` are set as `trace id` +4. Rest of the labels are copied as it is to OTLP format [sc]: https://github.com/prometheus/prometheus/blob/v2.28.1/docs/configuration/configuration.md#scrape_config diff --git a/receiver/prometheusreceiver/internal/metricfamily.go b/receiver/prometheusreceiver/internal/metricfamily.go index 461f8c9253fa..a1d1d22fc9be 100644 --- a/receiver/prometheusreceiver/internal/metricfamily.go +++ b/receiver/prometheusreceiver/internal/metricfamily.go @@ -15,10 +15,12 @@ package internal // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver/internal" import ( + "encoding/hex" "fmt" "sort" "strings" + "github.com/prometheus/prometheus/model/exemplar" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/value" "github.com/prometheus/prometheus/scrape" @@ -27,6 +29,11 @@ import ( "go.uber.org/zap" ) +const ( + traceIDKey = "trace_id" + spanIDKey = "span_id" +) + type metricFamily struct { mtype pmetric.MetricType // isMonotonic only applies to sums @@ -50,6 +57,7 @@ type metricGroup struct { hasSum bool value float64 complexValue []*dataPoint + exemplars pmetric.ExemplarSlice } func newMetricFamily(metricName string, mc scrape.MetricMetadataStore, logger *zap.Logger) *metricFamily { @@ -141,6 +149,16 @@ func (mg *metricGroup) toDistributionPoint(dest pmetric.HistogramDataPointSlice) point.SetStartTimestamp(tsNanos) // metrics_adjuster adjusts the startTimestamp to the initial scrape timestamp point.SetTimestamp(tsNanos) populateAttributes(pmetric.MetricTypeHistogram, mg.ls, point.Attributes()) + mg.setExemplars(point.Exemplars()) +} + +func (mg *metricGroup) setExemplars(exemplars pmetric.ExemplarSlice) { + if mg == nil { + return + } + if mg.exemplars.Len() > 0 { + mg.exemplars.MoveAndAppendTo(exemplars) + } } func (mg *metricGroup) toSummaryPoint(dest pmetric.SummaryDataPointSlice) { @@ -201,6 +219,7 @@ func (mg *metricGroup) toNumberDataPoint(dest pmetric.NumberDataPointSlice) { point.SetDoubleValue(mg.value) } populateAttributes(pmetric.MetricTypeGauge, mg.ls, point.Attributes()) + mg.setExemplars(point.Exemplars()) } func populateAttributes(mType pmetric.MetricType, ls labels.Labels, dest pcommon.Map) { @@ -226,9 +245,10 @@ func (mf *metricFamily) loadMetricGroupOrCreate(groupKey uint64, ls labels.Label mg, ok := mf.groups[groupKey] if !ok { mg = &metricGroup{ - family: mf, - ts: ts, - ls: ls, + family: mf, + ts: ts, + ls: ls, + exemplars: pmetric.NewExemplarSlice(), } mf.groups[groupKey] = mg // maintaining data insertion order is helpful to generate stable/reproducible metric output @@ -319,3 +339,59 @@ func (mf *metricFamily) appendMetric(metrics pmetric.MetricSlice) { metric.MoveTo(metrics.AppendEmpty()) } + +func (mf *metricFamily) addExemplar(l labels.Labels, e exemplar.Exemplar) { + gk := mf.getGroupKey(l) + mg := mf.groups[gk] + if mg == nil { + return + } + es := mg.exemplars + convertExemplar(e, es.AppendEmpty()) +} + +func convertExemplar(pe exemplar.Exemplar, e pmetric.Exemplar) { + e.SetTimestamp(timestampFromMs(pe.Ts)) + e.SetDoubleValue(pe.Value) + e.FilteredAttributes().EnsureCapacity(len(pe.Labels)) + for _, lb := range pe.Labels { + switch strings.ToLower(lb.Name) { + case traceIDKey: + var tid [16]byte + err := decodeAndCopyToLowerBytes(tid[:], []byte(lb.Value)) + if err == nil { + e.SetTraceID(tid) + } else { + e.FilteredAttributes().PutString(lb.Name, lb.Value) + } + case spanIDKey: + var sid [8]byte + err := decodeAndCopyToLowerBytes(sid[:], []byte(lb.Value)) + if err == nil { + e.SetSpanID(sid) + } else { + e.FilteredAttributes().PutString(lb.Name, lb.Value) + } + default: + e.FilteredAttributes().PutString(lb.Name, lb.Value) + } + } +} + +/* + decodeAndCopyToLowerBytes copies src to dst on lower bytes instead of higher + +1. If len(src) > len(dst) -> copy first len(dst) bytes as it is. Example -> src = []byte{0xab,0xcd,0xef,0xgh,0xij}, dst = [2]byte, result dst = [2]byte{0xab, 0xcd} +2. If len(src) = len(dst) -> copy src to dst as it is +3. If len(src) < len(dst) -> prepend required 0s and then add src to dst. Example -> src = []byte{0xab, 0xcd}, dst = [8]byte, result dst = [8]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0xcd} +*/ +func decodeAndCopyToLowerBytes(dst []byte, src []byte) error { + var err error + decodedLen := hex.DecodedLen(len(src)) + if decodedLen >= len(dst) { + _, err = hex.Decode(dst, src[:hex.EncodedLen(len(dst))]) + } else { + _, err = hex.Decode(dst[len(dst)-decodedLen:], src) + } + return err +} diff --git a/receiver/prometheusreceiver/internal/transaction.go b/receiver/prometheusreceiver/internal/transaction.go index be1cdc56d635..f9f08eecac90 100644 --- a/receiver/prometheusreceiver/internal/transaction.go +++ b/receiver/prometheusreceiver/internal/transaction.go @@ -124,21 +124,52 @@ func (t *transaction) Append(ref storage.SeriesRef, ls labels.Labels, atMs int64 return 0, t.AddTargetInfo(ls) } - curMF, ok := t.families[metricName] + curMF := t.getOrCreateMetricFamily(metricName) + + return 0, curMF.Add(metricName, ls, atMs, val) +} + +func (t *transaction) getOrCreateMetricFamily(mn string) *metricFamily { + curMf, ok := t.families[mn] if !ok { - familyName := normalizeMetricName(metricName) - if mf, ok := t.families[familyName]; ok && mf.includesMetric(metricName) { - curMF = mf + fn := normalizeMetricName(mn) + if mf, ok := t.families[fn]; ok && mf.includesMetric(mn) { + curMf = mf } else { - curMF = newMetricFamily(metricName, t.mc, t.logger) - t.families[curMF.name] = curMF + curMf = newMetricFamily(mn, t.mc, t.logger) + t.families[curMf.name] = curMf } } - - return 0, curMF.Add(metricName, ls, atMs, val) + return curMf } func (t *transaction) AppendExemplar(ref storage.SeriesRef, l labels.Labels, e exemplar.Exemplar) (storage.SeriesRef, error) { + select { + case <-t.ctx.Done(): + return 0, errTransactionAborted + default: + } + + if t.isNew { + if err := t.initTransaction(l); err != nil { + return 0, err + } + } + + l = l.WithoutEmpty() + + if dupLabel, hasDup := l.HasDuplicateLabelNames(); hasDup { + return 0, fmt.Errorf("invalid sample: non-unique label names: %q", dupLabel) + } + + mn := l.Get(model.MetricNameLabel) + if mn == "" { + return 0, errMetricNameNotFound + } + + mf := t.getOrCreateMetricFamily(mn) + mf.addExemplar(l, e) + return 0, nil } diff --git a/receiver/prometheusreceiver/internal/transaction_test.go b/receiver/prometheusreceiver/internal/transaction_test.go index 45ad35dd3e6a..f2e5d69e4162 100644 --- a/receiver/prometheusreceiver/internal/transaction_test.go +++ b/receiver/prometheusreceiver/internal/transaction_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/model/exemplar" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/metadata" "github.com/prometheus/prometheus/scrape" @@ -189,6 +190,78 @@ func TestTransactionAppendSummaryNoQuantile(t *testing.T) { require.ErrorIs(t, err, errEmptyQuantileLabel) } +func TestAppendExemplarWithNoMetricName(t *testing.T) { + sink := new(consumertest.MetricsSink) + tr := newTransaction(scrapeCtx, &startTimeAdjuster{startTime: startTimestamp}, sink, nil, componenttest.NewNopReceiverCreateSettings(), nopObsRecv()) + + labels := labels.FromStrings( + model.InstanceLabel, "0.0.0.0:8855", + model.JobLabel, "test", + ) + + _, err := tr.AppendExemplar(0, labels, exemplar.Exemplar{Value: 0}) + assert.Equal(t, errMetricNameNotFound, err) +} + +func TestAppendExemplarWithEmptyMetricName(t *testing.T) { + sink := new(consumertest.MetricsSink) + tr := newTransaction(scrapeCtx, &startTimeAdjuster{startTime: startTimestamp}, sink, nil, componenttest.NewNopReceiverCreateSettings(), nopObsRecv()) + + labels := labels.FromStrings( + model.InstanceLabel, "0.0.0.0:8855", + model.JobLabel, "test", + model.MetricNameLabel, "", + ) + _, err := tr.AppendExemplar(0, labels, exemplar.Exemplar{Value: 0}) + assert.Equal(t, errMetricNameNotFound, err) +} + +func TestAppendExemplarWithDuplicateLabels(t *testing.T) { + sink := new(consumertest.MetricsSink) + tr := newTransaction(scrapeCtx, &startTimeAdjuster{startTime: startTimestamp}, sink, nil, componenttest.NewNopReceiverCreateSettings(), nopObsRecv()) + + labels := labels.FromStrings( + model.InstanceLabel, "0.0.0.0:8855", + model.JobLabel, "test", + model.MetricNameLabel, "", + "a", "b", + "a", "c", + ) + _, err := tr.AppendExemplar(0, labels, exemplar.Exemplar{Value: 0}) + require.Error(t, err) + assert.Contains(t, err.Error(), `invalid sample: non-unique label names: "a"`) +} + +func TestAppendExemplarWithoutAddingMetric(t *testing.T) { + sink := new(consumertest.MetricsSink) + tr := newTransaction(scrapeCtx, &startTimeAdjuster{startTime: startTimestamp}, sink, nil, componenttest.NewNopReceiverCreateSettings(), nopObsRecv()) + + labels := labels.FromStrings( + model.InstanceLabel, "0.0.0.0:8855", + model.JobLabel, "test", + model.MetricNameLabel, "counter_test", + "a", "b", + ) + _, err := tr.AppendExemplar(0, labels, exemplar.Exemplar{Value: 0}) + assert.NoError(t, err) +} + +func TestAppendExemplarWithNoLabels(t *testing.T) { + sink := new(consumertest.MetricsSink) + tr := newTransaction(scrapeCtx, &startTimeAdjuster{startTime: startTimestamp}, sink, nil, componenttest.NewNopReceiverCreateSettings(), nopObsRecv()) + + _, err := tr.AppendExemplar(0, nil, exemplar.Exemplar{Value: 0}) + assert.Equal(t, errNoJobInstance, err) +} + +func TestAppendExemplarWithEmptyLabelArray(t *testing.T) { + sink := new(consumertest.MetricsSink) + tr := newTransaction(scrapeCtx, &startTimeAdjuster{startTime: startTimestamp}, sink, nil, componenttest.NewNopReceiverCreateSettings(), nopObsRecv()) + + _, err := tr.AppendExemplar(0, []labels.Label{}, exemplar.Exemplar{Value: 0}) + assert.Equal(t, errNoJobInstance, err) +} + func nopObsRecv() *obsreport.Receiver { return obsreport.NewReceiver(obsreport.ReceiverSettings{ ReceiverID: config.NewComponentID("prometheus"), @@ -204,7 +277,7 @@ func TestMetricBuilderCounters(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("counter_test", 100, "foo", "bar"), + createDataPoint("counter_test", 100, nil, "foo", "bar"), }, }, }, @@ -225,13 +298,91 @@ func TestMetricBuilderCounters(t *testing.T) { return []pmetric.Metrics{md0} }, }, + { + name: "single-item-with-exemplars", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint( + "counter_test", + 100, + []exemplar.Exemplar{ + { + Value: 1, + Ts: 1663113420863, + Labels: []labels.Label{{Name: model.MetricNameLabel, Value: "counter_test"}, {Name: model.JobLabel, Value: "job"}, {Name: model.InstanceLabel, Value: "instance"}, {Name: "foo", Value: "bar"}}, + }, + { + Value: 1, + Ts: 1663113420863, + Labels: []labels.Label{{Name: "foo", Value: "bar"}, {Name: "trace_id", Value: ""}, {Name: "span_id", Value: ""}}, + }, + { + Value: 1, + Ts: 1663113420863, + Labels: []labels.Label{{Name: "foo", Value: "bar"}, {Name: "trace_id", Value: "10a47365b8aa04e08291fab9deca84db6170"}, {Name: "span_id", Value: "719cee4a669fd7d109ff"}}, + }, + { + Value: 1, + Ts: 1663113420863, + Labels: []labels.Label{{Name: "foo", Value: "bar"}, {Name: "trace_id", Value: "174137cab66dc880"}, {Name: "span_id", Value: "dfa4597a9d"}}, + }, + }, + "foo", "bar"), + }, + }, + }, + wants: func() []pmetric.Metrics { + md0 := pmetric.NewMetrics() + mL0 := md0.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics() + m0 := mL0.AppendEmpty() + m0.SetName("counter_test") + sum := m0.SetEmptySum() + sum.SetAggregationTemporality(pmetric.MetricAggregationTemporalityCumulative) + sum.SetIsMonotonic(true) + pt0 := sum.DataPoints().AppendEmpty() + pt0.SetDoubleValue(100.0) + pt0.SetStartTimestamp(startTimestamp) + pt0.SetTimestamp(tsNanos) + pt0.Attributes().PutString("foo", "bar") + + e0 := pt0.Exemplars().AppendEmpty() + e0.SetTimestamp(timestampFromMs(1663113420863)) + e0.SetDoubleValue(1) + e0.FilteredAttributes().PutString(model.MetricNameLabel, "counter_test") + e0.FilteredAttributes().PutString(model.JobLabel, "job") + e0.FilteredAttributes().PutString(model.InstanceLabel, "instance") + e0.FilteredAttributes().PutString("foo", "bar") + + e1 := pt0.Exemplars().AppendEmpty() + e1.SetTimestamp(timestampFromMs(1663113420863)) + e1.SetDoubleValue(1) + e1.FilteredAttributes().PutString("foo", "bar") + + e2 := pt0.Exemplars().AppendEmpty() + e2.SetTimestamp(timestampFromMs(1663113420863)) + e2.SetDoubleValue(1) + e2.FilteredAttributes().PutString("foo", "bar") + e2.SetTraceID([16]byte{0x10, 0xa4, 0x73, 0x65, 0xb8, 0xaa, 0x04, 0xe0, 0x82, 0x91, 0xfa, 0xb9, 0xde, 0xca, 0x84, 0xdb}) + e2.SetSpanID([8]byte{0x71, 0x9c, 0xee, 0x4a, 0x66, 0x9f, 0xd7, 0xd1}) + + e3 := pt0.Exemplars().AppendEmpty() + e3.SetTimestamp(timestampFromMs(1663113420863)) + e3.SetDoubleValue(1) + e3.FilteredAttributes().PutString("foo", "bar") + e3.SetTraceID([16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x41, 0x37, 0xca, 0xb6, 0x6d, 0xc8, 0x80}) + e3.SetSpanID([8]byte{0x00, 0x00, 0x00, 0xdf, 0xa4, 0x59, 0x7a, 0x9d}) + + return []pmetric.Metrics{md0} + }, + }, { name: "two-items", inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("counter_test", 150, "foo", "bar"), - createDataPoint("counter_test", 25, "foo", "other"), + createDataPoint("counter_test", 150, nil, "foo", "bar"), + createDataPoint("counter_test", 25, nil, "foo", "other"), }, }, }, @@ -263,9 +414,9 @@ func TestMetricBuilderCounters(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("counter_test", 150, "foo", "bar"), - createDataPoint("counter_test", 25, "foo", "other"), - createDataPoint("counter_test2", 100, "foo", "bar"), + createDataPoint("counter_test", 150, nil, "foo", "bar"), + createDataPoint("counter_test", 25, nil, "foo", "other"), + createDataPoint("counter_test2", 100, nil, "foo", "bar"), }, }, }, @@ -308,7 +459,7 @@ func TestMetricBuilderCounters(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("poor_name_count", 100, "foo", "bar"), + createDataPoint("poor_name_count", 100, nil, "foo", "bar"), }, }, }, @@ -345,12 +496,12 @@ func TestMetricBuilderGauges(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("gauge_test", 100, "foo", "bar"), + createDataPoint("gauge_test", 100, nil, "foo", "bar"), }, }, { pts: []*testDataPoint{ - createDataPoint("gauge_test", 90, "foo", "bar"), + createDataPoint("gauge_test", 90, nil, "foo", "bar"), }, }, }, @@ -380,13 +531,105 @@ func TestMetricBuilderGauges(t *testing.T) { return []pmetric.Metrics{md0, md1} }, }, + { + name: "one-gauge-with-exemplars", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint( + "gauge_test", + 100, + []exemplar.Exemplar{ + { + Value: 2, + Ts: 1663350815890, + Labels: []labels.Label{{Name: model.MetricNameLabel, Value: "counter_test"}, {Name: model.JobLabel, Value: "job"}, {Name: model.InstanceLabel, Value: "instance"}, {Name: "foo", Value: "bar"}}, + }, + { + Value: 2, + Ts: 1663350815890, + Labels: []labels.Label{{Name: "foo", Value: "bar"}, {Name: "trace_id", Value: ""}, {Name: "span_id", Value: ""}}, + }, + { + Value: 2, + Ts: 1663350815890, + Labels: []labels.Label{{Name: "foo", Value: "bar"}, {Name: "trace_id", Value: "10a47365b8aa04e08291fab9deca84db6170"}, {Name: "span_id", Value: "719cee4a669fd7d109ff"}}, + }, + { + Value: 2, + Ts: 1663350815890, + Labels: []labels.Label{{Name: "foo", Value: "bar"}, {Name: "trace_id", Value: "174137cab66dc880"}, {Name: "span_id", Value: "dfa4597a9d"}}, + }, + }, + "foo", "bar"), + }, + }, + { + pts: []*testDataPoint{ + createDataPoint("gauge_test", 90, nil, "foo", "bar"), + }, + }, + }, + wants: func() []pmetric.Metrics { + md0 := pmetric.NewMetrics() + mL0 := md0.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics() + m0 := mL0.AppendEmpty() + m0.SetName("gauge_test") + gauge0 := m0.SetEmptyGauge() + pt0 := gauge0.DataPoints().AppendEmpty() + pt0.SetDoubleValue(100.0) + pt0.SetStartTimestamp(0) + pt0.SetTimestamp(tsNanos) + pt0.Attributes().PutString("foo", "bar") + + e0 := pt0.Exemplars().AppendEmpty() + e0.SetTimestamp(timestampFromMs(1663350815890)) + e0.SetDoubleValue(2) + e0.FilteredAttributes().PutString(model.MetricNameLabel, "counter_test") + e0.FilteredAttributes().PutString(model.JobLabel, "job") + e0.FilteredAttributes().PutString(model.InstanceLabel, "instance") + e0.FilteredAttributes().PutString("foo", "bar") + + e1 := pt0.Exemplars().AppendEmpty() + e1.SetTimestamp(timestampFromMs(1663350815890)) + e1.SetDoubleValue(2) + e1.FilteredAttributes().PutString("foo", "bar") + + e2 := pt0.Exemplars().AppendEmpty() + e2.SetTimestamp(timestampFromMs(1663350815890)) + e2.SetDoubleValue(2) + e2.FilteredAttributes().PutString("foo", "bar") + e2.SetTraceID([16]byte{0x10, 0xa4, 0x73, 0x65, 0xb8, 0xaa, 0x04, 0xe0, 0x82, 0x91, 0xfa, 0xb9, 0xde, 0xca, 0x84, 0xdb}) + e2.SetSpanID([8]byte{0x71, 0x9c, 0xee, 0x4a, 0x66, 0x9f, 0xd7, 0xd1}) + + e3 := pt0.Exemplars().AppendEmpty() + e3.SetTimestamp(timestampFromMs(1663350815890)) + e3.SetDoubleValue(2) + e3.FilteredAttributes().PutString("foo", "bar") + e3.SetTraceID([16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x41, 0x37, 0xca, 0xb6, 0x6d, 0xc8, 0x80}) + e3.SetSpanID([8]byte{0x00, 0x00, 0x00, 0xdf, 0xa4, 0x59, 0x7a, 0x9d}) + + md1 := pmetric.NewMetrics() + mL1 := md1.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics() + m1 := mL1.AppendEmpty() + m1.SetName("gauge_test") + gauge1 := m1.SetEmptyGauge() + pt1 := gauge1.DataPoints().AppendEmpty() + pt1.SetDoubleValue(90.0) + pt1.SetStartTimestamp(0) + pt1.SetTimestamp(tsPlusIntervalNanos) + pt1.Attributes().PutString("foo", "bar") + + return []pmetric.Metrics{md0, md1} + }, + }, { name: "gauge-with-different-tags", inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("gauge_test", 100, "foo", "bar"), - createDataPoint("gauge_test", 200, "bar", "foo"), + createDataPoint("gauge_test", 100, nil, "foo", "bar"), + createDataPoint("gauge_test", 200, nil, "bar", "foo"), }, }, }, @@ -418,13 +661,13 @@ func TestMetricBuilderGauges(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("gauge_test", 100, "foo", "bar"), - createDataPoint("gauge_test", 200, "bar", "foo"), + createDataPoint("gauge_test", 100, nil, "foo", "bar"), + createDataPoint("gauge_test", 200, nil, "bar", "foo"), }, }, { pts: []*testDataPoint{ - createDataPoint("gauge_test", 20, "foo", "bar"), + createDataPoint("gauge_test", 20, nil, "foo", "bar"), }, }, }, @@ -476,7 +719,7 @@ func TestMetricBuilderUntyped(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("unknown_test", 100, "foo", "bar"), + createDataPoint("unknown_test", 100, nil, "foo", "bar"), }, }, }, @@ -500,9 +743,9 @@ func TestMetricBuilderUntyped(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("something_not_exists", 100, "foo", "bar"), - createDataPoint("theother_not_exists", 200, "foo", "bar"), - createDataPoint("theother_not_exists", 300, "bar", "foo"), + createDataPoint("something_not_exists", 100, nil, "foo", "bar"), + createDataPoint("theother_not_exists", 200, nil, "foo", "bar"), + createDataPoint("theother_not_exists", 300, nil, "bar", "foo"), }, }, }, @@ -538,7 +781,7 @@ func TestMetricBuilderUntyped(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("some_count", 100, "foo", "bar"), + createDataPoint("some_count", 100, nil, "foo", "bar"), }, }, }, @@ -572,11 +815,11 @@ func TestMetricBuilderHistogram(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("hist_test_bucket", 1, "foo", "bar", "le", "10"), - createDataPoint("hist_test_bucket", 2, "foo", "bar", "le", "20"), - createDataPoint("hist_test_bucket", 10, "foo", "bar", "le", "+inf"), - createDataPoint("hist_test_sum", 99, "foo", "bar"), - createDataPoint("hist_test_count", 10, "foo", "bar"), + createDataPoint("hist_test_bucket", 1, nil, "foo", "bar", "le", "10"), + createDataPoint("hist_test_bucket", 2, nil, "foo", "bar", "le", "20"), + createDataPoint("hist_test_bucket", 10, nil, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_sum", 99, nil, "foo", "bar"), + createDataPoint("hist_test_count", 10, nil, "foo", "bar"), }, }, }, @@ -599,21 +842,119 @@ func TestMetricBuilderHistogram(t *testing.T) { return []pmetric.Metrics{md0} }, }, + { + name: "single item with exemplars", + inputs: []*testScrapedPage{ + { + pts: []*testDataPoint{ + createDataPoint( + "hist_test_bucket", + 1, + []exemplar.Exemplar{ + { + Value: 1, + Ts: 1663113420863, + Labels: []labels.Label{{Name: model.MetricNameLabel, Value: "counter_test"}, {Name: model.JobLabel, Value: "job"}, {Name: model.InstanceLabel, Value: "instance"}, {Name: "foo", Value: "bar"}}, + }, + { + Value: 1, + Ts: 1663113420863, + Labels: []labels.Label{{Name: "foo", Value: "bar"}, {Name: "trace_id", Value: ""}, {Name: "span_id", Value: ""}, {Name: "le", Value: "20"}}, + }, + { + Value: 1, + Ts: 1663113420863, + Labels: []labels.Label{{Name: "foo", Value: "bar"}, {Name: "trace_id", Value: "10a47365b8aa04e08291fab9deca84db6170"}, {Name: "traceid", Value: "e3688e1aa2961786"}, {Name: "span_id", Value: "719cee4a669fd7d109ff"}}, + }, + { + Value: 1, + Ts: 1663113420863, + Labels: []labels.Label{{Name: "foo", Value: "bar"}, {Name: "trace_id", Value: "174137cab66dc880"}, {Name: "span_id", Value: "dfa4597a9d"}}, + }, + { + Value: 1, + Ts: 1663113420863, + Labels: []labels.Label{{Name: "foo", Value: "bar"}, {Name: "trace_id", Value: "174137cab66dc88"}, {Name: "span_id", Value: "dfa4597a9"}}, + }, + }, + "foo", "bar", "le", "10"), + createDataPoint("hist_test_bucket", 2, nil, "foo", "bar", "le", "20"), + createDataPoint("hist_test_bucket", 10, nil, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_sum", 99, nil, "foo", "bar"), + createDataPoint("hist_test_count", 10, nil, "foo", "bar"), + }, + }, + }, + wants: func() []pmetric.Metrics { + md0 := pmetric.NewMetrics() + mL0 := md0.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics() + m0 := mL0.AppendEmpty() + m0.SetName("hist_test") + hist0 := m0.SetEmptyHistogram() + hist0.SetAggregationTemporality(pmetric.MetricAggregationTemporalityCumulative) + pt0 := hist0.DataPoints().AppendEmpty() + pt0.SetCount(10) + pt0.SetSum(99) + pt0.ExplicitBounds().FromRaw([]float64{10, 20}) + pt0.BucketCounts().FromRaw([]uint64{1, 1, 8}) + pt0.SetTimestamp(tsNanos) + pt0.SetStartTimestamp(startTimestamp) + pt0.Attributes().PutString("foo", "bar") + + e0 := pt0.Exemplars().AppendEmpty() + e0.SetTimestamp(timestampFromMs(1663113420863)) + e0.SetDoubleValue(1) + e0.FilteredAttributes().PutString(model.MetricNameLabel, "counter_test") + e0.FilteredAttributes().PutString(model.JobLabel, "job") + e0.FilteredAttributes().PutString(model.InstanceLabel, "instance") + e0.FilteredAttributes().PutString("foo", "bar") + + e1 := pt0.Exemplars().AppendEmpty() + e1.SetTimestamp(timestampFromMs(1663113420863)) + e1.SetDoubleValue(1) + e1.FilteredAttributes().PutString("foo", "bar") + e1.FilteredAttributes().PutString("le", "20") + + e2 := pt0.Exemplars().AppendEmpty() + e2.SetTimestamp(timestampFromMs(1663113420863)) + e2.SetDoubleValue(1) + e2.FilteredAttributes().PutString("foo", "bar") + e2.FilteredAttributes().PutString("traceid", "e3688e1aa2961786") + e2.SetTraceID([16]byte{0x10, 0xa4, 0x73, 0x65, 0xb8, 0xaa, 0x04, 0xe0, 0x82, 0x91, 0xfa, 0xb9, 0xde, 0xca, 0x84, 0xdb}) + e2.SetSpanID([8]byte{0x71, 0x9c, 0xee, 0x4a, 0x66, 0x9f, 0xd7, 0xd1}) + + e3 := pt0.Exemplars().AppendEmpty() + e3.SetTimestamp(timestampFromMs(1663113420863)) + e3.SetDoubleValue(1) + e3.FilteredAttributes().PutString("foo", "bar") + e3.SetTraceID([16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x41, 0x37, 0xca, 0xb6, 0x6d, 0xc8, 0x80}) + e3.SetSpanID([8]byte{0x00, 0x00, 0x00, 0xdf, 0xa4, 0x59, 0x7a, 0x9d}) + + e4 := pt0.Exemplars().AppendEmpty() + e4.SetTimestamp(timestampFromMs(1663113420863)) + e4.SetDoubleValue(1) + e4.FilteredAttributes().PutString("foo", "bar") + e4.FilteredAttributes().PutString("trace_id", "174137cab66dc88") + e4.FilteredAttributes().PutString("span_id", "dfa4597a9") + + return []pmetric.Metrics{md0} + }, + }, { name: "multi-groups", inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("hist_test_bucket", 1, "foo", "bar", "le", "10"), - createDataPoint("hist_test_bucket", 2, "foo", "bar", "le", "20"), - createDataPoint("hist_test_bucket", 10, "foo", "bar", "le", "+inf"), - createDataPoint("hist_test_sum", 99, "foo", "bar"), - createDataPoint("hist_test_count", 10, "foo", "bar"), - createDataPoint("hist_test_bucket", 1, "key2", "v2", "le", "10"), - createDataPoint("hist_test_bucket", 2, "key2", "v2", "le", "20"), - createDataPoint("hist_test_bucket", 3, "key2", "v2", "le", "+inf"), - createDataPoint("hist_test_sum", 50, "key2", "v2"), - createDataPoint("hist_test_count", 3, "key2", "v2"), + createDataPoint("hist_test_bucket", 1, nil, "foo", "bar", "le", "10"), + createDataPoint("hist_test_bucket", 2, nil, "foo", "bar", "le", "20"), + createDataPoint("hist_test_bucket", 10, nil, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_sum", 99, nil, "foo", "bar"), + createDataPoint("hist_test_count", 10, nil, "foo", "bar"), + createDataPoint("hist_test_bucket", 1, nil, "key2", "v2", "le", "10"), + createDataPoint("hist_test_bucket", 2, nil, "key2", "v2", "le", "20"), + createDataPoint("hist_test_bucket", 3, nil, "key2", "v2", "le", "+inf"), + createDataPoint("hist_test_sum", 50, nil, "key2", "v2"), + createDataPoint("hist_test_count", 3, nil, "key2", "v2"), }, }, }, @@ -650,21 +991,21 @@ func TestMetricBuilderHistogram(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("hist_test_bucket", 1, "foo", "bar", "le", "10"), - createDataPoint("hist_test_bucket", 2, "foo", "bar", "le", "20"), - createDataPoint("hist_test_bucket", 10, "foo", "bar", "le", "+inf"), - createDataPoint("hist_test_sum", 99, "foo", "bar"), - createDataPoint("hist_test_count", 10, "foo", "bar"), - createDataPoint("hist_test_bucket", 1, "key2", "v2", "le", "10"), - createDataPoint("hist_test_bucket", 2, "key2", "v2", "le", "20"), - createDataPoint("hist_test_bucket", 3, "key2", "v2", "le", "+inf"), - createDataPoint("hist_test_sum", 50, "key2", "v2"), - createDataPoint("hist_test_count", 3, "key2", "v2"), - createDataPoint("hist_test2_bucket", 1, "foo", "bar", "le", "10"), - createDataPoint("hist_test2_bucket", 2, "foo", "bar", "le", "20"), - createDataPoint("hist_test2_bucket", 3, "foo", "bar", "le", "+inf"), - createDataPoint("hist_test2_sum", 50, "foo", "bar"), - createDataPoint("hist_test2_count", 3, "foo", "bar"), + createDataPoint("hist_test_bucket", 1, nil, "foo", "bar", "le", "10"), + createDataPoint("hist_test_bucket", 2, nil, "foo", "bar", "le", "20"), + createDataPoint("hist_test_bucket", 10, nil, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_sum", 99, nil, "foo", "bar"), + createDataPoint("hist_test_count", 10, nil, "foo", "bar"), + createDataPoint("hist_test_bucket", 1, nil, "key2", "v2", "le", "10"), + createDataPoint("hist_test_bucket", 2, nil, "key2", "v2", "le", "20"), + createDataPoint("hist_test_bucket", 3, nil, "key2", "v2", "le", "+inf"), + createDataPoint("hist_test_sum", 50, nil, "key2", "v2"), + createDataPoint("hist_test_count", 3, nil, "key2", "v2"), + createDataPoint("hist_test2_bucket", 1, nil, "foo", "bar", "le", "10"), + createDataPoint("hist_test2_bucket", 2, nil, "foo", "bar", "le", "20"), + createDataPoint("hist_test2_bucket", 3, nil, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test2_sum", 50, nil, "foo", "bar"), + createDataPoint("hist_test2_count", 3, nil, "foo", "bar"), }, }, }, @@ -714,11 +1055,11 @@ func TestMetricBuilderHistogram(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("hist_test_bucket", 10, "foo", "bar", "le", "+inf"), - createDataPoint("hist_test_bucket", 1, "foo", "bar", "le", "10"), - createDataPoint("hist_test_bucket", 2, "foo", "bar", "le", "20"), - createDataPoint("hist_test_sum", 99, "foo", "bar"), - createDataPoint("hist_test_count", 10, "foo", "bar"), + createDataPoint("hist_test_bucket", 10, nil, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_bucket", 1, nil, "foo", "bar", "le", "10"), + createDataPoint("hist_test_bucket", 2, nil, "foo", "bar", "le", "20"), + createDataPoint("hist_test_sum", 99, nil, "foo", "bar"), + createDataPoint("hist_test_count", 10, nil, "foo", "bar"), }, }, }, @@ -747,9 +1088,9 @@ func TestMetricBuilderHistogram(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("hist_test_bucket", 3, "foo", "bar", "le", "+inf"), - createDataPoint("hist_test_count", 3, "foo", "bar"), - createDataPoint("hist_test_sum", 100, "foo", "bar"), + createDataPoint("hist_test_bucket", 3, nil, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_count", 3, nil, "foo", "bar"), + createDataPoint("hist_test_sum", 100, nil, "foo", "bar"), }, }, }, @@ -777,9 +1118,9 @@ func TestMetricBuilderHistogram(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("hist_test_bucket", 3, "foo", "bar", "le", "20"), - createDataPoint("hist_test_count", 3, "foo", "bar"), - createDataPoint("hist_test_sum", 100, "foo", "bar"), + createDataPoint("hist_test_bucket", 3, nil, "foo", "bar", "le", "20"), + createDataPoint("hist_test_count", 3, nil, "foo", "bar"), + createDataPoint("hist_test_sum", 100, nil, "foo", "bar"), }, }, }, @@ -806,10 +1147,10 @@ func TestMetricBuilderHistogram(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("hist_test_bucket", 1, "foo", "bar", "le", "10"), - createDataPoint("hist_test_bucket", 2, "foo", "bar", "le", "20"), - createDataPoint("hist_test_bucket", 3, "foo", "bar", "le", "+inf"), - createDataPoint("hist_test_count", 3, "foo", "bar"), + createDataPoint("hist_test_bucket", 1, nil, "foo", "bar", "le", "10"), + createDataPoint("hist_test_bucket", 2, nil, "foo", "bar", "le", "20"), + createDataPoint("hist_test_bucket", 3, nil, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_count", 3, nil, "foo", "bar"), }, }, }, @@ -836,8 +1177,8 @@ func TestMetricBuilderHistogram(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("hist_test_sum", 99), - createDataPoint("hist_test_count", 10), + createDataPoint("hist_test_sum", 99, nil), + createDataPoint("hist_test_count", 10, nil), }, }, }, @@ -850,10 +1191,10 @@ func TestMetricBuilderHistogram(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("hist_test_bucket", 1, "foo", "bar", "le", "10"), - createDataPoint("hist_test_bucket", 2, "foo", "bar", "le", "20"), - createDataPoint("hist_test_bucket", 3, "foo", "bar", "le", "+inf"), - createDataPoint("hist_test_sum", 99, "foo", "bar"), + createDataPoint("hist_test_bucket", 1, nil, "foo", "bar", "le", "10"), + createDataPoint("hist_test_bucket", 2, nil, "foo", "bar", "le", "20"), + createDataPoint("hist_test_bucket", 3, nil, "foo", "bar", "le", "+inf"), + createDataPoint("hist_test_sum", 99, nil, "foo", "bar"), }, }, }, @@ -877,7 +1218,7 @@ func TestMetricBuilderSummary(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("summary_test", 5, "foo", "bar", "quantile", "1"), + createDataPoint("summary_test", 5, nil, "foo", "bar", "quantile", "1"), }, }, }, @@ -890,10 +1231,10 @@ func TestMetricBuilderSummary(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("summary_test", 1, "foo", "bar", "quantile", "0.5"), - createDataPoint("summary_test", 2, "foo", "bar", "quantile", "0.75"), - createDataPoint("summary_test", 5, "foo", "bar", "quantile", "1"), - createDataPoint("summary_test_sum", 500, "foo", "bar"), + createDataPoint("summary_test", 1, nil, "foo", "bar", "quantile", "0.5"), + createDataPoint("summary_test", 2, nil, "foo", "bar", "quantile", "0.75"), + createDataPoint("summary_test", 5, nil, "foo", "bar", "quantile", "1"), + createDataPoint("summary_test_sum", 500, nil, "foo", "bar"), }, }, }, @@ -906,10 +1247,10 @@ func TestMetricBuilderSummary(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("summary_test", 1, "foo", "bar", "quantile", "0.5"), - createDataPoint("summary_test", 2, "foo", "bar", "quantile", "0.75"), - createDataPoint("summary_test", 5, "foo", "bar", "quantile", "1"), - createDataPoint("summary_test_count", 500, "foo", "bar"), + createDataPoint("summary_test", 1, nil, "foo", "bar", "quantile", "0.5"), + createDataPoint("summary_test", 2, nil, "foo", "bar", "quantile", "0.75"), + createDataPoint("summary_test", 5, nil, "foo", "bar", "quantile", "1"), + createDataPoint("summary_test_count", 500, nil, "foo", "bar"), }, }, }, @@ -935,7 +1276,6 @@ func TestMetricBuilderSummary(t *testing.T) { q100 := qvL.AppendEmpty() q100.SetQuantile(1) q100.SetValue(5.0) - return []pmetric.Metrics{md0} }, }, @@ -944,8 +1284,8 @@ func TestMetricBuilderSummary(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("summary_test_sum", 100, "foo", "bar"), - createDataPoint("summary_test_count", 500, "foo", "bar"), + createDataPoint("summary_test_sum", 100, nil, "foo", "bar"), + createDataPoint("summary_test_count", 500, nil, "foo", "bar"), }, }, }, @@ -970,11 +1310,11 @@ func TestMetricBuilderSummary(t *testing.T) { inputs: []*testScrapedPage{ { pts: []*testDataPoint{ - createDataPoint("summary_test", 1, "foo", "bar", "quantile", "0.5"), - createDataPoint("summary_test", 2, "foo", "bar", "quantile", "0.75"), - createDataPoint("summary_test", 5, "foo", "bar", "quantile", "1"), - createDataPoint("summary_test_sum", 100, "foo", "bar"), - createDataPoint("summary_test_count", 500, "foo", "bar"), + createDataPoint("summary_test", 1, nil, "foo", "bar", "quantile", "0.5"), + createDataPoint("summary_test", 2, nil, "foo", "bar", "quantile", "0.75"), + createDataPoint("summary_test", 5, nil, "foo", "bar", "quantile", "1"), + createDataPoint("summary_test_sum", 100, nil, "foo", "bar"), + createDataPoint("summary_test_count", 500, nil, "foo", "bar"), }, }, }, @@ -1032,6 +1372,11 @@ func (tt buildTestData) run(t *testing.T) { pt.t = st _, err := tr.Append(0, pt.lb, pt.t, pt.v) assert.NoError(t, err) + + for _, e := range pt.exemplars { + _, err := tr.AppendExemplar(0, pt.lb, e) + assert.NoError(t, err) + } } assert.NoError(t, tr.Commit()) mds := sink.AllMetrics() @@ -1090,16 +1435,17 @@ func (s *startTimeAdjuster) AdjustMetrics(metrics pmetric.Metrics) error { } type testDataPoint struct { - lb labels.Labels - t int64 - v float64 + lb labels.Labels + t int64 + v float64 + exemplars []exemplar.Exemplar } type testScrapedPage struct { pts []*testDataPoint } -func createDataPoint(mname string, value float64, tagPairs ...string) *testDataPoint { +func createDataPoint(mname string, value float64, es []exemplar.Exemplar, tagPairs ...string) *testDataPoint { var lbls []string lbls = append(lbls, tagPairs...) lbls = append(lbls, model.MetricNameLabel, mname) @@ -1107,9 +1453,10 @@ func createDataPoint(mname string, value float64, tagPairs ...string) *testDataP lbls = append(lbls, model.InstanceLabel, "instance") return &testDataPoint{ - lb: labels.FromStrings(lbls...), - t: ts, - v: value, + lb: labels.FromStrings(lbls...), + t: ts, + v: value, + exemplars: es, } }