Skip to content

Commit f978447

Browse files
add exemplars support to prometheusremotewriteexporter
Signed-off-by: Anne-Elisabeth Lelievre <[email protected]>
1 parent ac26625 commit f978447

File tree

3 files changed

+230
-18
lines changed

3 files changed

+230
-18
lines changed

exporter/prometheusremotewriteexporter/helper.go

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"time"
2424
"unicode"
2525

26+
"github.com/prometheus/prometheus/pkg/timestamp"
27+
2628
"github.com/prometheus/common/model"
2729
"github.com/prometheus/prometheus/prompb"
2830
"go.opentelemetry.io/collector/model/pdata"
@@ -85,6 +87,36 @@ func addSample(tsMap map[string]*prompb.TimeSeries, sample *prompb.Sample, label
8587
}
8688
}
8789

90+
// addSampleAndExemplar finds a TimeSeries in tsMap that corresponds to the label set labels, add exemplars and samples to the TimeSeries; it
91+
// creates a new TimeSeries in the map if not found. tsMap is unmodified if either of its parameters is nil.
92+
func addSampleAndExemplar(tsMap map[string]*prompb.TimeSeries, sample *prompb.Sample, exemplar *prompb.Exemplar, labels []prompb.Label, metric pdata.Metric) {
93+
if sample == nil || labels == nil || tsMap == nil {
94+
return
95+
}
96+
97+
sig := timeSeriesSignature(metric, &labels)
98+
ts, ok := tsMap[sig]
99+
100+
if ok {
101+
ts.Samples = append(ts.Samples, *sample)
102+
103+
if exemplar != nil {
104+
ts.Exemplars = append(ts.Exemplars, *exemplar)
105+
}
106+
} else {
107+
newTs := &prompb.TimeSeries{
108+
Labels: labels,
109+
Samples: []prompb.Sample{*sample},
110+
}
111+
112+
if exemplar != nil {
113+
newTs.Exemplars = []prompb.Exemplar{*exemplar}
114+
}
115+
116+
tsMap[sig] = newTs
117+
}
118+
}
119+
88120
// timeSeries return a string signature in the form of:
89121
// TYPE-label1-value1- ... -labelN-valueN
90122
// the label slice should not contain duplicate label names; this method sorts the slice by label name before creating
@@ -318,7 +350,10 @@ func addSingleHistogramDataPoint(pt pdata.HistogramDataPoint, resource pdata.Res
318350
}
319351
boundStr := strconv.FormatFloat(bound, 'f', -1, 64)
320352
labels := createAttributes(resource, pt.Attributes(), externalLabels, nameStr, baseName+bucketStr, leStr, boundStr)
321-
addSample(tsMap, bucket, labels, metric)
353+
354+
promExemplar := getPromExemplar(pt, index)
355+
356+
addSampleAndExemplar(tsMap, bucket, promExemplar, labels, metric)
322357
}
323358
// add le=+Inf bucket
324359
cumulativeCount += pt.BucketCounts()[len(pt.BucketCounts())-1]
@@ -327,7 +362,34 @@ func addSingleHistogramDataPoint(pt pdata.HistogramDataPoint, resource pdata.Res
327362
Timestamp: time,
328363
}
329364
infLabels := createAttributes(resource, pt.Attributes(), externalLabels, nameStr, baseName+bucketStr, leStr, pInfStr)
330-
addSample(tsMap, infBucket, infLabels, metric)
365+
366+
promExemplar := getPromExemplar(pt, len(pt.BucketCounts())-1)
367+
368+
addSampleAndExemplar(tsMap, infBucket, promExemplar, infLabels, metric)
369+
}
370+
371+
func getPromExemplar(pt pdata.HistogramDataPoint, index int) *prompb.Exemplar {
372+
if index < pt.Exemplars().Len() {
373+
exemplar := pt.Exemplars().At(index)
374+
375+
promExemplar := &prompb.Exemplar{
376+
Value: exemplar.DoubleVal(),
377+
Timestamp: timestamp.FromTime(exemplar.Timestamp().AsTime()),
378+
}
379+
380+
labels := exemplar.FilteredAttributes().AsRaw()
381+
for key, value := range labels {
382+
promLabel := prompb.Label{
383+
Name: key,
384+
Value: value.(string),
385+
}
386+
387+
promExemplar.Labels = append(promExemplar.Labels, promLabel)
388+
}
389+
390+
return promExemplar
391+
}
392+
return nil
331393
}
332394

333395
// addSingleSummaryDataPoint converts pt to len(QuantileValues) + 2 samples.

exporter/prometheusremotewriteexporter/helper_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ package prometheusremotewriteexporter
1616

