Skip to content

Commit e50e22c

Browse files
committed
exporter/prometheusexporter: support Summary
Allows Summary metrics to be exported to Prometheus. To walk through this feature, I created an adaptation of a Brian Brazil tutorial for DropWizard, to create a Java server at https://github.com/odeke-em/bugs/tree/master/opentelemetry-collector/2661 and it uses DropWizard, and exports out JVM statistics with Prometheus where the *gc_collection_seconds are of the summary kind when scraped by visiting http://localhost:1234/metrics which produced ``` # HELP jvm_gc_collection_seconds Time spent in a given JVM garbage collector in seconds. # TYPE jvm_gc_collection_seconds summary jvm_gc_collection_seconds_count{gc="G1 Young Generation",} 4.0 jvm_gc_collection_seconds_sum{gc="G1 Young Generation",} 0.026 jvm_gc_collection_seconds_count{gc="G1 Old Generation",} 0.0 ``` and then roundtripped the collector to scrape those metrics and then export them to Prometheus VS making Prometheus directly scrape that endpoint and then comparing results. Also added an end to end test to ensure that a mock DropWizard server which produces JVM statistics can be scraped by the Prometheus receiver, which will feed metrics to an active Prometheus exporter, and then we scrape from the Prometheus exporter to ensure that the summary metrics are written out and that they make sense. DropWizard -> Prometheus Receiver -> Prometheus Exporter -> HTTP scrape + Verify Fixes #2661
1 parent 8cac172 commit e50e22c

File tree

4 files changed

+377
-0
lines changed

4 files changed

+377
-0
lines changed

exporter/prometheusexporter/accumulator.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,43 @@ func (a *lastValueAccumulator) addMetric(metric pdata.Metric, il pdata.Instrumen
9494
return a.accumulateIntHistogram(metric, il, now)
9595
case pdata.MetricDataTypeHistogram:
9696
return a.accumulateDoubleHistogram(metric, il, now)
97+
case pdata.MetricDataTypeSummary:
98+
return a.accumulateSummary(metric, il, now)
99+
default:
100+
a.logger.With(
101+
zap.Any("data_type", metric.DataType()),
102+
zap.Any("metric_name", metric.Name()),
103+
).Error("failed to translate metric")
97104
}
98105

99106
return 0
100107
}
101108

