From 3fc352c2d0a510dd66bd4596c539163df68c3c63 Mon Sep 17 00:00:00 2001 From: Arun Mahendra Date: Thu, 21 Apr 2022 13:43:56 -0500 Subject: [PATCH] Adding a feature to prometheusexporter to export exemplars along with histogram metrics --- exporter/prometheusexporter/collector.go | 27 +++++++ exporter/prometheusexporter/collector_test.go | 80 +++++++++++++++++++ exporter/prometheusexporter/config.go | 3 + exporter/prometheusexporter/factory.go | 1 + exporter/prometheusexporter/go.mod | 2 +- exporter/prometheusexporter/go.sum | 4 +- exporter/prometheusexporter/prometheus.go | 5 +- 7 files changed, 117 insertions(+), 5 deletions(-) diff --git a/exporter/prometheusexporter/collector.go b/exporter/prometheusexporter/collector.go index 45e9634868318..b8981d8a8eaba 100644 --- a/exporter/prometheusexporter/collector.go +++ b/exporter/prometheusexporter/collector.go @@ -193,8 +193,10 @@ func (c *collector) convertDoubleHistogram(metric pmetric.Metric, resourceAttrs desc, attributes := c.getMetricMetadata(metric, ip.Attributes(), resourceAttrs) indicesMap := make(map[float64]int) + buckets := make([]float64, 0, len(ip.MBucketCounts())) for index, bucket := range ip.MExplicitBounds() { + if _, added := indicesMap[bucket]; !added { indicesMap[bucket] = index buckets = append(buckets, bucket) @@ -215,11 +217,36 @@ func (c *collector) convertDoubleHistogram(metric pmetric.Metric, resourceAttrs points[bucket] = cumCount } + arrLen := ip.Exemplars().Len() + exemplars := make([]prometheus.Exemplar, arrLen) + for i := 0; i < arrLen; i++ { + e := ip.Exemplars().At(i) + + labels := make(prometheus.Labels, e.FilteredAttributes().Len()) + e.FilteredAttributes().Range(func(k string, v pcommon.Value) bool { + labels[k] = v.AsString() + return true + }) + + exemplars[i] = prometheus.Exemplar{ + Value: e.DoubleVal(), + Labels: labels, + Timestamp: e.Timestamp().AsTime(), + } + } + m, err := prometheus.NewConstHistogram(desc, ip.Count(), ip.Sum(), points, attributes...) if err != nil { return nil, err } + if arrLen > 0 { + m, err = prometheus.NewMetricWithExemplars(m, exemplars...) + if err != nil { + return nil, err + } + } + if c.sendTimestamps { return prometheus.NewMetricWithTimestamp(ip.Timestamp().AsTime(), m), nil } diff --git a/exporter/prometheusexporter/collector_test.go b/exporter/prometheusexporter/collector_test.go index 5870ece0ce638..2cb5c3658cb47 100644 --- a/exporter/prometheusexporter/collector_test.go +++ b/exporter/prometheusexporter/collector_test.go @@ -96,6 +96,86 @@ func TestConvertInvalidMetric(t *testing.T) { } } +func TestConvertDoubleHistogramExemplar(t *testing.T) { + // initialize empty histogram + metric := pmetric.NewMetric() + metric.SetDataType(pmetric.MetricDataTypeHistogram) + metric.SetName("test_metric") + metric.SetDescription("this is test metric") + metric.SetUnit("T") + + // initialize empty datapoint + hd := metric.Histogram().DataPoints().AppendEmpty() + + bounds := []float64{5, 25, 90} + hd.SetExplicitBounds(bounds) + bc := []uint64{2, 35, 70} + hd.SetBucketCounts(bc) + + exemplarTs, _ := time.Parse("unix", "Mon Jan _2 15:04:05 MST 2006") + exemplars := []prometheus.Exemplar{ + { + Timestamp: exemplarTs, + Value: 3, + Labels: prometheus.Labels{"test_label_0": "label_value_0"}, + }, + { + Timestamp: exemplarTs, + Value: 50, + Labels: prometheus.Labels{"test_label_1": "label_value_1"}, + }, + { + Timestamp: exemplarTs, + Value: 78, + Labels: prometheus.Labels{"test_label_2": "label_value_2"}, + }, + } + + // add each exemplar value to the metric + for _, e := range exemplars { + pde := hd.Exemplars().AppendEmpty() + pde.SetDoubleVal(e.Value) + for k, v := range e.Labels { + pde.FilteredAttributes().InsertString(k, v) + } + pde.SetTimestamp(pcommon.NewTimestampFromTime(e.Timestamp)) + } + + pMap := pcommon.NewMap() + + c := collector{ + accumulator: &mockAccumulator{ + metrics: []pmetric.Metric{metric}, + resourceAttributes: pMap, + }, + logger: zap.NewNop(), + } + + pbMetric, _ := c.convertDoubleHistogram(metric, pMap) + m := io_prometheus_client.Metric{} + pbMetric.Write(&m) + + buckets := m.GetHistogram().GetBucket() + + require.Equal(t, 3, len(buckets)) + + require.Equal(t, 3.0, buckets[0].GetExemplar().GetValue()) + require.Equal(t, int32(128654848), buckets[0].GetExemplar().GetTimestamp().GetNanos()) + require.Equal(t, 1, len(buckets[0].GetExemplar().GetLabel())) + require.Equal(t, "test_label_0", buckets[0].GetExemplar().GetLabel()[0].GetName()) + require.Equal(t, "label_value_0", buckets[0].GetExemplar().GetLabel()[0].GetValue()) + + require.Equal(t, 0.0, buckets[1].GetExemplar().GetValue()) + require.Equal(t, int32(0), buckets[1].GetExemplar().GetTimestamp().GetNanos()) + require.Equal(t, 0, len(buckets[1].GetExemplar().GetLabel())) + + require.Equal(t, 78.0, buckets[2].GetExemplar().GetValue()) + require.Equal(t, int32(128654848), buckets[2].GetExemplar().GetTimestamp().GetNanos()) + require.Equal(t, 1, len(buckets[2].GetExemplar().GetLabel())) + require.Equal(t, "test_label_2", buckets[2].GetExemplar().GetLabel()[0].GetName()) + require.Equal(t, "label_value_2", buckets[2].GetExemplar().GetLabel()[0].GetValue()) +} + // errorCheckCore keeps track of logged errors type errorCheckCore struct { errorMessages []string diff --git a/exporter/prometheusexporter/config.go b/exporter/prometheusexporter/config.go index 41ab87ad0f4f7..9e6e44d98ccfd 100644 --- a/exporter/prometheusexporter/config.go +++ b/exporter/prometheusexporter/config.go @@ -46,6 +46,9 @@ type Config struct { // ResourceToTelemetrySettings defines configuration for converting resource attributes to metric labels. ResourceToTelemetrySettings resourcetotelemetry.Settings `mapstructure:"resource_to_telemetry_conversion"` + // EnableOpenMetrics enables the use of the OpenMetrics encoding option for the prometheus exporter. + EnableOpenMetrics bool `mapstructure:"enable_open_metrics"` + // skipSanitizeLabel if enabled, labels that start with _ are not sanitized skipSanitizeLabel bool } diff --git a/exporter/prometheusexporter/factory.go b/exporter/prometheusexporter/factory.go index 05af3cdde06f9..3c4be9a52a136 100644 --- a/exporter/prometheusexporter/factory.go +++ b/exporter/prometheusexporter/factory.go @@ -47,6 +47,7 @@ func createDefaultConfig() config.Exporter { SendTimestamps: false, MetricExpiration: time.Minute * 5, skipSanitizeLabel: featuregate.GetRegistry().IsEnabled(dropSanitizationGate.ID), + EnableOpenMetrics: false, } } diff --git a/exporter/prometheusexporter/go.mod b/exporter/prometheusexporter/go.mod index f2851e8ef931c..9641655fff7c6 100644 --- a/exporter/prometheusexporter/go.mod +++ b/exporter/prometheusexporter/go.mod @@ -6,7 +6,7 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.51.0 github.com/open-telemetry/opentelemetry-collector-contrib/pkg/resourcetotelemetry v0.51.0 github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.51.0 - github.com/prometheus/client_golang v1.12.2 + github.com/prometheus/client_golang v1.12.2-0.20220318110013-3bc8f2c651ff github.com/prometheus/client_model v0.2.0 github.com/prometheus/common v0.34.0 github.com/prometheus/prometheus v0.35.1-0.20220503184552-2381d7be5731 diff --git a/exporter/prometheusexporter/go.sum b/exporter/prometheusexporter/go.sum index c261cfc312214..921bc83de85e3 100644 --- a/exporter/prometheusexporter/go.sum +++ b/exporter/prometheusexporter/go.sum @@ -1018,8 +1018,8 @@ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= -github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.12.2-0.20220318110013-3bc8f2c651ff h1:iSQ1cKhnRgOLZhTsXtL+zgT6PGM6WWj9qxJvBktwCB4= +github.com/prometheus/client_golang v1.12.2-0.20220318110013-3bc8f2c651ff/go.mod h1:KLGtagmR8GET5drwfO/cxzy0C3Om4pyqDr8kpjTJm6s= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/exporter/prometheusexporter/prometheus.go b/exporter/prometheusexporter/prometheus.go index 15887e49d54bb..e0c226b6af0fb 100644 --- a/exporter/prometheusexporter/prometheus.go +++ b/exporter/prometheusexporter/prometheus.go @@ -56,8 +56,9 @@ func newPrometheusExporter(config *Config, set component.ExporterCreateSettings) handler: promhttp.HandlerFor( registry, promhttp.HandlerOpts{ - ErrorHandling: promhttp.ContinueOnError, - ErrorLog: newPromLogger(set.Logger), + ErrorHandling: promhttp.ContinueOnError, + ErrorLog: newPromLogger(set.Logger), + EnableOpenMetrics: config.EnableOpenMetrics, }, ), }, nil