1717
import (
1818
"testing"
19+
"time"
1920

21+
"github.com/prometheus/prometheus/pkg/timestamp"
2022
"github.com/prometheus/prometheus/prompb"
2123
"github.com/stretchr/testify/assert"
2224
"go.opentelemetry.io/collector/model/pdata"
@@ -486,3 +488,101 @@ func TestEnsureTimeseriesPointsAreSortedByTimestamp(t *testing.T) {
486488
}
487489
}
488490
}
491+
492+
// Test_addSampleAndExemplar checks addSampleAndExemplar updates the map it receives correctly based on the sample, exemplars and Label
493+
// set it receives.
494+
// Test cases are two exemplars/samples belonging to the same TimeSeries and two exemplars/samples belong to different TimeSeries
495+
// case.
496+
func Test_addSampleAndExemplar(t *testing.T) {
497+
type testCase struct {
498+
metric pdata.Metric
499+
sample prompb.Sample
500+
exemplar prompb.Exemplar
501+
labels []prompb.Label
502+
}
503+
504+
tests := []struct {
505+
name string
506+
orig map[string]*prompb.TimeSeries
507+
testCase []testCase
508+
want map[string]*prompb.TimeSeries
509+
}{
510+
{
511+
"two_histogram_points_same_ts_same_metric",
512+
map[string]*prompb.TimeSeries{},
513+
[]testCase{
514+
{validMetrics1[validHistogram],
515+
getSample(floatVal1, msTime1),
516+
getExemplar(float64(intVal1), msTime1),
517+
promLbs1,
518+
},
519+
{
520+
validMetrics2[validHistogram],
521+
getSample(floatVal2, msTime2),
522+
getExemplar(float64(intVal2), msTime2),
523+
promLbs1,
524+
},
525+
},
526+
twoHistogramPointsSameTs,
527+
},
528+
{
529+
"two_histogram_points_different_ts_same_metric",
530+
map[string]*prompb.TimeSeries{},
531+
[]testCase{
532+
{validMetrics1[validHistogram],
533+
getSample(float64(intVal1), msTime1),
534+
getExemplar(float64(intVal1), msTime1),
535+
promLbs1,
536+
},
537+
{validMetrics1[validHistogram],
538+
getSample(float64(intVal1), msTime2),
539+
getExemplar(float64(intVal1), msTime2),
540+
promLbs2,
541+
},
542+
},
543+
twoHistogramPointsDifferentTs,
544+
},
545+
}
546+
// run tests
547+
for _, tt := range tests {
548+
t.Run(tt.name, func(t *testing.T) {
549+
addSampleAndExemplar(tt.orig, &tt.testCase[0].sample, &tt.testCase[0].exemplar,
550+
tt.testCase[0].labels, tt.testCase[0].metric)
551+
addSampleAndExemplar(tt.orig, &tt.testCase[1].sample, &tt.testCase[1].exemplar,
552+
tt.testCase[1].labels, tt.testCase[1].metric)
553+
assert.Exactly(t, tt.want, tt.orig)
554+
})
555+
}
556+
}
557+
558+
// Test_getPromExemplar checks if exemplar is not nul and return the prometheus exemplar.
559+
func Test_getPromExemplar(t *testing.T) {
560+
tnow := time.Now()
561+
tests := []struct {
562+
name string
563+
histogram *pdata.HistogramDataPoint
564+
expected *prompb.Exemplar
565+
}{
566+
{
567+
"with_exemplars",
568+
getHistogramDataPointWithExemplars(tnow, floatVal1, traceIDKey, traceIDValue1),
569+
&prompb.Exemplar{
570+
Value: floatVal1,
571+
Timestamp: timestamp.FromTime(tnow),
572+
Labels: []prompb.Label{getLabel(traceIDKey, traceIDValue1)},
573+
},
574+
},
575+
{
576+
"without_exemplar",
577+
getHistogramDataPoint(),
578+
nil,
579+
},
580+
}
581+
// run tests
582+
for _, tt := range tests {
583+
t.Run(tt.name, func(t *testing.T) {
584+
requests := getPromExemplar(*tt.histogram, 0)
585+
assert.Exactly(t, tt.expected, requests)
586+
})
587+
}
588+
}