109+
func (a *lastValueAccumulator) accumulateSummary(metric pdata.Metric, il pdata.InstrumentationLibrary, now time.Time) (n int) {
110+
dps := metric.Summary().DataPoints()
111+
for i := 0; i < dps.Len(); i++ {
112+
ip := dps.At(i)
113+
114+
signature := timeseriesSignature(il.Name(), metric, ip.LabelsMap())
115+
116+
v, ok := a.registeredMetrics.Load(signature)
117+
stalePoint := ok &&
118+
ip.Timestamp().AsTime().Before(v.(*accumulatedValue).value.Summary().DataPoints().At(0).Timestamp().AsTime())
119+
120+
if stalePoint {
121+
// Only keep this datapoint if it has a later timestamp.
122+
continue
123+
}
124+
125+
mm := createMetric(metric)
126+
mm.Summary().DataPoints().Append(ip)
127+
a.registeredMetrics.Store(signature, &accumulatedValue{value: mm, instrumentationLibrary: il, updated: now})
128+
n++
129+
}
130+
131+
return n
132+
}
133+
102134
func (a *lastValueAccumulator) accumulateIntGauge(metric pdata.Metric, il pdata.InstrumentationLibrary, now time.Time) (n int) {
103135
dps := metric.IntGauge().DataPoints()
104136
for i := 0; i < dps.Len(); i++ {

exporter/prometheusexporter/collector.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ func (c *collector) convertMetric(metric pdata.Metric) (prometheus.Metric, error
7070
return c.convertIntHistogram(metric)
7171
case pdata.MetricDataTypeHistogram:
7272
return c.convertDoubleHistogram(metric)
73+
case pdata.MetricDataTypeSummary:
74+
return c.convertSummary(metric)
7375
}
7476

7577
return nil, errUnknownMetricType
@@ -207,6 +209,40 @@ func (c *collector) convertIntHistogram(metric pdata.Metric) (prometheus.Metric,
207209
return m, nil
208210
}
209211

212+
func (c *collector) convertSummary(metric pdata.Metric) (prometheus.Metric, error) {
213+
dataPoints := metric.Summary().DataPoints()
214+
215+
var cumSum float64
216+
var cumCount uint64
217+
quantiles := make(map[float64]float64)
218+
labelsMap := pdata.NewStringMap()
219+
for i := 0; i < dataPoints.Len(); i++ {
220+
sdpi := dataPoints.At(i)
221+
qv := sdpi.QuantileValues()
222+
for j := 0; j < qv.Len(); j++ {
223+
qvj := qv.At(j)
224+
quantiles[qvj.Quantile()] += qvj.Value()
225+
}
226+
// Copy all the label map values.
227+
sdpi.LabelsMap().ForEach(func(key, value string) {
228+
labelsMap.Insert(key, value)
229+
})
230+
cumSum += sdpi.Sum()
231+
cumCount += sdpi.Count()
232+
}
233+
234+
desc, labelValues := c.getMetricMetadata(metric, labelsMap)
235+
m, err := prometheus.NewConstSummary(desc, cumCount, cumSum, quantiles, labelValues...)
236+
if err != nil {
237+
return nil, err
238+
}
239+
if c.sendTimestamps {
240+
ip := dataPoints.At(0)
241+
return prometheus.NewMetricWithTimestamp(ip.Timestamp().AsTime(), m), nil
242+
}
243+
return m, nil
244+
}
245+
210246
func (c *collector) convertDoubleHistogram(metric pdata.Metric) (prometheus.Metric, error) {
211247
ip := metric.Histogram().DataPoints().At(0)
212248
desc, labels := c.getMetricMetadata(metric, ip.LabelsMap())

exporter/prometheusexporter/collector_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,10 +349,12 @@ func TestCollectMetrics(t *testing.T) {
349349
require.Equal(t, tt.value, *pbMetric.Counter.Value)
350350
require.Nil(t, pbMetric.Gauge)
351351
require.Nil(t, pbMetric.Histogram)
352+
require.Nil(t, pbMetric.Summary)
352353
case prometheus.GaugeValue:
353354
require.Equal(t, tt.value, *pbMetric.Gauge.Value)
354355
require.Nil(t, pbMetric.Counter)
355356
require.Nil(t, pbMetric.Histogram)
357+
require.Nil(t, pbMetric.Summary)
356358
}
357359
}
358360
require.Equal(t, 1, j)
@@ -488,3 +490,115 @@ func TestAccumulateHistograms(t *testing.T) {
488490
}
489491
}
490492
}
493+
494+
func TestAccumulateSummary(t *testing.T) {
495+
quantileValue := func(pN, value float64) pdata.ValueAtQuantile {
496+
vqpN := pdata.NewValueAtQuantile()
497+
vqpN.SetQuantile(pN)
498+
vqpN.SetValue(value)
499+
return vqpN
500+
}
501+
quantilesFromMap := func(qf map[float64]float64) (qL []*io_prometheus_client.Quantile) {
502+
f64Ptr := func(v float64) *float64 { return &v }
503+
for quantile, value := range qf {
504+
qL = append(qL, &io_prometheus_client.Quantile{
505+
Quantile: f64Ptr(quantile), Value: f64Ptr(value),
506+
})
507+
}
508+
return qL
509+
}
510+
tests := []struct {
511+
name string
512+
metric func(time.Time) pdata.Metric
513+
wantSum float64
514+
wantCount uint64
515+
wantQuantiles []*io_prometheus_client.Quantile
516+
}{
517+
{
518+
name: "Summary with single point",
519+
wantSum: 0.012,
520+
wantCount: 10,
521+
wantQuantiles: quantilesFromMap(map[float64]float64{
522+
0.50: 190,
523+
0.99: 817,
524+
}),
525+
metric: func(ts time.Time) (metric pdata.Metric) {
526+
sp := pdata.NewSummaryDataPoint()
527+
sp.SetCount(10)
528+
sp.SetSum(0.012)
529+
sp.SetCount(10)
530+
sp.LabelsMap().Insert("label_1", "1")
531+
sp.LabelsMap().Insert("label_2", "2")
532+
sp.SetTimestamp(pdata.TimestampFromTime(ts))
533+
534+
sp.QuantileValues().Append(quantileValue(0.50, 190))
535+
sp.QuantileValues().Append(quantileValue(0.99, 817))
536+
537+
metric = pdata.NewMetric()
538+
metric.SetName("test_metric")
539+
metric.SetDataType(pdata.MetricDataTypeSummary)
540+
metric.Summary().DataPoints().Append(sp)
541+
metric.SetDescription("test description")
542+
543+
return
544+
},
545+
},
546+
}
547+
548+
for _, tt := range tests {
549+
for _, sendTimestamp := range []bool{true, false} {
550+
name := tt.name
551+
if sendTimestamp {
552+
name += "/WithTimestamp"
553+
}
554+
t.Run(name, func(t *testing.T) {
555+
ts := time.Now()
556+
metric := tt.metric(ts)
557+
c := collector{
558+
accumulator: &mockAccumulator{
559+
[]pdata.Metric{metric},
560+
},
561+
sendTimestamps: sendTimestamp,
562+
logger: zap.NewNop(),
563+
}
564+
565+
ch := make(chan prometheus.Metric, 1)
566+
go func() {
567+
c.Collect(ch)
568+
close(ch)
569+
}()
570+
571+
n := 0
572+
for m := range ch {
573+
n++
574+
require.Contains(t, m.Desc().String(), "fqName: \"test_metric\"")
575+
require.Contains(t, m.Desc().String(), "variableLabels: [label_1 label_2]")
576+
577+
pbMetric := io_prometheus_client.Metric{}
578+
m.Write(&pbMetric)
579+
580+
labelsKeys := map[string]string{"label_1": "1", "label_2": "2"}
581+
for _, l := range pbMetric.Label {
582+
require.Equal(t, labelsKeys[*l.Name], *l.Value)
583+
}
584+
585+
if sendTimestamp {
586+
require.Equal(t, ts.UnixNano()/1e6, *(pbMetric.TimestampMs))
587+
} else {
588+
require.Nil(t, pbMetric.TimestampMs)
589+
}
590+
591+
require.Nil(t, pbMetric.Gauge)
592+
require.Nil(t, pbMetric.Counter)
593+
require.Nil(t, pbMetric.Histogram)
594+
595+
s := *pbMetric.Summary
596+
require.Equal(t, tt.wantCount, *s.SampleCount)
597+
require.Equal(t, tt.wantSum, *s.SampleSum)
598+
require.Equal(t, tt.wantQuantiles, s.Quantile)
599+
}
600+
require.Equal(t, 1, n)
601+
})
602+
}
603+
}
604+
}

0 commit comments

Comments
 (0)