exporter/prometheusremotewriteexporter/testutil_test.go

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,24 @@ var (
3030
msTime2 = int64(time2 / uint64(int64(time.Millisecond)/int64(time.Nanosecond)))
3131
msTime3 = int64(time3 / uint64(int64(time.Millisecond)/int64(time.Nanosecond)))
3232

33-
label11 = "test_label11"
34-
value11 = "test_value11"
35-
label12 = "test_label12"
36-
value12 = "test_value12"
37-
label21 = "test_label21"
38-
value21 = "test_value21"
39-
label22 = "test_label22"
40-
value22 = "test_value22"
41-
label31 = "test_label31"
42-
value31 = "test_value31"
43-
label32 = "test_label32"
44-
value32 = "test_value32"
45-
label41 = "__test_label41__"
46-
value41 = "test_value41"
47-
dirty1 = "%"
48-
dirty2 = "?"
33+
label11 = "test_label11"
34+
value11 = "test_value11"
35+
label12 = "test_label12"
36+
value12 = "test_value12"
37+
label21 = "test_label21"
38+
value21 = "test_value21"
39+
label22 = "test_label22"
40+
value22 = "test_value22"
41+
label31 = "test_label31"
42+
value31 = "test_value31"
43+
label32 = "test_label32"
44+
value32 = "test_value32"
45+
label41 = "__test_label41__"
46+
value41 = "test_value41"
47+
dirty1 = "%"
48+
dirty2 = "?"
49+
traceIDValue1 = "traceID-value1"
50+
traceIDKey = "trace_id"
4951

5052
intVal1 int64 = 1
5153
intVal2 int64 = 2
@@ -78,6 +80,21 @@ var (
7880
"Gauge" + "-" + label21 + "-" + value21 + "-" + label22 + "-" + value22: getTimeSeries(getPromLabels(label21, value21, label22, value22),
7981
getSample(float64(intVal1), msTime2)),
8082
}
83+
twoHistogramPointsSameTs = map[string]*prompb.TimeSeries{
84+
"Histogram" + "-" + label11 + "-" + value11 + "-" + label12 + "-" + value12: getTimeSeriesWithSamplesAndExemplars(getPromLabels(label11, value11, label12, value12),
85+
[]prompb.Sample{getSample(float64(intVal1), msTime1), getSample(float64(intVal2), msTime2)},
86+
getExemplar(float64(intVal1), msTime1),
87+
getExemplar(float64(intVal2), msTime2)),
88+
}
89+
90+
twoHistogramPointsDifferentTs = map[string]*prompb.TimeSeries{
91+
"Histogram" + "-" + label11 + "-" + value11 + "-" + label12 + "-" + value12: getTimeSeriesWithSamplesAndExemplars(getPromLabels(label11, value11, label12, value12),
92+
[]prompb.Sample{getSample(float64(intVal1), msTime1)},
93+
getExemplar(float64(intVal1), msTime1)),
94+
"Histogram" + "-" + label21 + "-" + value21 + "-" + label22 + "-" + value22: getTimeSeriesWithSamplesAndExemplars(getPromLabels(label21, value21, label22, value22),
95+
[]prompb.Sample{getSample(float64(intVal1), msTime2)},
96+
getExemplar(float64(intVal1), msTime2)),
97+
}
8198
bounds = []float64{0.1, 0.5, 0.99}
8299
buckets = []uint64{1, 2, 3}
83100

@@ -184,6 +201,39 @@ func getTimeSeries(labels []prompb.Label, samples ...prompb.Sample) *prompb.Time
184201
}
185202
}
186203

204+
func getExemplar(v float64, t int64) prompb.Exemplar {
205+
return prompb.Exemplar{
206+
Value: v,
207+
Timestamp: t,
208+
Labels: []prompb.Label{getLabel(traceIDKey, traceIDValue1)},
209+
}
210+
}
211+
212+
func getTimeSeriesWithSamplesAndExemplars(labels []prompb.Label, samples []prompb.Sample, exemplars ...prompb.Exemplar) *prompb.TimeSeries {
213+
return &prompb.TimeSeries{
214+
Labels: labels,
215+
Samples: samples,
216+
Exemplars: exemplars,
217+
}
218+
}
219+
220+
func getHistogramDataPointWithExemplars(time time.Time, value float64, attributeKey string, attributeValue string) *pdata.HistogramDataPoint {
221+
h := pdata.NewHistogramDataPoint()
222+
223+
e := h.Exemplars().AppendEmpty()
224+
e.SetDoubleVal(value)
225+
e.SetTimestamp(pdata.NewTimestampFromTime(time))
226+
e.FilteredAttributes().Insert(attributeKey, pdata.NewAttributeValueString(attributeValue))
227+
228+
return &h
229+
}
230+
231+
func getHistogramDataPoint() *pdata.HistogramDataPoint {
232+
h := pdata.NewHistogramDataPoint()
233+
234+
return &h
235+
}
236+
187237
func getQuantiles(bounds []float64, values []float64) pdata.ValueAtQuantileSlice {
188238
quantiles := pdata.NewValueAtQuantileSlice()
189239
quantiles.EnsureCapacity(len(bounds))

0 commit comments

Comments
 (0)